从脚本外部抑制 bash 执行跟踪 (set -x)

hum*_*man 17 shell bash set

我试图找到这个问题的答案,但到目前为止没有运气:

我有一个运行其他一些脚本的脚本,其中许多其他脚本都包含“set -x”,这使得它们打印执行的每个命令。如果任何脚本将错误消息发送到 stderr,我想摆脱它但保留信息。

所以我不能简单地写 ./script 2>/dev/null

此外,我没有编辑其他脚本的权限,因此我无法手动更改设置选项。

我正在考虑将所有内容从 stderr 记录到单独的文件并过滤掉跟踪命令,但也许有更简单的方法?

Sté*_*las 25

使用bash4.1 及更高版本,您可以执行

BASH_XTRACEFD=7 ./script.bash 7> /dev/null
Run Code Online (Sandbox Code Playgroud)

(也适用于bash作为 调用时sh)。

基本上,我们告诉在文件描述符 7 上bash输出xtrace输出而不是默认值 2,并将该文件描述符重定向到/dev/null. fd 编号是任意的。使用 2 以上的 fd,该 fd 未在您的脚本中以其他方式使用。如果您在其中输入此命令的 shell 是bashyash,您甚至可以使用大于 9 的数字(尽管如果 shell 内部使用文件描述符,您可能会遇到问题)。

如果您bash从中调用该脚本的 shell是zsh,您还可以执行以下操作:

(export BASH_XTRACEFD; ./script.bash {BASH_XTRACEFD}> /dev/null)
Run Code Online (Sandbox Code Playgroud)

为变量自动分配第一个大于 9 的空闲 fd。

对于旧版本的bash,另一种选择,如果xtrace打开 with set -x(与#! /bin/bash -xor相对set -o xtrace)将重新定义set为在传递时不执行任何操作的导出函数-x(尽管如果它(或bash它调用的任何其他脚本)会破坏脚本)用于set设置位置参数)。

喜欢:

set()
  case $1 in
    (-x) return 0;;
    (-[!-]|"") builtin set "$@";;
    (*) echo >&2 That was a bad idea, try something else; builtin set "$@";;
  esac

export -f set
./script.bash
Run Code Online (Sandbox Code Playgroud)

另一种选择是在每个命令之前执行的$BASH_ENV文件中添加调试陷阱set +x

echo 'trap "{ set +x; } 2>/dev/null" DEBUG' > ~/.no-xtrace
BASH_ENV=~/.no-xtrace ./script.bash
Run Code Online (Sandbox Code Playgroud)

但是,当set -x在子外壳中完成时,这将不起作用。

正如@ilkkachu 所说,如果您对文件系统上的任何文件夹具有写权限,您至少应该能够制作脚本的副本并对其进行编辑。

如果您无处可写脚本的副本,或者每次对原始脚本进行更新时都不方便制作和编辑新副本,您仍然可以执行以下操作:

 bash <(sed 's/set -x/set +x/g' ./script.bash)
Run Code Online (Sandbox Code Playgroud)

如果脚本使用$0特殊变量或特殊变量$BASH_SOURCE(例如查找与脚本本身的位置相关的文件),则该(以及复制方法)可能无法正常工作,因此您可能需要进行更多编辑,例如替换$0为脚本的路径...


ilk*_*chu 5

由于它们是脚本,您可以制作它们的副本并进行编辑。

除此之外,过滤输出看起来很简单,您可以明确设置PS4比单个加号更不寻常的东西,以使过滤更容易:

PS4="%%%%" bash script.sh 2>&1 | grep -ve '^%%%%'
Run Code Online (Sandbox Code Playgroud)

(当然,这会导致 stdout 和 stdin 崩溃,但是在 Bash 中只使用 stderr 管道会有点麻烦,所以我会忽略它)

  • @Patrick,在`bash` 中这样做有问题,因为`grep` 是异步运行的(bash 不会等待它,所以它可以(并且经常会)在脚本中的下一个命令启动后输出内容)。 (4认同)