无法使用 set -eo pipefail 成功调用管道中的任何外部 shell 脚本失败



假设以下测试pipefail.sh批次:

#!/usr/bin/env bash
set -eo pipefail
./echoer.sh | head -n1 >/dev/null
echo "Might work with pipefail"
for i in {1..100} ; do
./echoer.sh | head -n1 >/dev/null
done
echo "Stable work with pipefail"

带有echoer.sh内容:

#!/usr/bin/env bash
echo 'head (GNU coreutils) 8.30'
echo 'GNU bash, version 5.0.16(1)-release (x86_64-pc-linux-gnu)'
exit 0

的预期结果/测试管道故障。sh

Might work with pipefail
Stable work with pipefail

实际行为:

Might work with pipefail

或者(随机地(没有输出。

如果我使用任何二进制实用程序而不是echoer.sh,管道中的Writer程序永远不会失败,但如果Writer是shell脚本(例如,glibc二进制包中的ldd(,它总是不起作用(导致pipefail脚本退出(。将测试pipefail.sh中的执行(./echoer.sh(替换为源(.echoer.sh加了成功执行的概率,即有时我会得到

Stable work with pipefail

测试pipefail.sh输出中。

头脑总是在这样的管道里获得成功。删除echoer.sh中的第二个echo可在单独的shell中成功执行源代码和执行代码。

让我们将问题简化为基本问题。考虑:

$ (set -o pipefail; cat /dev/zero | head -c10; declare -p PIPESTATUS)
declare -a PIPESTATUS=([0]="141" [1]="0")

发生的情况是,当head填充完后,它会完成,关闭管道。前面的命令,在本例中为cat,得到一个SIGPIPE(13(信号。因此,它将其退出代码设置为128+13=141以指示失败。

因此,问题是当第二个进程head结束时,第一个进程是否仍在运行。有时,你的echoer.shhead快,有时也慢。

由于我们同时处理两个进程,所以时间总是可变的。

采购与执行

将test-pipefail.sh中的执行(./echoer.sh(替换为源(. echoer.sh(会增加成功执行的概率

Sourcing消除了初始化新shell的需要,这可能会加快执行速度,从而提高在head之前完成的可能性。

二进制程序

如果我使用任何二进制实用程序,管道中的Writer程序永远不会失败而不是echoer.sh

我上面的cat示例显示了相反的情况。这是因为cat /dev/zero程序将永远不会结束,从而确保它最终将接收SIGPIPE。

我认为结果取决于编写器完成的速度。如果它很快完成,那么它就没有机会与SIGPIPE一起发送。

例如:

[STEP 119] # hexdump -n100 /dev/urandom | head -n1; echo '$?'=$?
0000000 eea2 36e7 24d8 15de 620c e258 f9d8 f138
$?=0
[STEP 120] # hexdump -n1000 /dev/urandom | head -n1; echo '$?'=$?
0000000 cf81 dd51 1594 88b2 c9c1 6c8a bbbd c80f
$?=0
[STEP 121] # hexdump -n1000 /dev/urandom | head -n1; echo '$?'=$?
0000000 ef2d b2d3 1024 af9f ee1e a5e6 5528 699e
$?=0
[STEP 122] # hexdump -n2000 /dev/urandom | head -n1; echo '$?'=$?
0000000 d9f7 6a0d 633b c1f7 8928 cef8 3ea9 6f5a
$?=141
[STEP 123] # hexdump -n2000 /dev/urandom | head -n1; echo '$?'=$?
0000000 c044 dbb0 c227 1836 9fb5 f03b b2d1 0605
$?=141
[STEP 124] #

最新更新