我喜欢在脚本中使用来set -x
显示正在发生的情况,特别是如果脚本将在 CI/CD 管道中运行并且我可能需要事后调试一些故障。
这样做的一个烦恼是,如果我想向用户回显一些文本(例如,状态消息或"I'm starting to do $X"
),那么该消息会输出两次 - 一次用于echo
回显命令本身,然后一次作为该echo
命令的输出。
有什么好方法可以让这个变得更好?一种解决方案是这样的:
set -x
... bunch of normal commands that get echoed
(
# Temporarily don't echo, so we don't double-echo
set +x
echo "Here is my status message"
)
... rest of commands get echoed again
但这样做的两个问题是
- 每次我想告诉用户一些事情时,都要写很多东西,而且它足够“不明显”,以至于每次都可能需要注释
- 它
set +x
也呼应了,这是不可取的。
还有其他效果好的选择吗?
就像是Make 的功能是在前面添加 an@
来抑制回声那就太好了,但我还没能在 Bash 中找到这样的功能。
答案1
您可以通过以下方式在 Bash 中实现此目的不是使用set -x
而不是捕获DEBUG
并进行自己的跟踪:
#!/bin/bash
set -T
trap '! [[ "$BASH_COMMAND" =~ ^(echo|printf) ]] &&
printf "+ %s\n" "$BASH_COMMAND"' DEBUG
foo=bar
echo This is a test
echo $foo
[[ $foo = bar ]] && /usr/bin/printf 'Matched\n'
这个想法是将您想要忽略的命令添加到该行的正则表达式中trap
。运行上面的命令会产生
+ foo=bar
This is a test
bar
+ [[ $foo = bar ]]
+ /usr/bin/printf 'Matched\n'
Matched
set -T
确保陷阱由 shell 函数继承。
您可以添加单独的机制来启用和禁用陷阱,例如使用 shell 变量:
#!/bin/bash
set -T
status() {
NOTRACE=1 echo "$@"
}
trap '! [[ "$BASH_COMMAND" =~ ^(status|NOTRACE=) ]] &&
printf "+ %s\n" "$BASH_COMMAND"' DEBUG
foo=bar
test=$(echo baz)
echo This is a test
status This is a status report
echo $foo
[[ $foo = bar ]] && /usr/bin/printf 'Matched\n'
这会产生
+ foo=bar
+ test=$(echo baz)
+ echo This is a test
This is a test
This is a status report
+ echo $foo
bar
+ [[ $foo = bar ]]
+ /usr/bin/printf 'Matched\n'
Matched
答案2
这是一个可怕的克鲁格,我觉得建议它很肮脏,但是......你可以用魔法别名。这个技巧的关键是别名作为命令执行的解析阶段的一部分进行扩展,因此set -x
在扩展时不会打印任何内容(与函数不同)。因此,您可以创建一个别名,在命令之前添加“关闭-x
”样板文件echo
,然后发现您还需要一个函数来-x
在最后运行“重新打开”样板文件。
您还需要在脚本中打开别名扩展。它通常是禁用的,因此,例如,如果您有类似grep
别名的内容grep --color
,那么每当脚本使用 时,就不会随机注入颜色代码grep
。因此,最安全的做法是unalias -a
先运行,删除任何可能带来麻烦的别名。
无论如何,这是代码:
unalias -a
shopt -s expand_aliases
alias cleanecho='{ set +x; } 2>/dev/null; resetx_after echo'
resetx_after() { "$@"; set -x; }
set -x
cleanecho "Ha, ha, you can't see the command that printed this!"
它是如何工作的:类似的命令cleanecho "something"
扩展为:
{ set +x; } 2>/dev/null; resetx_after echo "something"
{ set +x; } 2>/dev/null
关闭-x
模式(将其自己的跟踪重定向到/dev/null)。然后resetx_after echo "something"
运行,执行:
{ echo "something"; set -x; }
...打印字符串然后-x
重新打开跟踪。
顺便说一句,如果您希望能够printf
类似地使用其他命令,您可以为它们添加类似的别名:
alias cleanprintf='{ set +x; } 2>/dev/null; resetx_after printf'
...或者只是创建一个通用的 don-trace-this 别名来用作前缀:
alias notrace='{ set +x; } 2>/dev/null; resetx_after'
notrace printf 'set -x disabled for this command\n'
答案3
简单的。你已经解决了一半:只要搬进set -x
街区
#!/bin/sh
echo a
(
set -x
echo b
)
echo c
$ a.sh
a
+ echo b
b
c
如果您不想跟踪脚本的一部分 - 分两部分进行跟踪
#!/bin/sh
echo a
(
set -x
echo b
)
echo c
(
set -x
echo d
)
echo e
只需追踪可疑的片段而不是整个脚本。