All scripts should be run with "-eu". This prevents unbound variables from ever being used and will end the script on a failed command. If you want commands in the script to fail, just drop the "e" and run with "-u". At my last job we put "-eu" in literally all of our scripts to avoid this very problem.
I also do this. However, literally just hours ago, Greg Wooledge, author of The Bash Guide, posted the following to the GNU bug-bash mailing list.
> Errexit (a.k.a. set -e) is horrible, and you should not be using it in any new shell scripts you write. It exists solely for support of legacy scripts.
edit: I asked after this (for whatever reason, my email isn't yet showing in the archives; maybe it will appear later), and got an interesting response!
Do you have a strong reason to prefer "set" over shebang flags? I have a slight preference for shebang flags so I can deliberately override them from the command line (but it's not a hill I'd die on):
$ cat foo
#!/bin/bash -eu
cd /nowhere
echo 'still here'
$ ./foo
./foo: line 2: cd: nowhere: No such file or directory
$ bash -c ./foo # thinking about system(3)
./foo: line 2: cd: nowhere: No such file or directory
$ bash +e ./foo # override
./foo: line 2: cd: nowhere: No such file or directory
still here
$
EDIT: Google's shell style guide has "Executables must start with #!/bin/bash and a minimum number of flags. Use set to set shell options so that calling your script as bash <script_name> does not break its functionality."
Bash is not always /bin/bash. On some systems (BSDs) it is /usr/bin/bash.
So the portable sh'bang line for bash is:
#!/usr/bin/env bash
And there you can't add "-eu" anymore, because only one argument is possible in sh'bang lines. (/usr/bin/env would try to find an executable named "bash -eu")
So
#!/usr/bin/env bash
set -eu
is the more portable approach. However, for "/bin/sh" this should work portably:
> Bash is not always /bin/bash. On some systems (BSDs) it is /usr/bin/bash.
Nitpick: on every BSD system I've used, it's /usr/local/bin/bash. But I definitely agree that using /usr/bin/env bash is the preferred solution for when you want to use bash (although I don't think it's even installed by default on most BSD's); as an Arch user (where /usr/bin/python is symlinked to python3 rather than python2), I appreciate those who go out of their way to use proper, portable shebangs.
Disadvantage is that nobody can use an alternative shell to run your script as intended. (Alternative here could mean a less buggy version of whatever is the system shell.)
That doesn't solve the problem that parent incorrectly explained.
echo "$(false)"
swallows the exit code from $(false).
$ cat /tmp/test.sh
set -euvx
export SHELLOPTS
echo "$(false)"
echo "echo of string with failed subcommand does not kill script"
$(false)
echo "but consuming exit code does"
$ /tmp/test.sh
export SHELLOPTS
+ export SHELLOPTS
echo "$(false)"
false
++ false
+ echo ''
echo "echo of string with failed subcommand does not kill script"
+ echo 'echo of string with failed subcommand does not kill script'
echo of string with failed subcommand does not kill script
$(false)
false
++ false
I start out like that, but often I end up having to remove -u if I'm e.g. trying to check if $1 is populated. Is there a way to do that while still using -u?
"Abort command if variables are unset" is the -u flag, not -e. Both were mentioned long ago in this thread, but GP was talking about -e being painful for interactive shells. Imagine an interactive shell exiting as soon as a user's command failed!