如何通过管道将命令发送到 grep 并获取退出状态,同时仍将命令输出发送到 stdout 并将错误发送到 stderr?

如何通过管道将命令发送到 grep 并获取退出状态,同时仍将命令输出发送到 stdout 并将错误发送到 stderr?

具体用例:

我正在尝试使用curl的 URL -v,像平常一样将详细调试信息输出到 stderr,像平常一样将响应正文输出到 stdout,而且还要检查 的响应中是否存在正则表达式grep,如果没有找到,则退出非零值。该命令最简单的形式是

curl -v "${url}" | grep -q "${pattern}"

grep会消耗响应主体。

我尝试过的:

  1. 我看过“说服 grep 输出所有行,而不仅仅是那些匹配的行”,这会建议:

    curl -v "${url}" | grep --color -E "${pattern}|$"
    

    但这只是颜色突出显示匹配,如果pattern不存在,它不会为您提供非零退出代码。

  2. 我也看过“如何获取 grep 退出代码但打印所有行?”,这表明:

    curl -v "${url}" | tee /dev/stderr | grep -q "${pattern}"
    

    它以退出代码退出grep并输出响应正文;但它将响应正文输出到 stderr,并且我想保持curlstderr 和 stdout 流独立且完整。

  3. 下列的 ”直接输出到管道和标准输出“, 我试过

    curl -v "${url}" | tee >(grep -q "${pattern}")
    

    但是,虽然这会输出所有内容,并正确分离 stdout 和 stderr 流,但它会丢弃grep退出代码。

目前的解决方法:

到目前为止,我唯一能想到的就是将响应正文粘贴到临时文件中:

curl -v "${url}" | tee /tmp/response
grep -q "${pattern}" /tmp/response

这让我得到正确的输出和正确的退出代码。

但肯定有一种方法可以在没有临时文件的情况下将其作为单个管道来执行此操作吗?

答案1

你的第二个答案是在正确的轨道上。尝试

{ curl -v "${url}" | tee /dev/fd/3 | grep -q "${pattern}";} 3>&1

简要说明:

  • 将文件描述符3>&11(标准输出)复制到文件描述符 3(没有标准用法的最低描述符)上。这是指整个管道(即整个命令行)的标准输出。新的文件描述符 (3) 对整个管道有效。

    您可以使用任何数字 — 至少是 9 以内的任何数字。 POSIX Shell 命令语言规格, 2.7 重定向,说

    …所有实现应至少支持 0 到 9 …

    暗示符合 POSIX 标准的 shell 可能无法识别大于 9 的数字。并且 bash(1)

    使用大于 9 的文件描述符的重定向应谨慎使用……

  • tee /dev/fd/3告诉tee写入文件描述符 3 — 因此它连接tee到管道的标准输出。

    • 您必须在此处使用与指令中使用的相同的数字n>&1
    • 类似的文件名 可能不适用于非 Linux 操作系统。/dev/fd/n

    请注意,这与标准输出无关tee 这是通往 的管道 grep

斯蒂芬·查泽拉斯指出

grep -q当找到模式时退出,tee如果之后写入输出,则会导致死亡。 GNU 实现 tee至少可以 -p 忽略 SIGPIPE 并继续写入仍可以获得输出的目标。另请注意,某些 shell 仅等待管道的最后一个组件,因此在这里,一旦grep -q找到模式,就会携带脚本的其余部分......

作为形式,我验证了第一点(如果模式匹配,管道可以提前终止)1

我提供这个增强的解决方案来解决这两个问题:

{ curl -v "${url}" | tee /dev/fd/3 | { grep -q "${pattern}" && cat > /dev/null;} } 3>&1

笔记:

  • 如果数据流没有匹配模式,然后grep将读取整个流(即其输入),因此不会出现问题。并且,在这种情况下,grep将“失败”,因此,由于 &&, 将不会运行,并且复合命令将从;cat返回退出代码。grep即,失败(即,不匹配)。
  • 如果数据流匹配模式,那么(正如 Stéphane 所指出的) grep将在匹配模式时退出,并且不读取整个输入(即 的输出curl | tee)。但在这种情况下,grep将会“成功”,因此,由于 &&, cat 将要运行,它将吸收其余的数据(直到 EOF)。这将确保tee能够将整个数据流写入管道,并且管道(即整个命令行)在 curl处理完所有输出之前不会终止。

从技术上讲,这仍然存在一个问题:如果数据流与模式匹配并且grep“成功”,那么管道的退出状态将是 的退出状态cat。我不知道为什么(pipe) | cat > /dev/null会失败,但理论上是可能的。为了防止这种情况,我提出:

{ curl -v "${url}" | tee /dev/fd/3 | if grep -q "${pattern}"; then cat > /dev/null; true; else false; fi; } 3>&1

如果成功则显式返回 true grep,如果失败则显式返回 false。
______________
1 选项-ptee添加到版本 8.24 (2015-07-03)


对于上述任何变体,如果您想要 管道来自其他地方的输出curl,只需在命令行末尾添加管道,就像通常一样:

{ curl …; fi; } 3>&1 | lpr

但是,如果你想重定向将其保存到文件中,您必须插入输出重定向这 3>&1

{ 卷曲……;菲; }>输出文件3>&1

回想一下,“通过管道传输到文件”是不正确的术语。

答案2

如果这里没有什么特别的grep,也许可以使用 awk 来进行模式匹配:

curl -v "${url}" | awk -v p="$pattern" '$0 ~ p {found=1} {print} END {exit ! found}'
  • -v p="$pattern"将 awk 变量设置p为 shell 变量的值pattern
  • $0 ~ p {found=1}found如果该行与p变量中的正则表达式匹配,则将 awk 变量设置为 1。
  • {print}- 打印所有行(因为该块没有给出条件)
  • END {exit ! found} - 在输入结束时,以负值状态退出found(因此如果模式匹配则为 0,否则为 1)。

awk 和 grep 支持不同的正则表达式,因此您可能需要更改模式。

相关内容