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

Wyz*_*ard 7 grep bash pipe io-redirection

我正在使用一个程序,该程序在出现问题时输出错误消息,但没有相应地设置其退出状态:退出状态始终为 0,表示成功。我想从一个 shell 脚本运行这个程序,如果它发出任何错误消息,就得到一个非零的退出状态,这样脚本就可以知道程序失败了。

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

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

我发现有效的一种方法是:

! my_program | tee /dev/stderr | grep -q "error message"
Run Code Online (Sandbox Code Playgroud)

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

使用bash或 POSIX shell 和 GNU 工具,是否有一种(简洁的)方法来扫描程序的标准输出和/或标准错误流,grep并相应地设置退出状态,但同时也让两个流都到达它们的正常目的地?

(请注意,my_program将其错误消息写入标准输出,而不是标准错误,因此只能扫描标准输出流的解决方案是可以的,但并不理想。如果它有所不同,我将在 CentOS 7 上执行此操作。 )

Wil*_*ard 6

...是否有一种(简洁的)方法可以使用 grep 之类的东西扫描程序的标准输出和/或标准错误流并相应地设置退出状态,但同时也让两个流都到达它们的正常目的地?

...my_program 将其错误消息写入标准输出,而不是标准错误,因此只能扫描标准输出流的解决方案是可以的,但并不理想。

我的解决方案回答了上面的粗体部分。

我认为最简单的方法是通过awk

myprogram |
awk 'BEGIN {status = 0} /error message/ {status = 1} 1; END {exit(status)}'
Run Code Online (Sandbox Code Playgroud)

awk命令完全按原样输出它输入的所有内容,但最后它退出时的状态取决于“错误消息”是否是输入的一部分。

简明版本(具有较短的变量名称):

myprogram | awk 'BEGIN{s=0} /error message/{s=1} 1; END{exit(s)}'
Run Code Online (Sandbox Code Playgroud)


Mat*_*vid 5

以下是一些$MSG在 stdout/stderr 上grep 错误消息的衬垫,并且在出现错误消息时不要/不要立即停止程序:

# 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
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 对于所有人:您申请的流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')
    
    Run Code Online (Sandbox Code Playgroud)
  • 对于 stderr 的 grepping:上面的简单命令行假设文件描述符 3 未使用。这通常是一个可以做出的假设。为了避免这种假设,你可以让 Bash 分配一个保证不被使用的文件描述符:

    bash -c 'exec {fd}>&1; { my_program 2>&1 >&${fd} | awk -v s="$MSG" '\''$0~s{exit(1)} 1'\'' >&2; } ${fd}>&1'
    
    Run Code Online (Sandbox Code Playgroud)


小智 0

尝试并排两个终端。在第一个 shell 窗口中:

tail -F /tmp/xyzzy
Run Code Online (Sandbox Code Playgroud)

在第二个中,正是您所做的,仅对 tmp 文件:

my_program | tee /tmp/xyzzy | grep -q "error message"
Run Code Online (Sandbox Code Playgroud)

按该顺序启动它们。

这很笨拙,因为您需要经常清除临时文件,或者选择一个新名称,但它可以工作。

稍后添加...尝试如下:

my_program | tee /tmp/xyzzy ; grep -q "error message" /tmp/xyzzy
Run Code Online (Sandbox Code Playgroud)

序列的退出状态是 grep 的退出状态。这与你想要的相反,叹息。所以否定它。

my_program | tee /tmp/xyzzy ; ! grep -q "error message" /tmp/xyzzy
Run Code Online (Sandbox Code Playgroud)