我正在尝试下载一堆网页,一旦我下载了 N 行 html,我希望整个事情停止。但相反,管道中之前的步骤仍然继续进行。一个例子来看看问题:
for i in /accessories /aches-pains /allergy-hayfever /baby-child /beauty-skincare; do echo $i; sleep 2; done | \
while read -r line; do curl "https://www.medino.com$line"; done \
| head -n 2
现在,我希望它发出一个请求,然后中止。
但实际发生的情况是这样的:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0<!DOCTYPE html>
<html lang="en" >
100 4412 0 4412 0 0 12788 0 --:--:-- --:--:-- --:--:-- 12751
curl: (23) Failed writing body (0 != 2358)
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2358 0 2358 0 0 3772 0 --:--:-- --:--:-- --:--:-- 3766
curl: (23) Failed writing body (0 != 2358)
( ^ repeats 4 times)
为什么脚本不立即中止,而是继续运行?我不是管道方面的超级专家,所以感觉我在这里错过了一些基本的东西。
答案1
管道的第二部分是while read -r line; do curl ...$line; done
。当它运行时:
在第一次迭代中,shell 将第一个值读取到行中,并运行curl; curl(获取并)输出网页,其中
head -n2
提取前两行并退出,关闭第二部分和第三部分之间的管道。在您的示例中,curl 将此输出写入至少两个块,因此它在第二次写入时出现错误并失败,即以非零状态退出。当一个命令失败时,shell 不会终止大多数命令序列(包括复合命令),因为 shell 经常以交互方式使用,如果每次执行任何命令时 shell 都死掉,迫使您重新登录并重新开始,那将是非常不方便的。运行任何程序时出错。
因此,shell 将第二个值读入 line 并运行第二个curl,这会立即失败,因为管道已关闭,但 shell 再次继续读取第三行并运行第三个curl,依此类推,直到输入结束导致
read
失败;由于read
位于 的 list-1 部分while
,因此它的失败会导致循环终止。
您可以使用以下命令显式测试curl是否失败(然后终止):
generate_values | while read -n line && curl ...$line; do :; done | head -n2
或者你可以设置一个 shell 选项,这样它做失败时终止:
generate_values | { set -e; while read -n line; do curl ...$line; done } | head -n2
注意这两种方法都可能运行一结束,因为curl仅在写入时报告错误后管道被关闭,即在最后一个块之后。如果您的输出限制 ( head -n$n
) 在curl #2 的最后一个输出块期间耗尽,则该curl 将“成功”退出,并且shell 将启动curl #3,该curl #3 将在第一次(或唯一一次)写入时失败。