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

Hak*_*aba 3 linux bash shell subprocess pipe

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

$ bash -c "sleep 5 | false"  &  wait $!
[1] 46950
[1]+  Exit 1                  bash -c "sleep 5 | false"
$ echo $?
1
Run Code Online (Sandbox Code Playgroud)

这有效,并在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
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?


我正在使用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在他的回答中指出了这一点.添加花括号会使wait之前的后台进程等待.例如:

{ bash -c "sleep 5 | false"  &  wait $! ; } | true
Run Code Online (Sandbox Code Playgroud)

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

ran*_*mir 10

这里有两个要点:

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

所以,当你说:

cmd & wait $!
Run Code Online (Sandbox Code Playgroud)

然后cmd在你的当前shell中运行,在后台运行,并且wait(作为shell的内置函数)可以等待cmdPID,因为它cmd是该shell的子代(因此也是它的子代wait).

另一方面,当你说:

cmd & wait $! | cmd2
Run Code Online (Sandbox Code Playgroud)

那么cmd仍然是在当前的shell中运行,管道引起一个新的子shell wait(新bash工艺),其中cmd不是它的孩子,并且wait 可以在它的兄弟迫不及待(其父母的孩子).

作为外壳的语法的进一步澄清-的&运营商(沿;,&&||)分离管道,形成列表.因此,列表是一系列管道,而管道是由一系列命令分隔的|.

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

cmd & { wait $! | cmd2; }
Run Code Online (Sandbox Code Playgroud)

不是这个:

{ cmd & wait $! ; } | cmd2
Run Code Online (Sandbox Code Playgroud)

这相当于你的预期.