如果之后使用管道,为什么等待生成“<pid>不是此shell的子代”错误?

时间:2017-10-15 07:38:16

标签: linux bash shell subprocess pipe

在下面我创建一个后台进程并等待它完成。

$ bash -c "sleep 5 | false"  &  wait $!
[1] 46950
[1]+  Exit 1                  bash -c "sleep 5 | false"
$ echo $?
1

这样可以在5秒后返回提示。

但是,wait如果我之后再使用一个管道,则会返回错误。

$ bash -c "sleep 5 | false"  &  wait $!  | true
[1] 49493
-bash: wait: pid 49493 is not a child of this shell
hbaba@mbp-005063:~/misc$ echo $?
0
hbaba@mbp-005063:~/misc$ ps -T -f
  UID   PID  PPID   C STIME   TTY           TIME CMD
980771313 49493 69771   0 12:56AM ttys056    0:00.00 bash -c sleep 5 | false
980771313 49498 49493   0 12:56AM ttys056    0:00.00 sleep 5
    0 49555 69771   0 12:56AM ttys056    0:00.01 ps -T -f

这里发生了什么?

我使用的是bash版本GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15)

我每次都可以重现等待错误。 我认为它与每个管道是一个单独的子shell有关。 https://unix.stackexchange.com/a/127346/212862

也许等待$! command在错误的shell中查找子进程。 错误消息提到49493 pid。这确实是bash -c …命令的正确pid。 ps -T表明了这一点。

有相关问题q1q2。但是在wait内置后,没有管道使用。

更新

我对&|之间的bash中的运算符优先级存在误解。 @randomir在他的answer中指出了这一点。添加花括号使wait等待先前的后台进程。例如:

{ bash -c "sleep 5 | false"  &  wait $! ; } | true

这不会返回相同的等待错误。

2 个答案:

答案 0 :(得分:8)

这里有两个要点:

  • wait(内置shell)只能等待(shell的)子项
  • 管道中的每个命令都在单独的子shell中运行

所以,当你说:

cmd & wait $!

然后cmd在当前shell中运行,在后台运行,wait(作为shell的内置)可以等待cmd的PID,因为{{1} }是该shell的子级(因此是cmd的子级)。

另一方面,当你说:

wait

然后cmd & wait $! | cmd2 仍在您当前的shell中运行,但是管道会为cmd(一个新的wait进程)引入一个新的子shell,其中{ {1}} 不是它的孩子bash 不能等待其兄弟(其父级的子女)。

作为shell语法的另一个澄清 - cmd运算符(以及wait&;)分隔管道,形成列表。因此,列表管道的序列,而管道是由&&分隔的命令序列。

这意味着上面的最后一个例子相当于:

||

到此:

|

这相当于你所期望的。

答案 1 :(得分:0)

Bash手册:

  

管道中的每个命令都作为一个单独的进程执行(即,在   一个子壳)。

您可以通过以下方式查看:

$ echo "me1: $BASHPID"
me1: 34438
$ sleep 100 &
[1] 34989
$ echo "me2: $BASHPID, child: $!"
me2: 34438, child: 34989
$ true | echo "me3: $BASHPID, child: $!"
me3: 34991, child: 34989

第一个和第二个echome1me2)在顶层shell(34438)的上下文中执行。 sleep子进程(34989)是其子级之一。第三个echome3)在子shell(34991)的上下文中执行。 sleep进程不再是其子女之一。