在这个例子中:
$ for i in {1..3}; do sleep 1; echo $i; done | head -n 2
为什么第一个命令(for 循环)仅在3
显示之前被终止?我预计它会在2
显示后立即被杀死。
我最初试图解决的问题是:
$ for i in *(/); do c=`find "$i" -iname "*.ext" | wc -l`; echo "$c $i"; done | head -n 3
我预计它会在屏幕上显示三行后立即结束,但它运行了循环四次,丢弃了第四行。
我以为会发生什么:
sleep 1
echo 1
sleep 1
echo 2
# 2 is printed and the for loop is killed
实际发生的情况:
sleep 1
echo 1
sleep 1
echo 2
sleep 1
echo 3
# 3 is not printed and the for loop is killed
有没有办法在给定的迭代次数后立即中止循环,而不在循环本身内部添加一些逻辑?
答案1
在我的不同 shell(ksh、zsh、bash)上,我有预期的行为:我只有 1、2,而不是 3。这里的问题可能与您的 ZSH 版本上如何处理 SIGPIPE 有关。
您可以尝试使用“无环境”实例运行这些命令吗?例如,没有别名、配置等...
正如注释中所说,当 shell 想要回显某些内容(使用 write() 函数)时,会收到错误。只要整个管道链正在运行(进程head
仍在运行并且 STDIN 打开),一切都会顺利。但是当head
终止时,shell 的输出将无处可去(管道损坏)。
但是,直到 shell 尝试在其标准输出中写入某些内容之前,他并不知道管道已损坏并继续进行处理。
因此,您将在调试中看到“echo 3”,这将失败并终止 shell。
答案2
仅当程序尝试写入关闭的管道时才会发送 SIGPIPE。否则发送 SIGPIPE 是非常糟糕的:碰巧打开管道的程序甚至可能不知道它,并且如果它们从不与管道交互,则不应该被杀死。
在 中for i in 1 2 3; do sleep 1; echo $i; done
,该sleep
命令不写入任何内容,因此它不会接收信号。仅当运行时才会echo
发送 SIGPIPE。
一般来说,SIGPIPE 不是为精密工作而设计的。它只是为了确保那些负责产生输出的程序不会在输出的消费者离开后一直运行。在大多数实际情况下,管道左侧的程序会缓冲写入,并且它只会在下次刷新缓冲区时收到 SIGPIPE。
如果您想在多次迭代后停止循环,请将该逻辑构建到循环中。