我正在使用一个程序,当出现问题时,该程序会输出错误消息,但不会相应地设置其退出状态:退出状态始终为 0,表示成功。我想从 shell 脚本运行这个程序,并在它发出任何错误消息时获得非零退出状态,以便脚本可以告诉程序失败。
错误消息遵循我可以与 匹配的可预测模式grep
,并grep
根据是否找到匹配来设置其退出状态,因此我可以将程序的输出通过管道传输到grep
并否定结果以!
获得我想要的退出状态。
问题是,如果我通过管道进入grep
,我不能看程序的输出不再存在,因为grep
消耗了它。我想以某种方式扫描输出中的错误消息,但也可以正常显示输出,因为除了错误之外还有其他重要消息。不幸的是,grep
没有选项可以传递所有输入、匹配和不匹配的行。
我发现的一种方法大多作品是:
! my_program | tee /dev/stderr | grep -q "error message"
这会将程序的输出输入到标准错误中grep
,但也会将其复制到标准错误中,该错误不会被重定向,因此我可以看到它。当我的脚本的标准输出和标准错误都发送到终端时,这是可以的(因此哪一个收到消息并不重要),但如果标准输出和标准错误被重定向到不同的地方,则可能会导致问题;消息最终会出现在错误的位置。
使用bash
POSIX shell 和 GNU 工具,是否有一种(简洁的)方法来扫描程序的标准输出和/或标准错误流grep
,并相应地设置退出状态,同时也让两个流都到达其正常目的地?
(请注意,将my_program
其错误消息写入标准输出,而不是标准错误,因此只能扫描标准输出流的解决方案是可以的,尽管并不理想。如果它有所不同,我在 CentOS 7 上执行此操作。 )
答案1
...是否有一种(简洁)方法可以使用 grep 之类的工具扫描程序的标准输出和/或标准错误流并相应地设置退出状态,同时也让两个流都到达其正常目的地?
...my_program 将其错误消息写入标准输出,而不是标准错误,因此只能扫描标准输出流的解决方案就可以了,虽然不理想。
我的解决方案回答了加粗的上面的部分。
我认为最简单的方法是通过awk
:
myprogram |
awk 'BEGIN {status = 0} /error message/ {status = 1} 1; END {exit(status)}'
该awk
命令按原样输出它输入的所有内容,但最后它退出的状态取决于“错误消息”是否是输入的一部分。
简洁版本(变量名较短):
myprogram | awk 'BEGIN{s=0} /error message/{s=1} 1; END{exit(s)}'
答案2
以下是一些在 stdout/stderr 上 grep 错误消息的衬垫$MSG
,并且一旦出现错误消息就停止/不停止程序:
# grep on stdout, do not stop early
my_program | awk -v s="$MSG" '$0~s{r=1} 1; END{exit(r)}'
# grep on stdout, do stop early
my_program | awk -v s="$MSG" '$0~s{exit(1)} 1'
# grep on stderr, do not stop early
{ my_program 2>&1 >&3 | awk -v s="$MSG" '$0~s{r=1} 1; END{exit(r)}' >&2; } 3>&1
# grep on stderr, do stop early
{ my_program 2>&1 >&3 | awk -v s="$MSG" '$0~s{exit(1)} 1' >&2; } 3>&1
笔记:
对于所有人:您应用的流
grep
将失去其潜在的 tty 状态,因为my_program
看到它进入awk
.这可能会影响my_program
写入该流的方式,例如,它可能不会打印旋转进度条,因为它可能假设它无法控制光标位置。对于所有:stdout 和 stderr 不会合并,并且可以像往常一样彼此独立地重定向。
对于所有人: 的原始退出代码
my_program
被完全忽略。唯一的其他简单选择是:如果my_program
出现错误退出或出现错误消息,则退出并出现错误。要获得此行为,您需要pipefail
在执行管道的 Bash shell 中启用。例如:(set -o pipefail; my_program | awk -v s="$MSG" '$0~s{exit(1)} 1')
对于 stderr 上的 grep 操作:上面的简单命令行假定文件描述符 3 未使用。这通常是一个可以做的假设。为了避免这种假设,您可以让 Bash 分配一个保证不被使用的文件描述符:
bash -c 'exec {fd}>&1; { my_program 2>&1 >&${fd} | awk -v s="$MSG" '\''$0~s{exit(1)} 1'\'' >&2; } ${fd}>&1'
答案3
尝试并排两个终端。在第一个 shell 窗口中:
tail -F /tmp/xyzzy
在第二个中,正是您所做的,仅对 tmp 文件:
my_program | tee /tmp/xyzzy | grep -q "error message"
按该顺序启动它们。
这很笨拙,因为您需要经常清除临时文件,或者选择一个新名称,但它可以工作。
稍后添加...尝试类似操作:
my_program | tee /tmp/xyzzy ; grep -q "error message" /tmp/xyzzy
序列的退出状态是 grep 的退出状态。这与你想要的相反,叹息。所以否定它。
my_program | tee /tmp/xyzzy ; ! grep -q "error message" /tmp/xyzzy
答案4
baz()
制作一个输出的演示函数“富”到标准输出和“酒吧”到标准错误:
baz() { echo foo ; echo bar >& 2 ; }
简单的情况,运行grep foo
一下标准输出,以及grep bar
关于标准错误:
{ baz 2>&1 1>&3 | grep bar 1>&2 ; } 3>&1 | grep foo
同样的事情,但悄悄地,使用tee
;输出看起来grep
根本没有使用:
{ baz 2>&1 1>&3 | tee /dev/stderr | grep -q bar ; } 3>&1 | \
{ tee /dev/stderr | grep -q foo ; } 2>&1
现在添加一个条件 after grep -q bar
,打印“BEEP!”到标准错误如果酒吧发现:
{ baz 2>&1 1>&3 | tee /dev/stderr | grep -q bar && echo "BEEP!" >&2 ; } 3>&1 | \
{ tee /dev/stderr | grep -q foo ; } 2>&1
最后两行的输出:
foo
bar
BEEP!