我正在尝试通过管道传递输入流,并希望捕获下游管道程序可能失败的情况,在这种情况下我需要重新启动它。所以我把它放到一个循环中:
step1 |while true ; do step2 ; done
Run Code Online (Sandbox Code Playgroud)
但是如果上游管道关闭,那么我希望该循环退出。我无法从 step2 的退出状态中看出这一点,否则我可能会说
step1 |until [ $? -ne 0 ] ; do step2 ; done
Run Code Online (Sandbox Code Playgroud)
我需要一个测试来检查 stdin 是否已关闭但实际上并未消耗 stdin 中的字符。
我正在考虑如何在 C 中做到这一点。零缓冲区的 read(2) 是否允许我对此进行测试?我不认为它。选择(2)怎么样?不。或者是吗?fcntl(2) 怎么样?我找不到任何东西。
除了消耗一个字节然后以某种方式再次将其取出之外,真的没有其他方法吗?没有关闭文件描述符测试?
至少在 Linux 上,您可以通过在事件掩码中使用poll()
with来判断管道的另一端是否已关闭POLLHUP
。
但请注意,此时管道中可能仍有数据可供读取,因此您可能还需要检查这些数据。再次在 Linux 上,这可以通过FIONREAD
ioctl来完成。
所以你可以定义一个:
stdin_alive() {
perl -MIO::Poll -e '
require "sys/ioctl.ph";
-p STDIN or die "stdin is not a pipe\n";
$p = IO::Poll->new;
$p->mask(STDIN, POLLHUP);
if ($p->poll(0)) {
ioctl(STDIN, &FIONREAD, $n) or die "FIONREAD: $!\n";
$n = unpack "L", $n;
exit 1 unless $n;
}'
}
Run Code Online (Sandbox Code Playgroud)
并将其用作:
step1 | while stdin_alive; do step2; done
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用以下ifne
命令moreutils
:
step1 |
while
ifne sh -c 'step2 && exit 42'
[ "$?" -eq 42 ]
do
continue
done
Run Code Online (Sandbox Code Playgroud)
ifne
尝试读取其标准输入。如果至少读取了一个字节,则ifne
启动命令,其标准输入连接到新管道,并将它从标准输入读取的内容通过新管道铲到命令中,因此效率较低,因为有额外的铲除和传输通过一个额外的管道,但这意味着无论输入类型如何(不仅是管道)它都可以工作。
与之前的解决方案的另一个区别是ifne
在开始之前等待 stdin 上的输入step2
,而poll()
+FIONREAD
方法只是检查管道是否在检查的确切时间点处于活动状态,管道是否有数据。这可以通过添加POLLIN
到被轮询的事件列表来改变。