The bash shell is somewhat like the lingua-franca of the UNIX-based shell scripting world, as nothing else manages to displace it. The usefulness of a language is significant if it is omnipresent.

In this post I begin a series about programming in bash. Its earlier incarnation sh is less of a concern to me because bash can be found in almost in every place where sh is present.

The pitfalls in bash-programming for the initiate or non-frequent shell script programmer are numerous. However, they are easy to avoid by just sticking in to some rules.

Relaying execution

Occasionally you want to relay an execution of one program to another. Possible reasons: providing arguments, environment variable hacks, path, and so forth.

For example, let's create a mutex around another program using flock, a program to manage locks from shell scripts.

flock has the following syntax:

flock [options] file|directory command [arguments]

From this, it is apparent that we would need to relay arguments to the executed program.

Relaying arguments (always use "$@"!)

It is important to understand the difference between the following expressions:

  • $*
  • $@
  • "$*"
  • "$@"

Let's test this with a simple bash script. The script executes Python to see what list of strings was actually relayed as an argument list.

echo
python -c "import sys; print(sys.argv[1:])" $*
python -c "import sys; print(sys.argv[1:])" $@
python -c "import sys; print(sys.argv[1:])" "$*"
python -c "import sys; print(sys.argv[1:])" "$@"

Execution result:

$ bash test.sh foo bar 'hello world' \?

['foo', 'bar', 'hello', 'world', '1', '2', 'm']
['foo', 'bar', 'hello', 'world', '1', '2', 'm']
['foo bar hello world ?']
['foo', 'bar', 'hello world', '?']

From this we can see there's only one good option. Let's break it down to what happens:

  • $* - don't quote arguments, perform wildcard expansion and then relay
  • $@ - same
  • "$*" - relay all arguments as one argument with IFS separator concatenation (space if undefined), don't perform wildcard expansion
  • "$@" - relay arguments and quote them, don't expand, equivalent to "$1" "$2" "$3" ...

There is more information about this in bash's docs.

Avoiding a redundant shell process

Once we reach executing the wrapper program, the shell process has done its job. Therefore, it would be best to replace it with the wrapped program. This can be done using an exec prefix.

Full example

#!/bin/bash

mkdir -p ~/.locker || exit -1

exec flock ~/.locker/my-lock-file "$@"