583

What would be the best way to check the exit status in an if statement in order to echo a specific output?

I'm thinking of it being:

if [ $? -eq 1 ] 
then
    echo "blah blah blah"
fi

The issue I am also having is that the exit statement is before the if statement simply because it has to have that exit code. Also, I know I'm doing something wrong since the exit would obviously exit the program.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
deadcell4
  • 6,085
  • 3
  • 14
  • 10
  • 4
    Plaese post your complete script (or at least a broader scope). Else this seems fine. – RedX Oct 31 '14 at 13:21
  • 9
    If you need to use the exit code from some particular program invocation in two different places, then you need to preserve it - something along the lines of `some_program; rc=$?; if [ ${rc} -eq 1 ] .... fi ; exit ${rc}` – twalberg Oct 31 '14 at 14:37

13 Answers13

537

Every command that runs has an exit status.

That check is looking at the exit status of the command that finished most recently before that line runs.

If you want your script to exit when that test returns true (the previous command failed) then you put exit 1 (or whatever) inside that if block after the echo.

That being said, if you are running the command and are wanting to test its output, using the following is often more straightforward.

if some_command; then
    echo command returned true
else
    echo command returned some error
fi

Or to turn that around use ! for negation

if ! some_command; then
    echo command returned some error
else
    echo command returned true
fi

Note though that neither of those cares what the error code is. If you know you only care about a specific error code then you need to check $? manually.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • 33
    @deadcell4 When one needs to terminate a shell script on a program failure, the following idiom is useful `a_command || return 1` – gboffi Oct 31 '14 at 13:32
  • 20
    @gboffi `return` only works in a function and a sourced script. You need `exit` for the other case (which does too much in a function and a sourced script). But yes, that's certainly a reasonable pattern if you don't need any specific cleanup or extra output. – Etan Reisner Oct 31 '14 at 13:33
  • 2
    I have to say that `dash`, the default non-interactive shell in many modern linux distributions, don't care of the distinction between `return` and `exit` inside of executed shell scripts. `dash` exits the script even if I use `return` in it. – gboffi Oct 31 '14 at 14:32
  • 3
    What is the logic behind the last two checks? It seems counter-intuitive that the condition `if ` passes if the exit code is 0. In any other language it would be the other way around – sjw Jun 04 '19 at 11:01
  • 8
    IMPORTANT NOTE: This won't work for pipes. `if ! some_command | some_other_command` will ignore the status of some_command. The two most command workarounds are to `set -o pipefail` (may change functionality in other parts of your program) or to move the `if` statement to `if [[ ${PIPESTATUS[0]} -ne 0 ]]` as a separate follow-up command (ugly, but functional). If you're using `set -e` then you'll also want to add `|| true` to the end of the pipe when using the second solution since removing the pipe from the control flow offered by `if` would otherwise cause it to immediately exit. – Alex Jansen Jul 04 '19 at 02:06
  • What about `grep`; which has 3 possible exit codes: 0 - something was selected, 1 - nothing; 2 - an error. – manu Jan 07 '21 at 06:15
  • 1
    Is there a way to fetch the error code in the "then" branch above, I know it returned non-zero but can I get exactly what flavor of non-zero? – chrisinmtown Feb 10 '21 at 21:57
  • "true" in this context means 0, as opposed to most other languages. It is hard to find information about the bash if statement since most guides actually describe the test command. – Marcus Ahlberg Nov 14 '22 at 11:40
  • _The test-commands list is executed, and if its return status is zero, the consequent-commands list is executed._ (from [Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs)). The logic behind this is simple. Shell commands return error codes, not data values. For readability, the `if` body (the consequent-commands list) is executed if the command succeeds, i.e., it returns no error, i.e. 0. – Alex Che Dec 30 '22 at 12:16
404

Note that exit codes != 0 are used to report errors. So, it's better to do:

retVal=$?
if [ $retVal -ne 0 ]; then
    echo "Error"
fi
exit $retVal

instead of

# will fail for error codes == 1
retVal=$?
if [ $retVal -eq 1 ]; then
    echo "Error"
fi
exit $retVal
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oo.oO
  • 12,464
  • 3
  • 23
  • 45
  • 3
    You must test on retVal, because $? after the assignment of retVal is not the return value from your command. – anr78 Apr 19 '18 at 08:05
  • 1
    Not really: http://mywiki.wooledge.org/BashFAQ/002 - however, I agree that edit improves the readability. – Oo.oO Apr 19 '18 at 08:32
  • 2
    Just found this post that explains it https://stackoverflow.com/questions/20157938/bash-exit-code-of-variable-assignment-to-command-substitution – anr78 Apr 19 '18 at 08:58
  • [`dnf check-update`](https://dnf.readthedocs.io/en/latest/command_ref.html#check-update-command-label) returns 0 (no updates), 100 (updates available) or 1 (error). – jww Jun 17 '19 at 22:37
  • 1
    @jww - well, that's not quite a good idea to go against convention (https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html). But, well, there is nothing to prevent that. If `dnf` developers have chosen this way, it's their choice. But still, their choice doesn't make the specification to be broken :) – Oo.oO Jun 18 '19 at 10:04
  • 1
    `# will fail for error codes > 1` but `$retVal -eq 1` checks for error code equal 1? – Timo Sep 25 '21 at 15:50
  • @Timo - true, true; This is a perfect sample of why shouldn't you trust comments - they become outdated at some point. Thanks for the remark. – Oo.oO Sep 25 '21 at 16:50
  • Why not simpilfy the if statement to `if [ $? -ne 0 ];` – carlin.scott Nov 17 '22 at 21:10
  • @carlin.scott - Follow your heart, follow your soul, I am not here to tell you how things must be done :) – Oo.oO Nov 18 '22 at 07:03
93

An alternative to an explicit if statement

Minimally:

test $? -eq 0 || echo "something bad happened"

Complete:

EXITCODE=$?
test $EXITCODE -eq 0 && echo "something good happened" || echo "something bad happened";
exit $EXITCODE
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Catskul
  • 17,916
  • 15
  • 84
  • 113
67

$? is a parameter like any other. You can save its value to use before ultimately calling exit.

exit_status=$?
if [ $exit_status -eq 1 ]; then
    echo "blah blah blah"
fi
exit $exit_status
chepner
  • 497,756
  • 71
  • 530
  • 681
50

For the record, if the script is run with set -e (or #!/bin/bash -e) and you therefore cannot check $? directly (since the script would terminate on any return code other than zero), but want to handle a specific code, @gboffis comment is great:

/some/command || error_code=$?
if [ "${error_code}" -eq 2 ]; then
   ...
Sled
  • 18,541
  • 27
  • 119
  • 168
dtk
  • 2,197
  • 2
  • 26
  • 19
  • 2
    Doesn't this break if `/some/command` is in the PATH? `error_code` is not set. – Justin Apr 09 '21 at 06:34
  • I don't see how that would interfere. You can try `/bin/mkdir` on an existing directory, that should return 1. Are you certain the command you tried did return an exit code other than 0? `/usr/bin/file` on an non-existant file for example prints an error but still returns 0 – dtk Apr 09 '21 at 07:41
  • @Justin, I agree. I think it's better to use `|| is_fail=true; if [ "$is_fail" = true ]; then . . . ` – Leponzo Jan 08 '22 at 18:23
  • `error_code` will only be set if `/some/command` returns with non-zero status. – Roland Weber Aug 03 '23 at 13:35
36

Just to add to the helpful and detailed answer:

If you have to check the exit code explicitly, it is better to use the arithmetic operator, (( ... )), this way:

run_some_command
(($? != 0)) && { printf '%s\n' "Command exited with non-zero"; exit 1; }

Or, use a case statement:

run_some_command; ec=$?  # grab the exit code into a variable so that it can
                         # be reused later, without the fear of being overwritten
case $ec in
    0) ;;
    1) printf '%s\n' "Command exited with non-zero"; exit 1;;
    *) do_something_else;;
esac

Related answer about error handling in Bash:

codeforester
  • 39,467
  • 16
  • 112
  • 140
  • 10
    Could you elaborate on why _it is better to use the arithmetic operator_? – quapka Aug 12 '20 at 21:35
  • 1
    This answer is actually incorrect. You can't use `$?` in a boolean check like that, regardless of whether you use `(( $? != 0 ))` or `[[ $? -ne 0 ]]` because it doesn't get parsed like normal vars do ([related description](https://ss64.com/bash/syntax-brackets.html#:~:text=However%20if%20you%20set%20a%20variable%20to%20the%20results%20of%20an%20arithmetic%20expression%20that%20will%20be%20set%20to%20the%20more%20logical%20True%20(1)%20/%20False%20(0))). – yuyu5 Dec 04 '21 at 04:03
  • 2
    Re why arithmetic is better: It's not, just personal preference. I like it more b/c it's shorter, i.e. `(( $retVal )) && echo 'ERROR'` instead of `(( $retVal != 0 ))` or `[[ $retVal -ne 0 ]]` but that doesn't necessarily mean it's _better_. In fact, the shortcut I like to use would be confusing to anyone who doesn't know Bash all that well. – yuyu5 Dec 04 '21 at 04:06
18

If you are writing a function – which is always preferred – you can propagate the error like this:

function()
{
    if <command>; then
        echo worked
    else
        return
    fi
}

Now, the caller can do things like function && next as expected! This is useful if you have a lot of things to do in the if block, etc. (otherwise there are one-liners for this). It can easily be tested using the false command.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nishant
  • 20,354
  • 18
  • 69
  • 101
13

Using Z shell (zsh) you can simply use:

if [[ $(false)? -eq 1 ]]; then echo "yes" ;fi

When using Bash and set -e is on, you can use:

false || exit_code=$?
if [[ ${exit_code} -ne 0 ]]; then echo ${exit_code}; fi
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aviel Yosef
  • 533
  • 5
  • 10
10

you can just add this if statement:

if [ $? -ne 0 ];
then
    echo 'The previous command was not executed successfully';
fi
ivanko337
  • 173
  • 2
  • 4
3

I'm missing the most simple solution, which is just to use the && operator:

command --with parameters && echo "Command completed succesfully"

-- assuming command returns 0 on success, and non-zero on failure as Bash expects (which is kind of the opposite of many programming languages!)

The || operator can be used for failure:

commamnd --with parameters || echo "Command failed"
Colin 't Hart
  • 7,372
  • 3
  • 28
  • 51
2

This might only be useful in a limited set of use-cases, I use this specifically when I need to capture the output from a command and write it to a log file if the exit code reports that something went wrong.

RESULT=$(my_command_that_might_fail)
if (exit $?) 
then
    echo "everything went fine."    
else
    echo "ERROR: $RESULT" >> my_logfile.txt
fi
JonC
  • 978
  • 2
  • 7
  • 28
1

Below test scripts below work for

  • simple bash test commands
  • multiple test commands
  • bash test commands with pipe included:
if [[ $(echo -en "abc\n def" |grep -e "^abc") && ! $(echo -en "abc\n def" |grep -e "^def") ]] ; then
  echo "pipe true"
else
  echo "pipe false"
fi
if [[ $(echo -en "abc\n def" |grep -e "^abc") && $(echo -en "abc\n def" |grep -e "^def") ]] ; then
  echo "pipe true"
else
  echo "pipe false"
fi

The output is:

pipe true
pipe false
Chen Tian
  • 79
  • 8
0

This is a solution handling set -euo pipefail gracefully:

# ...

some-command && true
SOME_COMMAND_EXIT_STATUS="$?"

# ... 

Example

#!/bin/bash
set -euo pipefail

# due to "set -e", using " ... && true" construct to avoid script from
# exiting immediately on expectable nonzero exit code.
# As per the Bash Reference Manual:
#
#   "[...] The shell does not exit if the command that fails is
#    [...] part of any command executed in a && or || list
#    [as long as it isn't the final command in this list]"
#
# see https://www.gnu.org/software/bash/manual/html_node/Lists.html
# see "-e" at https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html

some-command && true
SOME_COMMAND_EXIT_STATUS="$?"

if [[ "$SOME_COMMAND_EXIT_STATUS" == 4 ]]; then
  echo "some-command exit status was 4"
else
  echo "some-command exit status was unequal 4"
fi

Abdull
  • 26,371
  • 26
  • 130
  • 172