Linux: Shell script Tips

This article will describe shell script Tips.

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