93

How can I write a shell script that exits, if one part of it fails? For example, if the following code snippet fails, then the script should exit.

n=0
until [ $n -ge 5 ]
do
  gksu *command* && break
  n=$[$n+1]
  sleep 3
0

6 Answers 6

152

One approach would be to add set -e to the beginning of your script. That means (from help set):

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

So if any of your commands fail, the script will exit.

Alternatively, you can add explicit exit statements at the possible failure points:

command || exit 1
7
  • 10
    I (and the bash wiki) would rather people put some thought in proper error handling rather than using the (broken by design IMO) set -e feature. It doesn't really apply here though. The OP wants to exit the script after 5 failing attempts to run the command. Commented Sep 12, 2016 at 9:50
  • 1
    @StéphaneChazelas I won't argue with you about whether it's broken, I'm sure you're right. However, the OP asked "How can I write a shell script that exits, if one part of it fails?", what makes you think it's about exiting after 5 failures?
    – terdon
    Commented Sep 12, 2016 at 10:36
  • because I can't think of any other way the question can be interpreted. Commented Sep 12, 2016 at 10:40
  • 1
    @StéphaneChazelas you may well be right. I interpreted it literally: how can the whole script exit if any part of it fails. And set -e is the only way I know to do that.
    – terdon
    Commented Sep 12, 2016 at 10:41
  • In that script snipet, the commands that could trigger set -e is sleep (break being a special builtin would cause the script to exit on failure in most shells, commands in if or to the left of && are not affected by set -e, n=... could fail if n is read-only, but then that would exit the script without set -e as well), so that interpretation sounds quite unlikely. I agree the question is poorly worded. Commented Sep 12, 2016 at 10:46
26

You can exit a script at any place using the keyword exit. You can also specify an exit code in order to indicate to other programs that or how your script failed, e.g. exit 1 or exit 2 etc. (By convention, exit code 0 is for success and anything greater than 0 signifies failure; however, also by convention, exit codes above 127 are reserved for abnormal termination (e.g. by a signal)).

The generic construction to exit on failure is

if [ failure condition ]; then
    exit n
fi

with suitable failure condition and n. But in specific scenarios you may proceed differently. Now for your case I interpret your question that if any of the five invocations of gksu fail, then you mean to exit. One way is to use a function like this

function try_command {
    for i in 1 2 3 4 5 ; do
        if gksu command ; then
            return 0
        fi
    fi
    exit 1
}

and then, invoke the loop by try_command.

There are (more) advanced or sophisticated ways of how to address your question. However, the solution above is more accessible to beginners than, say, Stephane's solution.

15
attempt=0
until gksu command; do
  attempt=$((attempt + 1))
  if [ "$attempt" -gt 5 ]; then
    exit 1
  fi
done

exit exits the script unless it's called in a subshell. If that part of the script is in a subshell, for instance because it's within (...) or $(...) or part of a pipe-line, then it will only exit that subshell.

In that case, if you want the script to exit in addition to the subshell, then you'll need to call exit upon that subshell exiting.

For instance, here with 2 nested levels of subshells:

(
  life=hard
  output=$(
    echo blah
    [ "$life" = easy ] || exit 1 # exit subshell
    echo blih not run
  ) || exit # if the subshell exits with a non-zero exit status,
            # exit as well with the same exit status

  echo not run either
) || exit # if the subshell exits with a non-zero exit status,
          # exit as well with the same exit status

It can become trickier if the subshell is part of a pipeline. bash has a special $PIPESTATUS array, similar to zsh's $pipestatus one that can help you here:

{
   echo foo
   exit 1
   echo bar
} | wc -c
subshell_ret=${PIPESTATUS[0]}
if [ "$subshell_ret" -ne 0 ]; then
  exit "$subshell_ret"
fi
5

Trap will perform an action upon receiving a signal.

trap "echo EXIT;  exit" 0
trap "echo HUP;   exit" 1
trap "echo CTL-C; exit" 2
trap "echo QUIT;  exit" 3
trap "echo ERR;   exit" ERR
n=0
until [ $n -ge 5 ]
do
  n=$[$n+1]
  echo $n
  sleep 3
done

Run this and let it exit normally. It traps on signal 0.

EXIT

Run it again and interrupt with ^C. It traps on both signal 2 and signal 0.

CTL-C
EXIT

A non-zero exit status will trap on ERR

ERR
EXIT
0
pass_to_functio()
{
  echo "command exited with status $1"
}

(
 exec <Any Command >  &
 child_pid=$!
 wait $child_pid
 pass_to_function $?
)&
0

I prefer doing it this way:

  • explicit exit at important commands
  • common function for notification about failure, to not miss it
function exit_failed() {
    echo ""
    echo "  ... batch failed !!!"
    echo ""

    exit 1
}

7z a $FN . || exit_failed
<call-something-else> || exit_failed

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .