57

I have a bash script that I use to execute multiple commands in sequence and I need to return non-zero exit code if at least one command in the sequence returns non-zero exit code. I know there is a wait command for that but I'm not sure I understand how to use it.

UPD The script looks like this:

#!/bin/bash
command1
command2
command3

All the commands run in foreground. All the commands need to run regardless of which exit status the previous command returned (so it must not behave as "exit on first error"). Basically I need to gather all the exit statuses and return global exit status accordingly.

bakoyaro
  • 2,550
  • 3
  • 36
  • 63
Andrii Yurchuk
  • 3,090
  • 6
  • 29
  • 40
  • maybe this http://stackoverflow.com/questions/3474526/stop-on-first-error is what you want? – Marian Theisen Apr 18 '13 at 10:50
  • 2
    @Marian Theisen No, the script must not stop on first error. All the commands in a script need to be run regardless of what status the previous command returned. – Andrii Yurchuk Apr 18 '13 at 10:59

4 Answers4

63

Just do it:

EXIT_STATUS=0
command1 || EXIT_STATUS=$?
command2 || EXIT_STATUS=$?
command3 || EXIT_STATUS=$?
exit $EXIT_STATUS

Not sure which of the statuses it should return if several of commands have failed.

kan
  • 28,279
  • 7
  • 71
  • 101
  • 4
    If you want to halt after the first error, you can use "set -e" – mcoolive Aug 07 '14 at 08:41
  • can someone briefly explain what is happening here? my understanding is "command passes, i.e. truthy" || "the act of assigning $? to EXIT_STATUS is truthy" so the command overall is always truthy and fine – yo conway Jun 20 '22 at 19:18
  • @yoconway yeah, that's the purpose. All of the lines are truthy and therefore the next line is also executed. (Only) if one of the commands fail, EXIT_STATUS is written, which can then be given as the exit status at the end. – jtunhag Apr 04 '23 at 15:04
  • Geniusly simple. Obviously, it limits the final exit status to the last non-zero exit status, but that's good enough for me, and most likely good enough for most. Thanks! – Joshua Pinter Aug 23 '23 at 13:21
11

If by sequence you mean pipe then you need to set pipefail in your script like set -o pipefail. From man bash:

The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully. If the reserved word ! precedes a pipeline, the exit status of that pipeline is the logical negation of the exit status as described above. The shell waits for all commands in the pipeline to terminate before returning a value.

If you just mean sequential commands then just check the exit status of each command and set a flag if the exit status is none zero. Have your script return the value of the flag like:

#!/bin/bash

EXIT=0
grep -q A <<< 'ABC' || EXIT=$?  # Will exit with 0
grep -q a <<< 'ABC' || EXIT=$?  # Will exit with 1
grep -q A <<< 'ABC' || EXIT=$?  # Will exit with 0
echo $EXIT                      # Will print 1
exit $EXIT                      # Exit status of script will be 1 

This uses the logical operator OR || to only set EXIT if the command fails. If multiple commands fail the exit status from the last failed command will be return by the script.

If these commands are not running in the background then wait isn't relevant here.

Chris Seymour
  • 83,387
  • 30
  • 160
  • 202
3

If you wish to know which command failed, but not neccessarily its return code you could use:

#!/bin/bash

rc=0;
counter=0;

command1 || let "rc += 1 << $counter"; let counter+=1;
command2 || let "rc += 1 << $counter"; let counter+=1;
command3 || let "rc += 1 << $counter"; let counter+=1;

exit $rc

This uses bit shifting in bash in order to set the bit corresponding to which command failed.

Hence if the first command failed you'll get an return code of 1 (=2^0), if the third failed you would get a return code of 8 (=2^3), and if both the first and the third command failed you would get 9 as the return code.

imp25
  • 2,327
  • 16
  • 23
0

If you wish to know which command failed:

#!/bin/bash
EXITCODE_RESULT=0
command1
EXIT_CODE_1=$?
command2
EXIT_CODE_2=$?
command3
EXIT_CODE_3=$?

for i in ${!EXIT_CODE_*}
do
    # check if the values of the EXIT_CODE vars contain 1
    EXITCODE_RESULT=$(($EXITCODE_RESULT || ${!i}))
    if [ ${!i} -ne 0 ]
    then
        var_fail+="'$i' "
        
    else
        var_succ+="'$i' "
    fi
done

In $var_fail you get a list of the failed EXIT_CODE vars and in $var_succ a list of the successful ones

alixander
  • 426
  • 1
  • 7
  • 18