Categories
Bash

safer Bash scripts

TL;DR

You should write the following at the beginning of every script:

set -euo pipefail
shopt -s lastpipe

Long version

Bash is a common used script language, if it comes to write some shell scripts for Unix. To make your scripts safer for common mistakes you should set some options of Bash in your script to prevent unexpected behavior and to enable some stricter rules.

set -e

If you set this option, Bash will immediately stop execution of your script if an error occurs.

The script:

#!/bin/bash

unknowncommand
echo "my second line"

will output with an return code of 0:

./test.sh: line 3: unknowncommand: command not found
my second output

If you add set -e at the beginning:

#!/bin/bash

set -e

unknowncommand
echo "my second line"

you will get the following output with an return code of 127.

./test.sh: line 5: unknowncommand: command not found

So you can trust that if a command like mkdir or cd for example will fail, your script will stop.

If you want to allow a command to fail, you have to add || true, for example:

#!/bin/bash

set -e

unknowncommand || true
echo "my second line"

set -u

This option will prevent you from using undefined variables.

The script:

#!/bin/bash

echo "Hello $NAME"

will output:

Hello

Bash handle undefined variables as empty variables per default. If you add set -u at the beginning of your script:

#!/bin/bash

set -u

echo "Hello $NAME"

you will get the following output:

./test.sh: line 5: NAME: unbound variable

If you are not sure, if the variable exists, you can use the syntax for default values:

#!/bin/bash

set -u

echo "Hello ${NAME:-World}"

set -o pipefail

If you are using pipes, only the last command will define the result of the whole pipe. So if you run this script:

#!/bin/bash

set -eu

unknowncommand | echo "other command"
echo "after command"

you will get the following output:

./test.sh: line 5: unknowncommand: command not found
other command
after command

If you add set -o pipefail at the beginning of your script:

#!/bin/bash

set -euo pipefail

unknowncommand | echo "other command"
echo "after command"

the script will stop after the failed pipe and you get the output:

./test.sh: line 5: unknowncommand: command not found

shopt -s lastpipe

If you run a script in a pipe and want to set variables, this won’t work, because your command is run in a sub shell and then you cannot change variables.

The following script:

#!/bin/bash

set -euo pipefail

VAR=Hello
echo "some" | { while read -r file ; do VAR=World; done }
echo $VAR

would output:

Hello

If you add shopt -s lastpipe at the beginning of your script:

#!/bin/bash

set -euo pipefail
shopt -s lastpipe

VAR=Hello
echo "some" | { while read -r file ; do VAR=World; done }
echo $VAR

the script would output:

World