如何恢复像`set -x`这样的shell选项的值?


47

我想set -x在脚本的开头,然后“撤消”它(回到设置之前的状态),而不是盲目地设置+x。这可能吗?

PS:我已经在这里检查过了;据我所知,这似乎并没有回答我的问题。

Answers:


58

抽象

要反转,set -x只需执行一个set +x。在大多数情况下,字符串的反义词set -str是带有+:的相同字符串set +str

通常,要恢复所有(有关bash的信息,请参阅下面的内容errexit)(通过set命令进行更改),您可以这样做(也有关bash的shopt选项,请阅读以下内容):

oldstate="$(set +o); set -$-"                # POSIXly store all set options.
.
.
set -vx; eval "$oldstate"         # restore all options stored.

较长的说明

重击

该命令:

shopt -po xtrace

将生成一个反映选项状态的可执行字符串。该p标志表示打印,该o标志指定我们正在询问该set命令设置的选项(而不是该命令设置的选项shopt)。您可以将此字符串分配给变量,然后在脚本末尾执行该变量以恢复初始状态。

# 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"

此解决方案同时适用于多个选项:

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"

添加set +vx可避免打印一长串选项。


而且,如果您未列出任何选项名称,

oldstate="$(shopt -po)"

它为您提供所有选项的值。而且,如果省略了o标记,则可以使用shopt选项执行相同的操作:

# store state of dotglob option.
dglobstate="$(shopt -p dotglob)"

# store state of all options.
oldstate="$(shopt -p)"

如果需要测试是否set设置了选项,最惯用的方法是:

[[ -o xtrace ]]

比其他两个类似的测试更好:

  1. [[ $- =~ x ]]
  2. [[ $- == *x* ]]

使用任何测试,都可以:

# 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"

这是测试shopt选项状态的方法:

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

一个简单的,符合POSIX的解决方案,用于存储所有set选项:

set +o

这是在POSIX标准描述如下:

+ o

    将当前选项设置以适合于作为实现相同选项设置的命令重新输入到外壳的格式写入标准输出。

因此,只需:

oldstate=$(set +o)

将保留使用set命令设置的所有选项的值。

同样,将选项恢复为其原始值只是执行变量的问题:

set +vx; eval "$oldstate"

这完全等同于使用Bash的shopt -po。请注意,它不会涵盖所有可能的Bash选项,因为其中一些是由设置的shopt

bash特例

shoptbash中列出了许多其他shell选项:

$ 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

这些可以追加到上面设置的变量中,并以相同的方式恢复:

$ oldstate="$oldstate;$(shopt -p)"
.
.                                   # change options as needed.
.
$ eval "$oldstate" 

可以这样做($-附加以确保errexit保留):

oldstate="$(shopt -po; shopt -p); set -$-"

set +vx; eval "$oldstate"             # use to restore all options.

注意:每个外壳程序都有一个稍微不同的方式来构建已设置或未设置的选项列表(更不用说已定义的不同选项),因此字符串不能在外壳程序之间移植,但是对于同一外壳程序有效。

zsh特例

zsh从5.3版开始,它也可以正常工作(遵循POSIX)。在以前的版本中,它仅部分遵循POSIX set +o,它以适合于作为命令重新输入到Shell的格式打印选项,但仅用于设置选项(它不打印未设置的选项)。

mksh特例

mksh(以及相应的lksh)尚不能执行此操作(MIRBSD KSH R54 2016/11/11)。mksh手册包含以下内容:

在将来的版本中,set + o将符合POSIX的行为,并显示命令以恢复当前选项。

设置-e特殊情况

在bash中,set -eerrexit)的值在子shell内重置,这使得很难set +o在$(…)子shell内捕获其值。

解决方法是,使用:

oldstate="$(set +o); set -$-"

21

使用Almquist Shell及其衍生版本(至少为dashNetBSD / FreeBSD sh)和 bash4.4或更高版本,您可以使用来将选项设置为本地函数local -$-如果愿意,可以将变量设置为本地):

$ bash-4.4 -c 'f() { local -; set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace

这并不适用于采购文件,但你可以重新定义sourcesource() { . "$@"; }解决这一点。

使用时ksh88,默认情况下,选项更改在该函数中是本地的。使用ksh93,这仅是使用function f { ...; }语法定义的函数的情况(与用于其他shell(包括ksh88)的动态范围相比,范围是静态的):

$ ksh93 -c 'function f { set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace

在中zsh,通过以下localoptions选项完成此操作:

$ zsh -c 'f() { set -o localoptions; set -x; echo test; }; f; echo no trace'
+f:0> echo test
test
no trace

POSIXly,您可以执行以下操作:

case $- in
  (*x*) restore=;;
  (*) restore='set +x'; set -x
esac
echo test
{ eval "$restore";} 2> /dev/null
echo no trace

然而,一些炮弹将输出+ 2> /dev/null时的恢复(你会看到痕迹是的case,当然,如果结构set -x已经启用)。该方法也不是可重入的(例如,如果您在调用自身的函数或使用相同技巧的另一个函数中这样做)。

有关如何实现可解决此问题的堆栈的信息,请参见https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh(变量的本地作用域和POSIX shell的选项)。

对于任何外壳,您都可以使用子外壳来限制选项的范围

$ sh -c 'f() (set -x; echo test); f; echo no trace'
+ echo test
test
no trace

但是,这不仅限制了选项,还限制了所有内容的范围(变量,函数,别名,重定向,当前工作目录...)。


bash 4.4于4天前(2016年9月16日)问世,所以您可能必须自己重新编译它,但为什么不这样做
edi9999

在我看来,这oldstate=$(set +o)是一种存储所有选项的更简单(和POSIX)方式。
sorontar '16

@sorontar,好一点虽然不为壳实现工作像pdksh/ mksh(和其他的pdksh的衍生物)或zsh其中set +o仅输出从默认设置的偏差。这将适用于bash / dash / yash,但不能移植。
斯特凡Chazelas

13

您可以$-在开始时读取变量以查看是否-x已设置,然后将其保存到变量,例如

if [[ $- == *x* ]]; then
  was_x_set=1
else
  was_x_set=0
fi

Bash手册

($-,连字符。)扩展为调用时,由set Builtin命令或由shell本身设置的那些选项标志(如-i选项)。


7
[[ $SHELLOPTS =~ xtrace ]] && wasset=1
set -x
echo rest of your script
[[ $wasset -eq 0 ]] && set +x

在bash中,$ SHELLOPTS设置为带有已打开的标志。在打开xtrace之前先进行检查,并且仅在xtrace之前关闭时才将其重置。


5

只是说一下显而易见的(如果set -x要在脚本的持续时间内生效),并且这只是一个临时测试措施(不是永久地作为输出的一部分),则可以使用该-x选项调用脚本,例如,

$ bash -x path_to_script.sh

...或者,通过添加以下-x选项,临时更改脚本(第一行)以启用调试输出:

#!/bin/bash -x
...rest of script...

我意识到这可能对您想要的东西来说太笼统了,但这是启用/禁用的最简单,最快捷的方法,而不会使脚本与您可能想要删除的临时内容过度复杂(以我的经验)。


3

这提供了保存和恢复通过POSIX $-特殊参数可见的标志的功能。我们将local扩展名用于局部变量。在符合POSIX的可移植脚本中,将使用全局变量(无local关键字):

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"}
}

类似于在Linux内核中如何保存和恢复中断标志的用法:

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 ... */

对于该$-变量未涵盖的任何扩展选项,这将不起作用。

只是注意到POSIX正是我想要的东西:的+o参数set不是选项,而是一个转储一堆命令的命令,如果eval-ed将恢复该选项。所以:

flags=$(set +o)

set -x

# ...

eval "$flags"

而已。

一个小问题是,如果在-x此之前打开了该选项evalset -o则会看到一堆难看的命令。为了消除这种情况,我们可以执行以下操作:

set +x             # turn off trace not to see the flurry of set -o.
eval "$flags"      # restore flags

1

您可以使用子外壳。

(
   set 
   do stuff
)
Other stuff, that set does not apply to
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.