write safe shell scripts set -euf set -o pipefail
et -euf -o pipefail
In dash,
set -o
doesn't exist, so use only set -euf
.
What do those do?
set -e
If a command fails,
set -e
will make the whole script exit, instead of just resuming on the next line. If you have commands that can fail without it being an issue, you can append || true
or || :
to suppress this behavior — for example set -e
followed by false || :
will not cause your script to terminate.
set -u
Treat unset variables as an error, and immediately exit.
set -f
Disable filename expansion (globbing) upon seeing
*
, ?
, etc..
If your script depends on globbing, you obviously shouldn't set this. Instead, you may find
shopt -s failglob
useful, which causes globs that don't get expanded to cause errors, rather than getting passed to the command with the *
intact.
set -o pipefail
set -o pipefail
causes a pipeline (for example, curl -s http://sipb.mit.edu/ | grep foo
) to produce a failure return code if any command errors. Normally, pipelines only return a failure if the last command errors. In combination with set -e
, this will make your script exit if any command in a pipeline errors.Quote liberally
Whenever you pass a variable to a command, you should probably quote it. Otherwise, the shell will perform word-splitting and globbing, which is likely not what you want.
For example, consider the following:
alex@kronborg tmp [15:23] $ dir="foo bar"
alex@kronborg tmp [15:23] $ ls $dir
ls: cannot access foo: No such file or directory
ls: cannot access bar: No such file or directory
alex@kronborg tmp [15:23] $ cd "$dir"
alex@kronborg foo bar [15:25] $ file=*.txt
alex@kronborg foo bar [15:26] $ echo $file
bar.txt foo.txt
alex@kronborg foo bar [15:26] $ echo "$file"
*.txt
Depending on what you are doing in your script, it is likely that the word-splitting and globbing shown above are not what you expected to have happen. By using
"$foo"
to access the contents of the foo
variable instead of just $foo
, this problem does not arise.
When writing a wrapper script, you may wish pass along all the arguments your script received. Do that with:
wrapped-command "$@"
See "Special Parameters" in the bash manual for details on the distinction between
$*
, $@
, and "$@"
— the first and second are rarely what you want in a safe shell script.
Comments
Post a Comment