Check for bash options

In bash-4.4 and above, you can use:

myfunction() {
  local -
  set -o someoption
  ...
}

A few notes:

  • that's copied from ash. In ash, the idea is to make $- (which stores the set of options) local. It works in ash because in ash, local doesn't affect the value nor attributes of the variable (contrary to bash where it initially makes it unset). Still it also works with bash the same way as in ash as a special case, that is it doesn't cause $- to be unset or revert to a default behaviour
  • it only covers the options set with set -o option, not the ones set by shopt -s option (bash being the only shell with two sets of options!)
  • like for local variables, it is dynamic scoping, not static. The value of $- will be restored upon returning from the function, but the new setting will affect the other functions or sourced scripts called by your function. If you want static scoping, you'll need to switch to ksh93 and use:

    function myfunction {
      set -o myoption
      ...
      other_function
    }
    

    With the other_function not affected as long as it's declared with the function other_function { syntax as well (and not the Bourne other_function()... syntax).

  • As it doesn't reset the options, your function may still be affected by options set by other code. To avoid that, you'd want to use zsh instead. zsh's equivalent of local - is set -o localoptions, but you can also get to a well known emulation mode locally. For instance:

    myfunction() {
      emulate -L zsh
      set -o someoption
      ...
    }
    

    would start with a sane zsh-default set of options (with some exceptions, see doc) on top of which you add your option. That's also dynamic scoping like in ash/bash, but you can also call other functions with:

    emulate zsh -c 'other_function...'
    

    for that other_function to be called with a vanilla zsh option set.

    zsh also has a number of operators that can make you avoid changing options globally in the first place (like (N)/(D) glob qualifiers for nullglob/dotglob, (#i) glob operator for case insensitive globbing, ${(s:x:)var} to avoid mingling with $IFS, ${~var} to request globbing upon parameter expansion (you need noglob in other Bourne-like shells to avoid (!) it)...


The $SHELLOPTS variable holds all set options separated by colons. As an example, it may look like:

braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor

To check for a given option one may do the following:

# check if pipefail is set, if not set it and remember this
if [[ ! "$SHELLOPTS" =~ "pipefail" ]]; then
    set -o pipefail;
    PIPEFAIL=0;
else
    PIPEFAIL=1;
fi

# do something here

# reset pipefail, if it was not set before
if [ "$PIPEFAIL" -eq 0 ]; then
    set +o pipefail;
fi

If you want to set a shell option inside a function, but do not want to affect the caller, you do it two different ways:

Option 1: subshell

Put the function inside a subshell (notice the parenthesis surrounding the function's statements instead of curly brackets):

function foo() (
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
)

Then, the caller can have pipefail unset and is not affected:

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

or

Option 2: test-and-reset

You could test-and-reset the option as needed (notice the curly braces around the function statements -- no subshell):

function foo() {
  shopt -q -o pipefail
  local resetpipefail=$?
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
  # only reset pipefail if needed
  [[ $resetpipefail -eq 1 ]] && set +o pipefail  
}

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

Hat tip to cuonglm for their shopt -q answer.

Tags:

Bash

Shopt