How to save & restore all shell options including errexit
I have read through many questions on various stack exchange sites and unix help forums on how to modify shell options and then restore them –
the most comprehensive one I found on here is at How to "undo" a `set -x`?
The received wisdom seems to be to either save off the result of set +o
or shopt -po
and then eval
it later to restore the previous settings.
However, in my own testing with bash 3.x and 4.x, the errexit
option does not get saved correctly when doing command substitution.
Here is an example script to show the issue:
set -o errexit
set -o nounset
echo "LOCAL SETTINGS:"
set +o
OLDOPTS=$(set +o)
echo
echo "SAVED SETTINGS:"
echo "$OLDOPTS"
And output (I trimmed out some of the irrelevant variables):
LOCAL SETTINGS:
set -o errexit
set -o nounset
SAVED SETTINGS:
set +o errexit
set -o nounset
This seems extremely dangerous. Most scripts I write depend on errexit
to halt execution if any commands fail. I just located a bug in one of my scripts caused by this, where the function that was supposed to restore errexit
at the end wound up overriding it, setting it back to the default of off for the duration of the script.
What I’d like to be able to do is write functions that can set options as needed and then restore all the options properly before exiting. But it seems as if in the subshell invoked by the command substitution, errexit
is not inherited.
I’m at a loss for how to save off the result of set +o
without using command substitution or jumping through FIFO hoops. I can read from $SHELLOPTS
but it is not writable or in eval
-able format.
I know one alternative is to use a subshell function, but that introduces a lot of headaches for being able to log output as well as pass back multiple variables.
Probably related: https://stackoverflow.com/questions/29532904/bash-subshell-errexit-semantics (seems there is a workaround for bash 4.4 and up but I’d rather have a portable solution)
What you’re doing should work. But bash turns off the errexit
option in command substitutions, so it preserves all the options except this one. This is specific to bash and specific to the errexit
option. Bash does preserve errexit
when running in POSIX mode. Since bash 4.4, bash also doesn’t clear errexit
in a command substitution if shopt -s inherit_errexit
is in effect.
Since the option is turned off before any code runs inside the command substitution, you have to check it outside.
OLDOPTS=$(set +o)
case $- in
*e*) OLDOPTS="$OLDOPTS; set -e";;
*) OLDOPTS="$OLDOPTS; set +e";;
esac
If you don’t like this complexity, use zsh instead.
setopt local_options
errexit
is propagated into process substitutions.
set -e
# Backup restore commands into an array
declare -a OPTS
readarray -t OPTS < <(shopt -po)
set +e
# Restore options
declare cmd
for cmd in "${OPTS[@]}"; do
eval "$cmd"
done
Check:
$ shopt -po errexit
set -o errexit
Bash version:
$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
After trying old of the above on alpine 3.6, I’ve now taken the following much simpler approach:
OLDOPTS="$(set +o); set -${-//c}"
set -euf -o pipefail
... my stuff
# restore original options
set +vx; eval "${OLDOPTS}"
as per documentation, “$-” holds the list of currently active options. Seems to work great, am I missing anything?
The simple solution is to append the errexit
setting to OLDOPTS
:
OLDOPTS="$(set +o)"
[ "${BASH_VERSION:+x}" ] && shopt -qo errexit && OLDOPTS+=";set -e" || true
Done.