【发布时间】:2017-07-26 12:23:23
【问题描述】:
我试图弄清楚如何对标准 UNIX shell 管道执行最懒惰的处理。例如,假设我有一个命令执行一些计算并一路输出,但计算变得越来越昂贵,因此前几行输出很快到达,但随后的行变得更慢。如果我只对前几行感兴趣,那么我想通过lazy evaluation 获得那些,在它们变得太昂贵之前尽快终止计算。
这可以通过直接的 shell 管道来实现,例如:
./expensive | head -n 2
但是,这不能以最佳方式工作。让我们用一个指数级变慢的脚本来模拟计算:
#!/bin/sh
i=1
while true; do
echo line $i
sleep $(( i ** 4 ))
i=$(( i+1 ))
done
现在,当我通过 head -n 2 管道传输此脚本时,我观察到以下内容:
-
line 1是输出。 - 休眠一秒后,输出
line 2。 - 尽管
head -n 2已经收到两条 (\n-terminated) 行并退出,expensive继续运行,现在在完成之前再等待 16 秒 (2 ** 4),此时管道也完成.
显然这并不像期望的那样懒惰,因为理想情况下expensive 将在head 进程收到两行后立即终止。但是,这不会发生; IIUC 它实际上在尝试写入其 third 行后终止,因为此时它尝试写入通过管道连接到 STDIN 的 head 进程的 STDOUT,该进程已经退出,因此不再从管道读取输入。这会导致expensive 接收SIGPIPE,从而导致运行脚本的bash 解释器调用其SIGPIPE 处理程序,该处理程序默认终止运行脚本(尽管可以通过trap 命令更改)。
所以问题是,我怎样才能让expensive 在head 退出时立即 退出,而不仅仅是在expensive 尝试将其第三行写入没有的管道时另一端还有听众吗?由于管道是由交互式 shell 进程构建和管理的,因此我在其中键入了 ./expensive | head -n 2 命令,推测交互式 shell 是解决此问题的任何地方,而不是对 expensive 或 head 的任何修改?是否有任何本机技巧或额外实用程序可以构建具有我想要的行为的管道?或者也许根本不可能在bash 或zsh 中实现我想要的,唯一的方法是编写我自己的管道管理器(例如在Ruby 或Python 中),它会在阅读器终止并立即终止编写器时发现?
【问题讨论】:
-
旁白:理想情况下,
.sh扩展应该用于 shell libraries,可以在任何符合 POSIX 的 shell 中获取(.bash用于仅与 bash 兼容的库,.zsh用于与 zsh 兼容的库)。每当将可执行命令重写为不同的语言时,对可执行命令使用扩展都会带来麻烦——现在你需要更新每个调用者以调用不同名称的命令,或者你有一个误导性的名称——而且在任何情况下调用都是误导性的一个带有bashshebang 的脚本,其名称暗示sh可以调用它。 -
好点,谢谢!我猜这个评论来自
$(( ))是bash-specific 并且不符合 POSIX 的事实? -
实际上,
$(( ))是 POSIX 兼容的——它只使用(( ))进入算术上下文而不进行替换,这是一个 bashism——但是 @ 987654358@shebang 表示您将获得两个不同的解释器(或以不同模式运行的解释器),具体取决于调用。 -
啊,明白了 - 谢谢!我已经从问题中删除了
.sh后缀。