ans*_*wer 1 bash pipe shell-script
下面的代码是我在 Linux 机器上运行的一个简单的 bash 脚本,我想知道为什么每个输出之间的时间间隔是四 秒而不是八秒?
$ for test in test1 test2 test3; do (echo ${test}; sleep 4s; echo hop2; sleep 4s; echo hop3) | date; done
Sun 11 Apr 2021 12:42:27 AM +07
Sun 11 Apr 2021 12:42:31 AM +07
Sun 11 Apr 2021 12:42:35 AM +07
Run Code Online (Sandbox Code Playgroud)
尽管将后面的时间值增加了一些,但每个输出之间的时间间隔仍然是四秒。
$ for test in test1 test2 test3; do (echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3) | date; done
Sun 11 Apr 2021 12:42:44 AM +07
Sun 11 Apr 2021 12:42:48 AM +07
Sun 11 Apr 2021 12:42:52 AM +07
Run Code Online (Sandbox Code Playgroud)
这非常令人困惑,如果有人能解释这一点,我将不胜感激。
更令人困惑的是,如果我将date
命令放在前面,看起来没有执行任何睡眠命令:
$ for test in test1 test2 test3; do (date; echo ${test}; sleep 50s; echo hop2; sleep 50s; echo hop3) | date; done
Sun 11 Apr 2021 01:22:35 AM +07
Sun 11 Apr 2021 01:22:35 AM +07
Sun 11 Apr 2021 01:22:35 AM +07
Run Code Online (Sandbox Code Playgroud)
Gor*_*son 17
为了澄清这一点,让我在回显“hop2”之前和之后向 stderr 添加一些调试输出(绕过管道):
$ for test in test1 test2 test3; do
(echo ${test}; sleep 4s; echo before hop2 >&2;
echo hop2; echo after hop2 >&2; sleep 4s; echo hop3) | date;
done
Sat Apr 10 11:29:46 PDT 2021
before hop2
Sat Apr 10 11:29:50 PDT 2021
before hop2
Sat Apr 10 11:29:54 PDT 2021
before hop2
Run Code Online (Sandbox Code Playgroud)
请注意,echo after hop2 >&2
永远不会执行,它之后的命令也不会执行:第二个sleep
和echo hop3
.
据我了解,这就是发生的事情。在循环中,两个独立的进程并行执行,第一个进程的输出通过管道传输到第二个进程的输入。两个进程执行:
echo ${test}
sleep 4s
echo before hop2 >&2
echo hop2
echo after hop2 >&2
sleep 4s
echo hop3
Run Code Online (Sandbox Code Playgroud)
和
date
Run Code Online (Sandbox Code Playgroud)
这是执行的粗略顺序(前 3 个步骤的确切顺序和第 4 步的开始将有些随机):
echo ${test}
;这会将“test1”(和一个换行符)写入管道,在管道中对其进行缓冲,以便稍后读取。date
,将当前日期打印到终端。sleep 4s
。echo before hop2 >&2
,将“before hop2”打印到终端。echo hop2
,但由于管道的唯一读取器已关闭它,因此它会收到 SIGPIPE 错误。这显然会导致整个子 shell 进程(不仅仅是echo
命令)退出。请注意,发生这种情况只是因为echo
它是内置的 shell;如果您使用/bin/echo hop2
(外部命令,而不是 shell 的echo
内置命令),它将sleep
按照您的预期执行第二个命令。
顺便说一句,这在不同的外壳之间是相对一致的。在 bash、zsh、dash 和 ksh(交互式)中运行它时,我得到了相同的结果。脚本中的 ksh 有点不同,因为它显然在继续之前不等待进程 1 退出,所以date
s 都会立即执行,然后(4 秒后)是一系列“before hop2”行。
(echo ${test}; ...) | date
Run Code Online (Sandbox Code Playgroud)
你想在这里做什么?您正在将数据传送到 的标准输入date
,但date
不读取任何输入。它只是打印日期并退出。
后date
退出,管道关闭,之后打印到任何数据无处可去的过程写管道,子shell (echo; sleep; echo; sleep)
,被发送的信号SIGPIPE和死去。
这就是管道的一般工作方式。如果左侧是可以产生大量输出的东西,甚至可能是任意数量的输出,那么信号就是告诉它在右侧失去兴趣后停止的东西。
例如,在类似的情况下cat /dev/zero | head -c128 > /dev/null
,信号最终会杀死 ,cat
因此它不会无限期地留下。shell 和 the 都不会cat
为此打印错误消息。管道像这样工作只是正常操作的一部分。手头的情况也是如此。(在某些情况下,您确实会收到一条错误消息,只是不要期望总是为此收到一条。)
(外循环不影响结果,所以我放弃了。你可以用它time ( ... ) | ...
来衡量管道花费的时间。)
在 Bash 中,您可以从PIPESTATUS
数组中检查管道中命令的退出状态:
$ ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
date; declare -p PIPESTATUS
Sat Apr 10 21:34:56 EEST 2021
declare -a PIPESTATUS=([0]="141" [1]="0")
Run Code Online (Sandbox Code Playgroud)
SIGPIPE 至少在 Linux 上是编号 13,因此它与显示的退出状态 141 = 128 + 信号编号相匹配。(进程也可以以状态 >= 128 正常退出,但这里不是这种情况。)
或者,你可以strace
看看会发生什么:
$ strace -f bash -c '( echo ${test}; sleep 4s; echo hop2;
sleep 50s; echo hop3; ) | date'
...
[pid 31647] write(1, "hop2\n", 5) = -1 EPIPE (Broken pipe)
[pid 31647] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=31647, si_uid=1000} ---
[pid 31647] +++ killed by SIGPIPE +++
...
Run Code Online (Sandbox Code Playgroud)
另一方面,如果有东西读取管道右侧的输入,则不会有 SIGPIPE,整个管道将总共休眠 54 秒:
$ time ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
cat > /dev/null
real 0m54.005s
user 0m0.000s
sys 0m0.000s
Run Code Online (Sandbox Code Playgroud)
或者,如果您忽略写入端的 SIGPIPE:
$ time ( trap '' PIPE; echo foo; sleep 4s; echo hop2; sleep 10s;
echo hop3; ) | date
Sat Apr 10 21:57:07 EEST 2021
bash: echo: write error: Broken pipe
bash: echo: write error: Broken pipe
real 0m14.006s
user 0m0.004s
sys 0m0.000s
Run Code Online (Sandbox Code Playgroud)
在忽略 SIGPIPE 的情况下,shell 在写入关闭的管道时会得到一个常规的错误返回,并且它会为此打印一个错误。
请注意,管道左侧写入管道和右侧关闭管道之间也存在计时问题。
如果我做这样的事情:
( echo foo; echo bar; sleep 4s; ) | date;
Run Code Online (Sandbox Code Playgroud)
在sleep
不运行。但是,如果我更换两节echo
的有for i in {1..100}; do echo foo; done;
,得到的前LHS模具sleep
。(这里的比赛将取决于系统。)
并将与另一个案例date
作为 LHS 上的第一件事:
(date; echo ${test}; sleep 50s; echo hop2; sleep 50s; echo hop3) | date
Run Code Online (Sandbox Code Playgroud)
这也是由于时间问题。由于date
是外部命令,shell 启动它可能比内部处理更慢echo
(毕竟,它是所有 shell 旁边的内置命令)。这使得date
右侧的更有可能赢得比赛并在第一个echo
写入之前关闭管道。
通常,如果您的意图不是将某些数据传递到另一端,那么写入管道并不是很有用,并且如果您有其他事情要做,请确保处理可能的 SIGPIPE。