1. Overview

With Bash scripting, we have a powerful tool under our belt. Bash scripts are a great way to run multiple commands consecutively. When writing a script, we have to remind ourselves that if one command fails, all subsequent commands are still executed. In this article, we’ll learn how to prevent this by adding some safeguards. The commands used in the examples in this article have been tested in Bash. They should also work in other POSIX-compatible shells.

2. The Problem

First, let’s have a look at how bash scripts handle errors by default. Assume we have a simple script hello.sh that prints the words ‘hello’ and ‘world’:

#!/bin/bash
echo hello
echo world

Running it gives the desired outcome:

$ ./hello.sh
hello
world

Now let’s add a statement that’s guaranteed to fail:

#!/bin/bash
echo hello
cat non-existing-file
echo world

When executed, this script will print an error. However, execution doesn’t stop, and ‘world’ is still printed:

$ ./hello.sh
hello
cat: non-existing-file: No such file or directory
world

Also, we get a zero exit code for our script, indicating everything was ok:

$ echo $?
0

3. Exit on First Error

Let’s say we want to make our script terminate with a non-zero exit code on the first error that occurs. Therefore we have to change the default behavior of our shell at the beginning of our script using set:

#!/bin/bash
set -e
echo hello
cat non-existing-file
echo world

With set -e on the first line, we tell Bash to stop execution on the first error:

$ ./hello.sh
hello
cat: non-existing-file: No such file or directory

Also, the returned exit code is equal to the exit code of the command that failed:

$ echo $?
1

4. Using pipefail

Unfortunately, this solution doesn’t work for scripts containing piped statements. For example, let’s pipe the first two commands together, starting with the command that fails:

#!/bin/bash
set -e
cat non-existing-file | echo hello
echo world

Although we used set -e, the output is:

$ ./hello.sh
hello
cat: non-existing-file: No such file or directory
world

To handle this scenario, let’s add -o pipefail as an additional option to the set command on the first line:

#!/bin/bash
set -eo pipefail
cat non-existing-file | echo hello
echo world

Hereby we tell Bash that when some error occurs within a pipe, it should stop and return the exit code of the rightmost command that failed:

$ ./hello.sh
hello
cat: non-existing-file: No such file or directory

This produces the same non-zero exit code we’ve seen before:

$ echo $?
1

5. Conclusion

In this article, we have learned how to use set -e to make bash scripts terminate immediately when some error occurs. We also learned to add -o pipefail to make pipes behave in the same way. It’s good practice to always add set -eo pipefail as the first line in our scripts.