15

I'm writing a shell function that will be called from many different places and I want to ignore all errors occurring inside the function disabling the (possibly enabled) set -e shell option, executing set +e.

The problem is that I don't know if that option was set or not, so I don't know if it's really needed to disable it, and more important, if I need to set it again at the end.

So, how do I know if it is set or not? Is there any way to ignore errors of all commands inside a shell function ignoring the -e shell option if is set?

Sly
  • 480

4 Answers4

22

Look at the value of $-, if it contains an e, then the -e option is set. This variable contains a list of all set options. This is described in the Bash man page.

Giacomo1968
  • 58,727
Yedric
  • 739
3

From man bash:

CONDITIONAL EXPRESSIONS
...
   -o optname
      True if the shell option optname is enabled. 
...

Which means, that this will do it:

if [ -o errexit ]
then
   ....
fi

and is much more clear than the rest of the alternatives.

PS: for optnames, look in man bash for option-name:

-o option-name
...
   errexit Same as -e.
mmoossen
  • 131
2

Three methods to consider:

  • Using shopt -po errexit to save the code needed to restore it in a variable and calling it later.
  • Using shopt -qo errexit which tells you on or off via the exit code
  • Using a subshell inside your function which is impossible to get wrong but also is not always applicable depending on what your function does.

Save and restore

If you want to set it back to what it was, there is an easy way to do this:

my_func(){
    local restore_errexit="$(shopt -po errexit)"
    set +e
# do stuff

${restore_errexit}

}

This is because (man bash) -p tells it to output the code necessary to set the option to whatever it is right now.

shopt [-pqsu] [-o] [optname ...]
       [...] The -p option causes output to be displayed in a form that 
       may be reused as input
       -q     Suppresses  normal  output  (quiet mode); the return status
              indicates whether the optname is set or unset. [...]
       -o     Restricts the values of optname to be those defined
              for the -o option to the set builtin.

EDIT: Note -o in the manpage. If an options is one of the options that can be set or unset with set builtin, then the -o is needed. So shopt -s extdebug, shopt -so xtrace

Control if statements

You may still want to do something else based on whether it was on or off, you can use [[ -o errexit ]] as was suggested in other answers but using shopt -q can work with both set and non-set options like extdebug (just don't put `-o'):

my_func(){
    local errexit_was_on
    if shopt -qo errexit ; then
         echo "${FUNCNAME[0]}: INFO: turning off errexit for this function"
         set +o errexit # same as `set +e` or `shopt -uo errexit`
         errexit_was_on=true
    else
         errexit_was_on=false
    fi
do_stuff

if [[ ${errexit_was_on} == true ]] ; then
     echo "${FUNCNAME[0]}: INFO: reactivating errexit for this function"
     set -o errexit # same as `set +e` or `shopt -so errexit`
fi

}

If your only goal is to save it and restore it to what it was and you don't care what it was, then I would use the first method and only use this one if you actually want to do one thing or the other based on the value of the option.

I think that @mmoossen's answer is a bit more clear than this but it only works for set options and not for shopt options.

Subshells

Another alternative is to use subshells. That depends on what your function does. If your function produces output and returns an exit code you can do it but if it has side effects then it depends: if the side effect is creating files, no problem, if it's setting outer-scope variables, it won't work.

my_func(){
    (
        set -e
        do_stuff
    )
}

Since the set -e happens in a subshell, it can't affect the main process I usually start with this because you can't get it wrong. With that, I try to communicate by producing output. If that turns out to be inconvenient to the point where it becomes worth it to communicate by setting outer-scope variables, then I pivot to the first method. Finally if I want to print some messages, then I use the second method.

Side note: Bash allows you to do

my_func()(

)

which is a shorthand for the same thing above so that your entire function happens in a subshell. I would advise against doing that: I used to do it but once I started modifying one such function and had forgotten that it was a subshell. One of those modifications was having it set the value of a variable from the outer scope. I started to believe that my computer was haunted because I was setting that variable and the calling code couldn't see the new value. I spent more time than I'd like to admit trying to debug this.

1

I like both answers. Ansgar's suggestion works well with bash, Trevor's is good if grep is in the PATH. If you want to be independent of shell (bash/sh) and PATH you could use

[ "${-#*e}" != "$-" ]