This article will describe shell script Tips.
Table of Contents
- 1. Remove whitespace from delimiter at for statement
- 2. Remove if statement with && and ||
- 3. Treat echo's outputs as return value
- 4. Use $(cmd) rather than `cmd`
- 5. (cmd), $(cmd) and `cmd` is child process
- 6. Use echo and return error value
- 7. Function declaration in one line
- 8. Reflection with type
- 9. Debug with /bin/sh -x
1 Remove whitespace from delimiter at for statement
IFS has shell script delimiters (whitespace and newline by default). This is useful for renaming file which has a whitespace.
#!/bin/sh func_a() { echo "func_a" echo "arg1 = ${1}" echo "arg2 = ${2}" for arg in $@; do echo "arg = $arg"; done } func_b() { echo "func_b" echo "arg1 = ${1}" echo "arg2 = ${2}" IFS=" " for arg in $@; do echo "arg = $arg"; done } func_a "This is arg1." "This is arg2." func_b "This is arg1." "This is arg2."
This script outputs as below. func_a separates with whitespace and func_b does not.
func_a arg1 = This is arg1. arg2 = This is arg2. arg = This arg = is arg = arg1. arg = This arg = is arg = arg2. func_b arg1 = This is arg1. arg2 = This is arg2. arg = This is arg1. arg = This is arg2.
2 Remove if statement with && and ||
Using && and || avoids abuse of if statement.
The following syntax like perl's "open or die" with && and || provides if statement. But the order of && and || cannot be reversed.
$ true && echo "true" || echo "false" true $ false && echo "true" || echo "false" false
If command after && is failed, command after || will be run.
$ true && false || echo "false" false
Multiple && and || are also useful.
$ true && true && echo "true" true $ false || false || echo "false" false
3 Treat echo's outputs as return value
You can assign echo's outputs to variable.
#!/bin/sh div() { [ ${2} -eq 0 ] && return 1 echo $(expr ${1} / ${2}) } val=$(div 10 5) && echo "10 / 5 = ${val}" val=$(div 10 0) && echo "10 / 0 = ${val}"
This script outputs as below. When div function returns 0, assigned value will output.
10 / 5 = 2
4 Use $(cmd) rather than `cmd`
$(cmd) can be nested as it is.
$(cmd1 $(cmd2 $(cmd3)))
Nested `cmd` needs nested escape character.
`cmd1 \`cmd2 \\`cmd3\\` \``
5 (cmd), $(cmd) and `cmd` is child process
(cmd), $(cmd) and `cmd` is child process. The difference with & is to wait for terminate.
call exit command in child process does not terminate parent. And parent process cannot access assigned value in child.
#!/bin/sh func() { echo "prev exit" exit 1 echo "post exit" return 0 } (echo "prev exit"; exit 1; echo "post exit") echo "ret = $?" msg=$(func) echo "ret = $?, msg = ${msg}"
This script outputs as below.
prev exit ret = 1 ret = 1, msg = prev exit
6 Use echo and return error value
While echo's output can be redirect to stderr, echo's return value will be true except serious error which output does not work like I/O error. If you want to output error message (echo "error") and return error value (cmd) for error case (false), echo's return value will be wrong. The following "cmd" does not run except echo's serious error.
$ false || echo "error" 1>&2 || cmd
Using echo and exit with "()" provides echo's output and return error value.
#!/bin/sh do_echo() { false || echo "error" 1>&2 || return 1 } do_echo_and_exit() { false || (echo "error" 1>&2 && exit 1) || return 1 } do_echo || echo "do_echo is failed" 1>&2 do_echo_and_exit || echo "do_echo_and_exit is failed" 1>&2
This script outputs as below.
error error do_echo_and_exit is failed
The error function which outputs error message and returns error value is useful too.
#!/bin/sh error() { echo "$@" 1>&2 return 1 } false || error "error" || echo "failed" 1>&2
This script outputs as below.
error failed
7 Function declaration in one line
You can define function declaration in one line with semicolon.
func() { echo "func"; }
8 Reflection with type
For example, there is a case statement which will call func_x function with value, and the definition of func_x will increase in the future.
In this case, you need to define func_x (callee) and also modify case statement (caller).
#!/bin/sh func_a() { echo "func_a is called"; } func_b() { echo "func_b is called"; } read name case ${name} in a) func_a;; b) func_b;; *) echo "func_${name} is not defined";; esac
type provides reflection code, modifying caller is unnecessary.
#!/bin/sh func_a() { echo "func_a is called"; } func_b() { echo "func_b is called"; } read name type func_${name} > /dev/null 2>&1 && func_${name} || \ echo "func_${name} is not defined"
9 Debug with /bin/sh -x
sh -x option outputs running statement. Loaded shell script with "." is applied too.
$ cat debug.sh #!/bin/sh a=1 [ ${a} -eq 1 ] && echo ${a} $ sh -x debug.sh + a=1 + [ 1 -eq 1 ] + echo 1 1
Changing shebang to /bin/sh -x is the same. This is useful for shell script which will be started automatically.
#!/bin/sh -x