Exit Status (and Command Grouping)
Scripts give concise feedback about what they did by providing an integer status when they finish ("exit"). The exit status of a group of commands depends on the way the commands are grouped.
From the source: The GNU bash manual, Exit Status, Lists, and Command Grouping.
Exit status basics
- An exit status is between 0 and 255.
- 0 means OK (the command succeeded).
- 0 < exit status <= 125 are error codes.
- > 125 are reserved for the shell (builtins etc.).
What was the exit status? ( $?)
When a command exits, the exit status is not written anywhere. However, the exit status is available as $? (only the most recent exit status is available):
ls ~/.emacs echo $? # output 0 if the file existed, 2 otherwise
The exit status of a script
Use the exit [n] command to terminate a script:
if test "$#" -ne 3; then echo "Usage: $0 a b c" &>2 exit 1 fi
- The argument of the exit command defaults to $?, that is, the exit code of the last command.
- If execution reaches the end of the script file without encountering an exit command, the script exits with the exit status of the last command.
Negating the exit code of a command
Prefix a command by ! to negate it's exit status - a command that succeeds (exit status 0) is negated into failure (exit status 1):
! echo "abc" # exit status 0 becomes exit status 1
A command that fails (exit status 2) is negated into success (exit status 0), assuming there is no file called abc:
! ls abc # exit status 2 becomes exit status 0
Since all non-zero exit codes are mapped to 0, ! is not a one-to-one function, and once negated, there is no way to deduce the original exit status.
Executing a command if another command fails ("OR")
To execute a command only if another command fails, combine them using ||:
# if emacs is not available, use vi ;-) emacs || vi # if the command fails, output it's exit status command || echo "$?"
Execute a command only if another command succeeds ("AND")
A command might only make sense if another command succeeds. Combining them using && provides this behaviour:
# write ~/.emacs to stdout if it exists ls ~/.emacs && cat ~/.emacs # publish cv.pdf if it does not contain any more TODOs grep -vq TODO cv.pdf && publish cv.pdf
Exit status as the condition in an if command
When commands are used as conditions in if-commands, it is the exit status that determines whether the condition evaluates to true or false.
# Outputs KO if ls doesNotExist 2> /dev/null; then echo OK; else echo KO; fi
Grouping commands
Sequential execution
Commands separated by ; and/or newline are executed sequentially, and the exit status is the exit status of the last command:
command1 ; command2
command1[;] command2[;]
The second command is executed regardless of the exit status of the first one. Furthermore, when command2 runs, the exit status of command1 is lost forever, since $? now contains the exit status of command2. Use set -o errexit to be forced to handle all errors.
Executing commands as one in the current shell
Multiple commands can be grouped into one logical command by surrounding them by {}-s. The semi-colon at the end is mandatory, and the whitespace is mandatory:
{ command1; command2; }
This is useful to disambiguate complex conditions (using also the AND and OR operators previously described):
if { command1 && command2; } || command3; then # ... fi
Grouping commands like this makes it easy to perform Redirections on all of them at once:
{ command1; command2; } &> /dev/null
See also: The GNU bash manual: Command Grouping.
Executing commands in a subshell
To localise the side effects of a group of commands, surround them by ()-s. The whitespace and the last semi-colon are all optional:
( command1; command2[;] )
This works just like Executing commands as one in the current shell, except that side effects such as changing the current directory or the values of variables are local to the subshell.
See also: Quoting Arguments: Command Substitution and The GNU bash manual: Command Grouping.