如何 grep 程序的输出并正常回显输出?

如何 grep 程序的输出并正常回显输出?

我正在使用一个程序,当出现问题时,该程序会输出错误消息,但不会相应地设置其退出状态:退出状态始终为 0,表示成功。我想从 shell 脚本运行这个程序,并在它发出任何错误消息时获得非零退出状态,以便脚本可以告诉程序失败。

错误消息遵循我可以与 匹配的可预测模式grep,并grep根据是否找到匹配来设置其退出状态,因此我可以将程序的输出通过管道传输到grep并否定结果以!获得我想要的退出状态。

问题是,如果我通过管道进入grep,我不能程序的输出不再存在,因为grep消耗了它。我想以某种方式扫描输出中的错误消息,但也可以正常显示输出,因为除了错误之外还有其他重要消息。不幸的是,grep没有选项可以传递所有输入、匹配和不匹配的行。

我发现的一种方法大多作品是:

! my_program | tee /dev/stderr | grep -q "error message"

这会将程序的输出输入到标准错误中grep,但也会将其复制到标准错误中,该错误不会被重定向,因此我可以看到它。当我的脚本的标准输出和标准错误都发送到终端时,这是可以的(因此哪一个收到消息并不重要),但如果标准输出和标准错误被重定向到不同的地方,则可能会导致问题;消息最终会出现在错误的位置。

使用bashPOSIX 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!

相关内容