Match-Resumption in case…esac of Bourne Again Shell

This article describes the impact of using the lesser discussed alternative code block terminators ;;& and ;& which control the „match-resuming“ behavior of Bash’s caseesac.

The following example shows a quite common use of Bash’s caseesac:

#!/bin/bash
# A simple option parser, understanding two "flag" type options.

for argument in "$@" ; do
  case "$argument" in
    --option-1)
      echo "Doing something specific for option --option-1 ..."
    ;;
    --option-2)
      echo "Doing something specific for option --option-2 ..."
    ;;
    *)
      echo "Saw any other argument besides the known options: \"$argument\"."
    ;;
  esac
done

In each of the matching blocks within the case statement, the character sequence ;; causes the program flow to terminate the block and to leave the case statement:

    --option-1)
      echo "Doing something specific for option --option-1 ..."
    ;;

In natural words: „If the value of shell variable argument is exactly --option-1 then execute the echo command and exit the case statement.“

For the next example, the option behavior shown above will be extended as follows:

  • If a parameter value starts with --option-* then do something that all such options have in common, and
  • match and perform specific actions for --option-1 and --option-2.

This can be accomplished by using ;;& instead of ;; as the terminator of the match code block, which will cause the code block to not exit case, but instead resume pattern matching with the remaining patterns:

    --option-*)
      echo "Doing something that all --option-* options have in common (saw \"$argument\") ..."
    ;;& # resume matching attempts with all patterns below
    --option-1)
      echo "Doing something specific for --option-1 ..."
    ;;
    --option-2)
      echo "Doing something specific for --option-2 ..."
    ;;

In natural words:

  • If the value of argument matches --option-* then execute the first echo, then proceed to match argument against the following patterns.
  • If the value of argument matches --option-1 then execute the second echo, then exit case.
  • If the value of argument matches --option-2 then execute the third echo, then exit case.

The option behavior will again be extended for the next example:

  • If a parameter value starts with --option-* then do something that all such options have in common,
  • match and perform specific actions for --option-1 and --option-2,
  • match and perform specific actions for --feature-1, and
  • if --option-1 was specified, the specific actions for option --feature-1 should be performed after the specific actions for --option-1.

This can be accomplished by putting the pattern-match block for --feature-1 immediately after the block for --option-1 and using ;& instead of ;; when terminating the block for --option-1:

    --option-*)
      echo "Doing something that all --option-* options have in common (saw \"$argument\") ..."
    ;;&
    --option-1)
      echo "Doing something specific for --option-1 ..."
    ;& # also execute the code from the matching block below
    --feature-1)
      echo "Doing something specific for --feature-1 ..." 
    ;;
    --option-2)
      echo "Doing something specific for --option-2 ..."
    ;;

The final example considers the return value that is produced by such a caseesac construct. Note that caseesac does not really have a return value by itself – it is a „program flow“ construct – and the return value of the last executed command will be the value of special variable $? after the caseesac has been processed.

#!/bin/bash
# A parser of several options, for demonstration purposes.

for argument in "$@" ; do
  case "$argument" in
    --option-*)
      echo "Doing something that all --option-* options have in common (saw \"$argument\") ..."
    ;;&
    --option-1)
      echo "Doing something specific for --option-1 ..."
    ;&
    --feature-1)
      echo "Doing something specific for --feature-1 ..."
      false # intentionally produce a false return value
    ;;
    --option-2)
      echo "Doing something specific for --option-2 ..."
    ;;
    *)
      echo "Saw any other argument besides the known options: \"$argument\"."
    ;;
  esac

  rv=$?

  echo "Done processing argument \"$argument\"; return value of \"case...esac was $rv"
done

Try out this script with various parameters and observe the return value produced by each iteration of for. What options cause the return value to be 1?