具体用例:
我正在尝试使用curl
的 URL -v
,像平常一样将详细调试信息输出到 stderr,像平常一样将响应正文输出到 stdout,而且还要检查 的响应中是否存在正则表达式grep
,如果没有找到,则退出非零值。该命令最简单的形式是
curl -v "${url}" | grep -q "${pattern}"
但grep
会消耗响应主体。
我尝试过的:
我看过“说服 grep 输出所有行,而不仅仅是那些匹配的行”,这会建议:
curl -v "${url}" | grep --color -E "${pattern}|$"
但这只是颜色突出显示匹配,如果
pattern
不存在,它不会为您提供非零退出代码。我也看过“如何获取 grep 退出代码但打印所有行?”,这表明:
curl -v "${url}" | tee /dev/stderr | grep -q "${pattern}"
它以退出代码退出
grep
并输出响应正文;但它将响应正文输出到 stderr,并且我想保持curl
stderr 和 stdout 流独立且完整。下列的 ”直接输出到管道和标准输出“, 我试过
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>&1
1(标准输出)复制到文件描述符 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 选项-p
已tee
添加到版本 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 支持不同的正则表达式,因此您可能需要更改模式。