43

I have some bash scripts I have setup that mostly use

#!/bin/bash

but I regularly come across some that look like

#!/bin/bash -e
#!/bin/bash -x
#!/bin/bash -ex

and so on.

Can someone explain the meaning and benefits of these shebang options and whether they apply to other shebangs?

4 Answers4

50

If a script /path/to/foo begins with #!/bin/bash, then executing /path/to/foo arg1 arg2 is equivalent to executing /bin/bash /path/too/foo arg1 arg2. If the shebang line is #!/bin/bash -ex, it is equivalent to executing /bin/bash -ex /path/too/foo arg1 arg2. This feature is managed by the kernel.

Note that you can portably have only one argument on the shebang line: some unices (such as Linux) only accept one argument, so that #!/bin/bash -e -x would lead to bash receiving the single five-character argument -e -x (a syntax error) rather than two arguments -e and -x.

For the Bourne shell sh and derived shells such as POSIX sh, bash, ksh, and zsh:

  • -e means that if any command fails (which it indicates by returning a nonzero status), the script will terminate immediately.
  • -x causes the shell to print an execution trace.

Other programs may understand these options but with different meanings.

25

They are options passed to bash see help set for more info, in this case:

-x  Print commands and their arguments as they are executed.
-e  Exit immediately if a command exits with a non-zero status.
cYrus
  • 22,335
2

I'd just like to mention an even better – as in more portable – alternative:

#!/usr/bin/env bash

The example above uses env to find the bash executable, which isn't always at /bin/bash. Ye olde #!/bin/bash scripts don't work on NixOS, for example.

If you use env as demonstrated above, you can't supply an argument such as -e to bash (as far as I know). But you can do this instead:

#!/usr/bin/env bash
set -e
1

Starting a script with #! /path/to/bash -option, or equivalently running bash -option /path/to/script, has the same effect as inserting set -option inside the script just after its #! line.

set -x is essentially just for debugging; you wouldn't want it left on normally.

The effect of set -e is a lot more complicated than the manual suggests. The more precise description is roughly:

After invoking set -e, if an untested command exits with a non-zero status, it will cause the shell to exit with that exit status.

I say more precise because it's still pretty vague: the list of situations where the command is considered tested is arcane and difficult to predict. The exact rules differ between shells and even change between versions of the same shell.

set -e does not cause Bash to exit in response to non-zero exit status of any commands:

  • between if/elif and then; or
  • between while/until and do; or
  • following !; or
  • followed by || or && or & (though for & the corresponding wait or fg may fail); or
  • followed by |, unless set -o pipefail is in effect; or
  • inside $( ... ) when used as part of a command (not bare assignments and/or redirections, which means that foo=$( bar ) will fail if bar fails, but local foo=$( bar ) will not); or
  • within a compound command where the above apply; or
  • inside a shell function invoked from anywhere the above apply.

The effects on compound commands and shell functions are recursive.

Because the shell itself exits with a non-zero status, the effect can propagate out of a subshell.

The ! exemption applies even if the inverted exit status is ignored.

Conversely, set -e triggers on every other non-zero status, regardless of whether it means "fail" or simply "false". For example ((x++)) will have a non-zero "fail" exit status when x is initially 0.

Because it's such a mess, there's a school of thought that says set -e should be avoided in favour of explicitly checking all commands.

Or if you still want to use set -e, remember to write ! in front of any ((arithmetic)) groups.