This is part thirteen of a series on how to approach bash programming in a way that’s safer and more structured than your basic script.

See part 1 if you want to catch the series from the start.

Last time, we discussed techniques for working in strict mode. This time, let’s TDD a function to implement strict mode.

As a reminder, setting any of the strict mode settings involves calling set -o. In order to unset any of them, you use set +o instead.

Test Drive

This time, let’s start with the tests. We’ll implement our strict_mode function in lib/support.bash.

shpec/support_shpec.bash:

set -o nounset
shopt -s expand_aliases
alias it='(_shpec_failures=0; alias setup &>/dev/null && { setup; unalias setup; alias teardown &>/dev/null && trap teardown EXIT ;}; it'
alias ti='return "$_shpec_failures"); (( _shpec_failures += $?, _shpec_examples++ ))'
alias end_describe='end; unalias setup teardown 2>/dev/null'

support_lib=$(dirname "$(readlink -f "$BASH_SOURCE")")/../lib/support.bash

[...]

source "$support_lib"

describe strict_mode
  it "sets errexit"
    strict_mode on
    [[ $- == *e* ]]       # errexit shows up as "e" in $-
    assert equal 0 $?     # result code 0 means true
  ti
end_describe

This fails since we don’t yet have strict_mode. Let’s remedy that.

lib/support.bash:

[...]

strict_mode () {
  set -o errexit
}

This passes, so on to the next test:

it "unsets errexit"
  set -o errexit    # set it so we really test turning it off
  strict_mode off
  [[ $- == *e* ]]
  assert unequal 0 $?
ti

Run shpec and failure again. Good.

support.bash again:

strict_mode () {
  case $1 in
    on  ) set -o errexit;;
    off ) set +o errexit;;
  esac
}

Pass. The tests for pipefail and nounset are similar. I don’t bother testing the off setting for the other two.

Here’s the entirety of strict_mode testing in shpec/support_shpec.bash:

describe strict_mode
  it "sets errexit"
    strict_mode on
    [[ $- == *e* ]]
    assert equal 0 $?
  ti

  it "unsets errexit"
    set -o errexit
    strict_mode off
    [[ $- == *e* ]]
    assert unequal 0 $?
  ti

  it "sets nounset"
    set +o nounset    # unset it first because set at top of file
    strict_mode on
    set +o errexit    # so test won't exit if it fails
    [[ $- == *u* ]]
    assert equal 0 $?
  ti

  it "sets pipefail"
    strict_mode on
    set +o errexit
    [[ :$SHELLOPTS: == *:pipefail:* ]]
    assert equal 0 $?
  ti
end_describe

And finally, lib/support.bash:

strict_mode () {
  case $1 in
    on )
      set -o errexit
      set -o nounset
      set -o pipefail
      ;;
    off )
      set +o errexit
      set +o nounset
      set +o pipefail
      ;;
  esac
}

Continue with part 14 - updated outline