2

Let's say I code something like:

if [ ${?} -ne 0 ]; then 
    exit ${?}

Would this work properly? Is this correct "syntax-wise"?

  • See [Why is testing "$?" to see if a command succeeded or not, an anti-pattern?](https://stackoverflow.com/q/36313216/4154375). – pjh Mar 11 '20 at 13:51

3 Answers3

5

The [ command in your if statement will set $? after it checks. You'll need to save the original exit status before testing.

some_command
es=$?
if [ "$es" -ne 0 ]; then
    exit "$es"
fi
chepner
  • 497,756
  • 71
  • 530
  • 681
3

The syntax is correct, but $? is reset by the [ ... ] command in the if statement. By definition, if the if block is entered then the [ ... ] test must have been successful, and $? is guaranteed to be 0.

You'll need to save it in a variable.

result=$?
if ((result != 0)); then 
    exit "$result"
fi

Alternately, it's more idiomatic to test the result of a command directly rather than testing $?. If you do so then you don't have the problem of $? changing.

if command; then
    echo success
else
    exit   # `exit` is equivalent to `exit $?`
fi

If you don't care about success then you can use ||:

command || exit
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • Thank you! This as well as @chepner answer cleared my doubts completely. – Hostile_Object Mar 11 '20 at 18:22
  • 1
    `if ! command; then exit "$?"; fi` (or `if ! command; then exit; fi`) will exit with status 0 if `command` returns (any) non-zero status. `$?` gets the value of the negated status. See [Check exit code of a program called in a while loop](https://stackoverflow.com/q/35411972/4154375) for an explanation of the problem, and some solutions. – pjh Mar 11 '20 at 20:30
1

If you want to exit on error and preserve the EXIT code of the command, you can enable the errexit option:

  • Either with: set -e
  • Either with: set -o errexit

See: help set | grep -F -- -e

-e Exit immediately if a command exits with a non-zero status.

errexit same as -e

Alternatively you can trap the ERR signal and use this to exit with the return code of the error.

This will save you from dealing with the -e option consequences.

#!/usr/bin/env bash

err_handler() {
  set -- $?
  printf 'The error handler caught code #%d\n' "$1" >&2
  exit "$1"
}

trap 'err_handler' ERR

create_error() {
  [ $# -ne 1 ] && return 0
  printf 'Create error #%d\n' "$1"
  return "$1"
}

create_error "$@"

Testing with different errors:

for e in 1 0 2; do ./a.sh "$e" ;echo $?; done
Create error #1
The error handler caught code #1
1
Create error #0
0
Create error #2
The error handler caught code #2
2
Léa Gris
  • 17,497
  • 4
  • 32
  • 41
  • 1
    There are limitations and difficulties with using `set -o errexit` (`set -e`). I'm happy to deal with them, but many people are not. See [BashFAQ/105 (Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?)](https://mywiki.wooledge.org/BashFAQ/105) for good, balanced, information. – pjh Mar 11 '20 at 14:18
  • @pjh I added an alternate solution with `trap`. – Léa Gris Mar 11 '20 at 14:36
  • 1
    You need to enable `errtrace` to make the `ERR` trap trigger on everything that would trigger with `errexit`. See [What does `set -o errtrace` do in a shell script?](https://stackoverflow.com/q/25378845/4154375). The `ERR` trap has the advantage that the handler can print helpful information about the context of the error. (With `errexit` programs sometimes stop mysteriously for no obvious reason.) Apart from that, the `ERR` trap has all the same limitations and difficulties as `errexit`. Extreme caution is required. – pjh Mar 11 '20 at 19:53