Writing Robust Bash Shell Scripts
davidpashley.com
davidpashley.com
Yes, the overhead for anything quick and dirty is higher, but if you're writing libraries, you only have to write them once.
The only real benefit to writing something complex entirely in a shell script is if you can't control the environment it will run in, and you need it to run everywhere, and you can't make users install an interpreter, which are increasingly rare requirements, and even then you have to write it in `sh` anyway.
set -e # same as set -o errexit
and I like using list constructs (&& ||) in lieu of simple `if` test blocks but it's really important to remember that list constructs aren't exactly the same. This always catches me out: if [ -e "missingfile" ]
then
echo "you'll never see this"
fi
(above runs fine with set -e) [ -e "missingfile" ] && echo "you'll never see this"
(while that one correctly fails and thus `set -e` fails the script)I generally stick an `|| true` on the end of those list constructs.
fwiw.
if [ "$filename" = "foo" ];
Why not if [[ $filename == "foo" ]];
?EDIT: aware of the fact that [[ ]] is a bash extension, but the actual title of the post is "Writing Robust Bash Shell Scripts", implying that it should be using the best stuff for BASH.
$ cat foo.sh
#!/bin/bash
echo $BASH_VERSION
unset filename
if [[ $filename == "foo" ]];
$ ./foo.sh
4.1.2(1)-release
./foo.sh: line 5: syntax error: unexpected end of file
If $filename evaluates to an empty string, then the last line of code becomes if [[ == "foo" ]];
Similarly, if $filename evaluates to a b c, then the last line of code becomes if [[ a b c == "foo" ]];
The bash interpreter has problems with both of these.As a counterexample:
(echo $BASH_VERSION;
foo=foo; [[ $foo = foo ]] && echo $foo;
foo="1 2 3"; [[ $foo = "1 2 3" ]] && echo $foo;
foo=''; echo ${foo?"No foo for you"};
unset foo; echo ${foo?"No foo for you"})
4.2.10(1)-release
foo
1 2 3
bash: foo: No foo for you
That last one is one of the few ways you can tell whether a variable is unset or merely set to an empty string.lrn2bash.
if [[ $filename == foo ]]; set -u
Another great thing I could have read in the effin manual.He could have added a little reminder about the ${VAR:-default} syntax. [[ http://www.ooblick.com/text/sh/ search for Quasi-variable constructs ]]
Great read, thanks.
${parameter:-word}
Use Default Values. If parameter is unset or null, the expan-
sion of word is substituted. Otherwise, the value of parameter
is substituted. Ives ~ $ FN='"a b"'
Ives ~ $ if [ "$FN" = '"a b"' ]; then echo "Does not matter"; fi
Does not matter
Still works fine, so clearly something more than simple variable expansion is going on here. It seems like the end-of-literal-string matching is performed before variable expansion.Most of the people reading this article, IME, are not familiar with /bin/sh, or its differences from /{bin,usr/bin,usr/local/bin}/bash, much less know how or why it is important to write robust AND portable shell scripts.
edit: changed