我理解这些:
true; echo "$?" # 0
false; echo "$?" # 1
true | echo "$?" # 0
但不是这个:
false | echo "$?" # 0
...为什么不打印1
?
我怎样才能强制管道发生故障,然后再恢复呢1
?
答案1
两者的结果true | echo "$?"
都false | echo "$?"
具有误导性。的内容"$?"
将被设置前将命令通过管道传输false
到命令echo
。
为了执行这些行,bash 设置了一个命令管道。管道已设置,然后命令并行启动。所以在你的例子中:
true; echo "$?" # 0
false; echo "$?" # 1
true | echo "$?" # 0
是相同的:
true
echo "$?" # 0
false
echo "$?" # 1
echo "$?" # 0
true
echo $?
在同时执行之前不会执行。
答案2
在 中false | echo $?
,$?
不是退出状态,false
因为$?
扩展到小数出口最近的状态管道[1],不是最新的命令,子外壳或者子进程。在 中false | echo $?
,false
不是管道,而只是管道的一部分。一个简单的完全的命令就像false;
是一个管道,即使不包含任何|
.
假设set -o pipefail
打开并且 的退出状态false | echo $?
将为 的退出状态false
,$?
则也无法退出当前管道的状态,因为当前管道在回显时尚未退出。
管道两侧的顺序并不重要(总是并行运行) 启动或终止,或者是否echo $?
实际在子进程或子 shell 中运行。
FWIW,当在子 shell 中时,变量和其他参数是总是在当前子 shell 的上下文中扩展:如果false | echo $?
是通过为管道两侧分叉单独的进程来实现的(bash 中是这种情况,但不是在所有 shell 中),$?
将在子进程中扩展,后,fork()
并且可能在管道的左侧退出之后[2]。
我怎样才能强制管道发生故障,然后得到 1 呢?
您使用set -o pipefail
,它在 bash、zsh 和 ksh 中受支持,并且应该包含在未来版本标准的。但如上所述,这只会影响$?
后管道已退出,但不在管道本身内。
[1]2.5.2 特殊参数在 SUSv4 标准中。在这种情况下,命令替换是否被视为管道取决于 shell;在大多数历史和现在的 shell 中:; echo `exit 13` $?
都会打印13
,但在某些(如 dash、yash 或 pdksh 派生的)中它将打印0
。
[2] 一个可能有助于理解的简单 bash 示例echo $BASHPID >&2 | echo $BASHPID >&2 | echo $BASHPID >&2
;变量BASHPID
是不是在 3 个子进程设置和运行之前展开。像这样的特殊参数$?
在这方面并不特殊;它们像任何其他变量一样被扩展。
答案3
这是因为管道的两个部分都是并行运行的,因此当命令已经开始打印时,false
命令尚未完成(并设置$?
),这仍然是echo
$?
以前的命令;在你的情况下,很可能是最后echo
执行的(并且可能是成功的)。