315

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.

1
  • 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. Commented Dec 29, 2015 at 18:02

9 Answers 9

573

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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.
It seems reversed, but read it out and it makes sense: "do this command (successfully)" OR "print this error and exit"
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.
I would recommend echoing to STDERR: { (>&2 echo 'my_command failed') ; exit 1; }
when I use exit 1; it also closes the terminal I am on
|
177

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.

1 Comment

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, 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).
124

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.

5 Comments

You can run commands on exit using the trap bash built-in command.
This is the answer to the X-Y question.
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!
set -e is not at all reliable, consistent or predictable! To convince yourself of that, review the exercises section of BashFAQ #105, or the table comparing different shells' behaviors at in-ulm.de/~mascheck/various/set-e
A one liner: set -euo pipefail
87

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

4 Comments

This can just be replaced by if my_command, there is no need to use the test command here.
+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).
@BartSas If the command is long, it's better to put it on its own line. It makes the script more readable.
@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.
19

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; 
}

2 Comments

IIRC, you need semicolons after each of the lines inside {}
@Alex: not if they're on separate line.
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.)

4 Comments

This could be defined as a function, thus reuse it.
'if [ $? -ne 0 ] ; then ... fi' is a complicated way to write '||'
if ! javac *.java; then ... -- why bother with $? at all?
Charles -- because I am a mediocre bash scripter at best :)
17

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.

3 Comments

There's no reason for it to be exit $?; just exit uses $? as its default value.
@CharlesDuffy didn't know. Awesome, so it's even simpler!
@CharlesDuffy I applied your suggestion
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.

Comments

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)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.