299

I am a noob in shell-scripting. I want to print a message and exit my script if a command fails. I've tried:

my_command && (echo 'my_command failed; exit)

but it does not work. It keeps executing the instructions following this line in the script. I'm using Ubuntu and bash.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
user459246
  • 3,139
  • 2
  • 15
  • 7
  • 6
    Did you intend the unclosed quote to be a syntax error that would cause fail/exit? if not, you should close the quote in your example. – hobs Dec 29 '15 at 18:02

9 Answers9

543

Try:

my_command || { echo 'my_command failed' ; exit 1; }

Four changes:

  • Change && to ||
  • Use { } in place of ( )
  • Introduce ; after exit and
  • spaces after { and before }

Since you want to print the message and exit only when the command fails ( exits with non-zero value) you need a || not an &&.

cmd1 && cmd2

will run cmd2 when cmd1 succeeds(exit value 0). Where as

cmd1 || cmd2

will run cmd2 when cmd1 fails(exit value non-zero).

Using ( ) makes the command inside them run in a sub-shell and calling a exit from there causes you to exit the sub-shell and not your original shell, hence execution continues in your original shell.

To overcome this use { }

The last two changes are required by bash.

codaddict
  • 445,704
  • 82
  • 492
  • 529
  • 7
    It does appear to be "reversed". If a function "succeeds" it returns 0 and if it "fails" it returns non-zero therefore && might be expected to evaluate when the first half returned non-zero. That does not mean the answer above is incorrect - no it is correct. && and || in scripts work based on success not on the return value. – CashCow Jul 20 '12 at 10:43
  • 12
    It seems reversed, but read it out and it makes sense: "do this command (successfully)" OR "print this error and exit" – simpleuser Mar 14 '14 at 22:18
  • 3
    The logic behind it is that the language uses short-circuit evaluation (SCE). With SCE, f the expression is of the form "p OR q", and p is evaluated to be true, then there is no reason to even look at q. If the expression is of the form "p AND q", and p is evaluated to be false, there is no reason to look at q. The reason for this is two-fold: 1) its faster, for obvious reasons and 2) it avoids certain kinds of errors (for example: "if x!=0 AND 10/x > 5" will crash if there is no SCE). The ability to use it in the command line like this is a happy side-effect. – user2635263 Jul 11 '15 at 00:01
  • 5
    I would recommend echoing to STDERR: `{ (>&2 echo 'my_command failed') ; exit 1; }` – rynop Feb 12 '19 at 16:48
  • when I use `exit 1`; it also closes the terminal I am on – alper Nov 29 '20 at 22:11
  • @alper see the answer I've added below – noonex Dec 31 '20 at 10:34
170

The other answers have covered the direct question well, but you may also be interested in using set -e. With that, any command that fails (outside of specific contexts like if tests) will cause the script to abort. For certain scripts, it's very useful.

Daenyth
  • 35,856
  • 13
  • 85
  • 124
  • 6
    Unfortunately, it's also very dangerous -- `set -e` has a long and arcane set of rules about when it works and when it doesn't. (If anyone runs `if yourfunction; then ...`, then `set -e` will never fire inside `yourfunction` or anything it calls, because that code is considered "checked"). See [the exercises section of BashFAQ #105](http://mywiki.wooledge.org/BashFAQ/105#Exercises), discussing this and other pitfalls (some of which only apply to specific shell versions, so you need to be sure to test against every release that might be used to run your script). – Charles Duffy Apr 11 '19 at 01:52
114

If you want that behavior for all commands in your script, just add

set -e 
set -o pipefail

at the beginning of the script. This pair of options tell the bash interpreter to exit whenever a command returns with a non-zero exit code. (For more details about why pipefail is needed, see http://petereisentraut.blogspot.com/2010/11/pipefail.html)

This does not allow you to print an exit message, though.

Leponzo
  • 624
  • 1
  • 8
  • 20
damienfrancois
  • 52,978
  • 9
  • 96
  • 110
  • 13
    You can run commands on exit using the `trap` bash built-in command. – Gavin Smith Apr 12 '14 at 18:07
  • 1
    This is the answer to the X-Y question. – jwg Nov 06 '15 at 16:14
  • 8
    Might be interesting to explain what commands do independently rather than the pair. Especially since `set -o pipefail` might not be the desired behavior. Still thanks for pointing it out! – Antoine Pinsard Jan 26 '17 at 10:49
  • 6
    `set -e` is not at all reliable, consistent or predictable! To convince yourself of that, review the exercises section of [BashFAQ #105](http://mywiki.wooledge.org/BashFAQ/105#Exercises), or the table comparing different shells' behaviors at https://www.in-ulm.de/~mascheck/various/set-e/ – Charles Duffy Apr 09 '19 at 23:58
82

Note also, each command's exit status is stored in the shell variable $?, which you can check immediately after running the command. A non-zero status indicates failure:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Alex Howansky
  • 50,515
  • 8
  • 78
  • 98
  • 12
    This can just be replaced by if my_command, there is no need to use the test command here. – Bart Sas Sep 29 '10 at 14:53
  • 7
    +1 because I think you oughtn't be punished for listing this alternative - it should be on the table - though it's kinda ugly and in my experience too easy to run another command in between without noticing the nature of the test (maybe I'm just stupid). – Tony Delroy Sep 30 '10 at 08:34
  • 1
    @BartSas If the command is long, it's better to put it on its own line. It makes the script more readable. – Michael Aug 13 '12 at 19:45
  • @Michael, not if it creates dependencies across lines, where you need to know what happened on line A before you can understand line B (or, worse, need to know what's going to happen in line B before you know it's safe to add something new after the end of line A). I've seen too many times someone added a `echo "Just finished step A"`, not knowing that that `echo` overwrote the `$?` that was going to be checked later. – Charles Duffy Apr 11 '19 at 01:53
17

I've hacked up the following idiom:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Precede each command with an informative echo, and follow each command with that same
if [ $? -ne 0 ];... line. (Of course, you can edit that error message if you want to.)

Grant Birchmeier
  • 17,809
  • 11
  • 63
  • 98
16

Provided my_command is canonically designed, ie returns 0 when succeeds, then && is exactly the opposite of what you want. You want ||.

Also note that ( does not seem right to me in bash, but I cannot try from where I am. Tell me.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
  • 76,634
  • 23
  • 210
  • 236
15

You can also use, if you want to preserve exit error status, and have a readable file with one command per line:

my_command1 || exit
my_command2 || exit

This, however will not print any additional error message. But in some cases, the error will be printed by the failed command anyway.

alexpirine
  • 3,023
  • 1
  • 26
  • 41
6

The trap shell builtin allows catching signals, and other useful conditions, including failed command execution (i.e., a non-zero return status). So if you don't want to explicitly test return status of every single command you can say trap "your shell code" ERR and the shell code will be executed any time a command returns a non-zero status. For example:

trap "echo script failed; exit 1" ERR

Note that as with other cases of catching failed commands, pipelines need special treatment; the above won't catch false | true.

kozel
  • 197
  • 1
  • 4
2

Using exit directly may be tricky as the script may be sourced from other places (e.g. from terminal). I prefer instead using subshell with set -e (plus errors should go into cerr, not cout) :

set -e
ERRCODE=0
my_command || ERRCODE=$?
test $ERRCODE == 0 ||
    (>&2 echo "My command failed ($ERRCODE)"; exit $ERRCODE)
noonex
  • 1,975
  • 1
  • 16
  • 18