3 advices to write robust Bash scripts

To write robust Bash scripts, I recommend you to:

1. Activate the Bash options: errexit, nounset

Add these two options at the top of your Bash scripts:

#!/usr/bin/env bash
set -o errexit      
set -o nounset
  • errexit will stop your Bash script each time a command in your script returns an exit code different from “0”. It does not show any message when it quits, this is why it is important to show a traceback (check the second advice below “show a Bash traceback”).
  • nounset will stop your script and output an error if you attempt to use undefined variables. nounset is very important because it can avoid you to run dangerous commands like: rm –fr “/$undefinedvariable”

2. Show a Bash traceback

In some cases, the option “set -o errexit” doesn’t show any message when it stops your Bash scripts (e.g. var=$(your_failed_command) will exit silently). I recommend you to add the source code below at the top of your Bash scripts to make them show a traceback each time “errexit” stops your script:

#!/usr/bin/env bash

set -o errexit    # stop the script each time a command fails
set -o nounset    # stop if you attempt to use an undef variable

function bash_traceback() {
  local lasterr="$?"
  set +o xtrace
  local code="-1"
  local bash_command=${BASH_COMMAND}
  echo "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]} ('$bash_command' exited with status $lasterr)"
  if [ ${#FUNCNAME[@]} -gt 2 ]; then
    # Print out the stack trace described by $function_stack
    echo "Traceback of ${BASH_SOURCE[1]} (most recent call last):"
    for ((i=0; i < ${#FUNCNAME[@]} - 1; i++)); do
    local funcname="${FUNCNAME[$i]}"
    [ "$i" -eq "0" ] && funcname=$bash_command
    echo -e "  $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]}\t$funcname"
    done
  fi
  echo "Exiting with status ${code}"
  exit "${code}"
}

# provide an error handler whenever a command exits nonzero
trap 'bash_traceback' ERR

# propagate ERR trap handler functions, expansions and subshells
set -o errtrace

The traceback will help you to know which command in your Bash script failed with a return code different from “0”:

Error in ./test.sh:43 ('false' exited with status 1)
Traceback of ./test.sh (most recent call last):
  0: ./test.sh:43	false
  1: ./test.sh:49	main
Exiting with status -1

3. Check your Bash scripts with shellcheck and follow the recommendations of Advanced Bash-Scripting Guide.