Jay Taylor's notes
back to listing indexbash - How to restore the value of shell options like `set -x`? - Unix & Linux Stack Exchange
[web search]I want to set -x
at the beginning of my script and "undo" it (go back to the state before I set it) afterward instead of blindly setting +x
. Is this possible?
P.S.: I've already checked here; that didn't seem to answer my question as far as I could tell.
Abstract
To reverse a set -x
just execute a set +x
. Most of the time, the reverse of an string set -str
is the same string with a +
: set +str
.
In general, to restore all (read below about bash errexit
) shell options (changed with set
command) you could do (also read below about bash shopt
options):
oldstate="$(set +o); set -$-" # POSIXly store all set options.
.
.
set -vx; eval "$oldstate" # restore all options stored.
Longer Description
bash
This command:
shopt -po xtrace
will generate an executable string that reflects the state of the option.
The p
flag means print, and the o
flag specifies that we are asking about option(s) set by the set
command (as opposed to option(s) set by the shopt
command).
You can assign this string to a variable, and execute the variable at the end of your script to restore the initial state.
# store state of xtrace option.
tracestate="$(shopt -po xtrace)"
# change xtrace as needed
echo "some commands with xtrace as externally selected"
set -x
echo "some commands with xtrace set"
# restore the value of xtrace to its original value.
eval "$tracestate"
This solution works for multiple options simultaneously:
oldstate="$(shopt -po xtrace noglob errexit)"
# change options as needed
set -x
set +x
set -f
set -e
set -x
# restore to recorded state:
set +vx; eval "$oldstate"
Adding set +vx
avoids the printing of a long list of options.
And, if you don’t list any option names,
oldstate="$(shopt -po)"
it gives you the values of all options.
And, if you leave out the o
flag,
you can do the same things with shopt
options:
# store state of dotglob option.
dglobstate="$(shopt -p dotglob)"
# store state of all options.
oldstate="$(shopt -p)"
If you need to test whether a set
option is set,
the most idiomatic (Bash) way to do it is:
[[ -o xtrace ]]
which is better than the other two similar tests:
[[ $- =~ x ]]
[[ $- == *x* ]]
With any of the tests, this works:
# record the state of the xtrace option in ts (tracestate):
[ -o xtrace ] && ts='set -x' || ts='set +x'
# change xtrace as needed
echo "some commands with xtrace as externally selected"
set -x
echo "some commands with xtrace set"
# set the xtrace option back to what it was.
eval "$ts"
Here’s how to test the state of a shopt
option:
if shopt -q dotglob
then
# dotglob is set, so “echo .* *” would list the dot files twice.
echo *
else
# dotglob is not set. Warning: the below will list “.” and “..”.
echo .* *
fi
POSIX
A simple, POSIX-compliant solution to store all set
options is:
set +o
which is described in the POSIX standard as:
+o
Write the current option settings to standard output in a format that is suitable for reinput to the shell as commands that achieve the same options settings.
So, simply:
oldstate=$(set +o)
will preserve values for all options set using the set
command.
Again, restoring the options to their original values is a matter of executing the variable:
set +vx; eval "$oldstate"
This is exactly equivalent to using Bash's shopt -po
. Note that it will not cover all possible Bash options, as some of those are set by shopt
.
bash special case
There are many other shell options listed with shopt
in bash:
$ shopt
autocd off
cdable_vars off
cdspell off
checkhash off
checkjobs off
checkwinsize on
cmdhist on
compat31 off
compat32 off
compat40 off
compat41 off
compat42 off
compat43 off
complete_fullquote on
direxpand off
dirspell off
dotglob off
execfail off
expand_aliases on
extdebug off
extglob off
extquote on
failglob off
force_fignore on
globasciiranges off
globstar on
gnu_errfmt off
histappend on
histreedit off
histverify on
hostcomplete on
huponexit off
inherit_errexit off
interactive_comments on
lastpipe on
lithist off
login_shell off
mailwarn off
no_empty_cmd_completion off
nocaseglob off
nocasematch off
nullglob off
progcomp on
promptvars on
restricted_shell off
shift_verbose off
sourcepath on
xpg_echo off
Those could be appended to the variable set above and restored in the same way:
$ oldstate="$oldstate;$(shopt -p)"
.
. # change options as needed.
.
$ eval "$oldstate"
It is possible to do (the $-
is appended to ensure errexit
is preserved):
oldstate="$(shopt -po; shopt -p); set -$-"
set +vx; eval "$oldstate" # use to restore all options.
Note: each shell has a slightly different way to build the list of options that are set or unset (not to mention different options that are defined), so the strings are not portable between shells, but are valid for the same shell.
zsh special case
zsh
also works correctly (following POSIX) since version 5.3. In previous versions it followed POSIX only partially with set +o
in that it printed options in a format that was suitable for reinput to the shell as commands, but only for set options (it didn't print un-set options).
mksh special case
The mksh (and by consequence lksh) is not yet (MIRBSD KSH R54 2016/11/11) able to do this. The mksh manual contains this:
In a future version, set +o will behave POSIX compliant and print commands to restore the current options instead.
set -e special case
In bash, the value of set -e
(errexit
) is reset inside sub-shells, that makes it difficult to capture its value with set +o
inside a $(…) sub-shell.
As a workaround, use:
oldstate="$(set +o); set -$-"
set -$-
workaround doesn't seem to work when running bash interactively: it raises bash: set: -i: invalid option
(tested using GNU bash, version 4.4.12(1)-release
)
– ErikMD
Feb 17 at 22:11
Similar to what @Jeff Shaller said but set to NOT echo that part in the middle (which is what I needed):
OPTS=$SHELLOPTS ; set +x
echo 'message' # or whatever
[[ $OPTS =~ xtrace ]] && set -x
You can use a sub-shell.
(
set …
do stuff
)
Other stuff, that set does not apply to
With the Almquist shell and derivatives (dash
, NetBSD/FreeBSD sh
at least) and bash
4.4 or above, you can make options local to a function with local -
(make the $-
variable local if you like):
$ bash-4.4 -c 'f() { local -; set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace
That doesn't apply to sourced files, but you can redefine source
as source() { . "$@"; }
to work around that.
With ksh88
, option changes are local to the function by default. With ksh93
, that's only the case for functions defined with the function f { ...; }
syntax (and the scoping is static compared to the dynamic scoping used in other shells including ksh88):
$ ksh93 -c 'function f { set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace
In zsh
, that's done with the localoptions
option:
$ zsh -c 'f() { set -o localoptions; set -x; echo test; }; f; echo no trace'
+f:0> echo test
test
no trace
POSIXly, you can do:
case $- in
(*x*) restore=;;
(*) restore='set +x'; set -x
esac
echo test
{ eval "$restore";} 2> /dev/null
echo no trace
However some shells will output a + 2> /dev/null
upon the restore (and you'll see the trace of that case
construct of course if set -x
was already enabled). That approach is also not re-entrant (like if you do that in a function that calls itself or another function that uses the same trick).
See https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh (local scope for variables and options for POSIX shells) for how to implement a stack that works around that.
With any shell, you can use subshells to limit the scope of options
$ sh -c 'f() (set -x; echo test); f; echo no trace'
+ echo test
test
no trace
However, that limits the scope of everything (variables, functions, aliases, redirections, current working directory...), not just options.
oldstate=$(set +o)
is a simpler (and POSIX) way to store all options.
– Isaac
Sep 20 '16 at 22:48
pdksh
/mksh
(and other pdksh derivatives) or zsh
where set +o
only outputs the deviations from the default settings. That would work for bash/dash/yash but not be portable.
– Stéphane Chazelas
Sep 21 '16 at 8:35
This provides functions to save and restore the flags that are visible through the POSIX $-
special parameter. We use the local
extension for local variables. In a POSIX conforming portable script, global variables would be used (no local
keyword):
save_opts()
{
echo $-
}
restore_opts()
{
local saved=$1
local on
local off=$-
while [ ${#saved} -gt 0 ] ; do
local rest=${saved#?}
local first=${saved%$rest}
if echo $off | grep -q $first ; then
off=$(echo $off | tr -d $first)
fi
on="$on$first"
saved=$rest
done
set ${on+"-$on"} ${off+"+$off"}
}
This is used similarly to how interrupt flags are saved and restored in the Linux kernel:
Shell: Kernel:
flags=$(save_opts) long flags;
save_flags (flags);
set -x # or any other local_irq_disable(); /* disable irqs on this core */
# ... -x enabled ... /* ... interrupts disabled ... */
restore_opts $flags restore_flags(flags);
# ... x restored ... /* ... interrupts restored ... */
This won't work for any of the extended options which are not covered in the $-
variable.
Just noticed that POSIX has what I was looking for: the +o
argument of set
is not an option, but a command which dumps a bunch of commands which, if eval
-ed will restore the options. So:
flags=$(set +o)
set -x
# ...
eval "$flags"
That's it.
One small problem is that if the -x
option is turned prior to this eval
, an ugly flurry of set -o
commands is seen. To eliminate this, we can do the following:
set +x # turn off trace not to see the flurry of set -o.
eval "$flags" # restore flags
You can read the $-
variable at the beginning to see whether -x
is set or not and then save it to a variable e.g.
if [[ $- == *x* ]]; then
was_x_set=1
else
was_x_set=0
fi
From the Bash manual:
($-, a hyphen.) Expands to the current option flags as specified upon invocation, by the set builtin command, or those set by the shell itself (such as the -i option).
Just to state the obvious, if set -x
is to be in effect for the duration of the script, and this is just a temporary testing measure (not to be permanently part of the output), then either invoke the script w/ the -x
option, e.g.,
$ bash -x path_to_script.sh
...or, temporarily change the script (first line) to enable debug output by adding the -x
option:
#!/bin/bash -x
...rest of script...
I realize this probably is too broad a stroke for what you want, but it's the simplest & quickest way to enable/disable, without overly complicating the script with temporary stuff that you'll probably want to remove anyway (in my experience).
[[ $SHELLOPTS =~ xtrace ]] && wasset=1
set -x
echo rest of your script
[[ $wasset -eq 0 ]] && set +x
In bash, $SHELLOPTS is set with the flags that are turned on. Check it before you turn xtrace on, and reset xtrace only if it was off before.
Linked
Related
Hot Network Questions
-
Why does IPA have stress here /ɡəˈʃtɔlt/
-
Are any real-life animals considered magical in the HP Universe?
-
Please help me identify the set these LEGO parts are from
-
How can we secure communication of an unchangeable app (Zoom)?
-
How to find out what OS version & CPU type used in Windows CE
-
Sci-fi story in OMNI magazine about transhumanism
-
What political and social factors underlie Sweden's controversial response to COVID-19?
-
Expedient repair for water damaged drywall behind tile
-
Massive Medieval structures
-
Road bike gear common practices
-
Did Trump vote by mail in the last two elections?
-
Has the number of pneumonia deaths in the US dramatically dropped in 2020?
-
What chemical reactions could ancient people use to propel projectiles?
-
What to do when a reviewer is late while daily posting about other activities on social media?
-
How can I offset the risk of a market crash when making a retirement plan?
-
What does Eru Ilúvatar's name mean?
-
Do studies show that viruses are contagious?
-
What is the most ‘understandable’ way to order sparkling water in German?
-
Why is same alloys in solder wire solid and in solder paste liquid?
-
Why did people not tend to keep records or consider future consequences until very recently?
-
How do I get started working with JSON in Apex?
-
How can I politely reply and educate students with unreasonable requests?
-
How do you take on less stress in a role than your predecessor did?
-
"Realistic" Thermostat Interface vs Minimalist
site design / logo © 2020 Stack Exchange Inc; user contributions licensed under cc by-sa 4.0 with attribution required. rev 2020.4.9.36569
This site is not affiliated with Linus Torvalds or The Open Group in any way.