Bash中使用set +x但不打印它

155

有人知道在bash中是否可以使用set +x而不被打印出来吗:

set -x
command
set +x

痕迹

+ command
+ set +x

但它应该只是打印

+ command

Bash的版本是4.1.10(4)。这个问题困扰我有一段时间了——输出被无用的set +x行所淹没,使跟踪功能不如它本应该有用。


1
这不是回答你的问题,但当你运行脚本时,为什么不使用:script.sh 2>&1 | grep -v 'set +x' - cdarke
7个回答

230

我遇到了同样的问题,但是我找到了一种不使用子shell的解决方案:

set -x
command
{ set +x; } 2>/dev/null

12
好的,注意:如果命令后面没有分号,它将无法正常工作;如果有分号但大括号周围没有空格,则会引发语法错误。 - sdaau
9
这将退出状态归零。 - Garth Kidd
12
每次成功执行命令时,退出状态都会被重置为零。 set +x 就是这样一个成功的命令。 - Daniel Alder
7
如果需要获取 command 的退出状态,可以使用以下形式:{ STATUS=$?; set +x; } 2>/dev/null。然后在随后的行中轻松检查 $STATUS - Greg Price
7
这里是稍微改进的代码高尔夫版:{ set +x; } 2>&-。这个命令会直接关闭文件描述符2,而不是将其重定向到/dev/null。有些程序无法处理将输出发送至stderr的情况,因此通常使用/dev/null比较好;但是,Shell的set -x追踪可以很好地处理它,因此在此处它完美地发挥作用,并且可以使这个命令更加简短。 - Greg Price
显示剩余3条评论

67

您可以使用子shell。在退出子shell时,x的设置将会丢失:

( set -x ; command )

好的,谢谢...说得好。实际上我知道“子shell技巧”。我希望它能比那更容易。这需要对代码进行重大更改,使代码变得更加复杂和难以阅读。在我看来,这比使用set +x命令更糟糕... - Andreas Spindler
我不明白( set -x \n command \n )set -x \n command \n set +x更糟糕在哪里。 - chepner
3
你不能设置变量。 - choroba
2
你不能使用 cd 命令:它不会改变父 shell 中的当前目录。 - Andreas Spindler
抱歉,我不确定我认为你的实际反对意见是什么。这是一个漫长的星期... - chepner
显示剩余2条评论

9
如何采用 @user108471 简化版本的解决方案?
shopt -s expand_aliases
alias trace_on='set -x'
alias trace_off='{ set +x; } 2>/dev/null'

trace_on
...stuff...
trace_off

这个怎么样? function () { set_plus_x='{ set +x; } 2>/dev/null' } - LexH

9
我最近遇到了这个问题,所以我想出了一个解决方案:

我最近对此感到非常烦恼,所以我想出了一个解决方案:

shopt -s expand_aliases
_xtrace() {
    case $1 in
        on) set -x ;;
        off) set +x ;;
    esac
}
alias xtrace='{ _xtrace $(cat); } 2>/dev/null <<<'

通过如下配置,您可以启用或禁用xtrace,并记录参数分配给变量的方式:

xtrace on
ARG1=$1
ARG2=$2
xtrace off

然后您会得到类似以下的输出:

$ ./script.sh one two
+ ARG1=one
+ ARG2=two

1
这是一个很好的观点 - 我忘记了别名不是继承的,所以风险比我想象的要小得多(假设您的脚本可能正在使用定义别名的第三方代码,但我同意这可能不是现实世界中的问题)。+1 - mklement0
除了安全方面,追踪是一个非常常见的功能,有些追踪信息可能是敏感的。 - Andreas Spindler
1
@AndreasSpindler,您能详细说明为什么您认为这种技术更有可能泄露更多的敏感信息吗? - user108471
1
基本上,因为别名和函数是高级结构,所以很难(如果不是不可能)被破坏。但它仍然是一个好的、直接的解决方案——可能是迄今为止最好的解决方案。 - Andreas Spindler
以下是一种省略函数的单行代码变体: alias xtrace='{ read -t 0&&read;[[ $REPLY == on ]]&&set -x||set +x; } 2>&- <<<' (如果您在读取时使用默认的REPLY变量,则最好避免使用此方法) - mr.spuratic
显示剩余2条评论

3
这是几个想法的组合,可以将一块代码封装起来并保留退出状态。
#!/bin/bash
shopt -s expand_aliases
alias trace_on='set -x'
alias trace_off='{ PREV_STATUS=$? ; set +x; } 2>/dev/null; (exit $PREV_STATUS)'

trace_on
echo hello
trace_off
echo "status: $?"

trace_on
(exit 56)
trace_off
echo "status: $?"

执行时:

$ ./test.sh 
+ echo hello
hello
status: 0
+ exit 56
status: 56

不错的尝试,但我认为exit周围的()是不必要的。好吧,也许这有点多虑,但如果这段代码被普遍使用,你就会有一个很好的攻击向量:重新定义trace_ontrace_off并注入读取执行命令的代码。如果你单独使用这样的“工具”,那么它是有益的,但如果代码与其他人一起使用,你必须考虑这些非标准化函数的好处是否超过了缺点。就个人而言,我已经选择了{ set +x; } 2>/dev/null,因为这种结构是通用的,并且不改变退出状态。 - Andreas Spindler
谢谢。我已经与我的同事分享了这种方法,他们很喜欢它能使输出和代码都更简洁。关于 () 包裹 exit 56 的部分; 这只是为了展示子进程的 exit 状态在 bash 脚本中是可访问的;如果没有括号,它就不会成为子进程,脚本将在那个点上退出,状态为 65。 - user3286792

1
这里是另一个。通常情况下,您只想跟踪脚本中的特定命令。那么为什么不编写一个只执行该操作的函数呢?
> call() { set -x; "$@"; { set +x; } 2>/dev/null; }
> call uname -a
+ uname -a
CYGWIN_NT-6.1-WOW W530 3.1.7(0.340/5/3) 2020-08-22 19:03 i686 Cygwin
> call make -j8 *.mak
+ make -j8 some_name.mak

我们可以通过返回(和跟踪)所调用命令的退出代码来进一步改进这个问题:
> call() { local rc; set -x; "$@"; rc=$?; { set +x; } 2>/dev/null; return $rc; }
> call true && echo yes
+ true
+ rc=0
yes
> call false && echo yes
+ false
+ rc=1

这是一个实际的例子,调用Rhapsody CLI程序并使用命令行选项从.rcl文件生成源代码:
die() {
    local c=${1:--1} m=${2:-'Died'}
    echo "$m at ${BASH_SOURCE[1]}:${FUNCNAME[1]} line ${BASH_LINENO[0]} (exit $c)" >&2
    exit $c
}
call() { local rc; set -x; "$@"; rc=$?; { set +x; } 2>/dev/null; return $rc; }

call "$opt_rhapsodycl" -f $rclfile || die $? 'Rhapsody license server not reachable'

例如,如果失败了,就会打印出类似这样的东西:
 + path/to/RhapsodyCL.exe -f configuration.rcl
 + rc=127
 Rhapsody license server not reachable at ./Build:main line 167 (exit 127)

或者在成功的情况下,脚本会继续执行。通过这两个函数(calldie),我们可以编写非常紧凑且易读的单行代码,并且还能产生良好的跟踪输出。


一个非常聪明的想法!提示:如果你把 rc=$? 放在 { set +x; } 的内部,它也会被静音:call() { local rc; set -x; "$@"; { rc=$?; set +x; } 2>/dev/null; return $rc; } - undefined

0
与问题有些相关的是如何使(逻辑)注释也显示在跟踪输出中,以提供脚本命令执行的高级操作的解释。
我通常的做法是,如果您不介意最小的开销,那么:

some_script.sh

#!/usr/bin/env bash
set -x
...
: This shows a directory listing
ls
...

示例输出

+ : This shows a directory listing
+ ls
test.sh

当然,你也可以使用单引号或双引号将参数括起来,并用:内置函数,其中用双引号(或不用引号)的好处是您还可以获得变量扩展(以及其他副作用)以在输出期间发生。 这种方法可以作为一种仅跟踪行内详细消息工具来使用,从而使您能够在常规跟踪输出中注入附加的调试跟踪消息,当禁用跟踪时不会输出这些消息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接