Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does wait generate “<pid> is not a child of this shell” error if a pipe is used afterwards?

In the following I create a background process and wait for it to complete.

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

This works and the prompt returns after 5 seconds.

However, wait returns an error if I use one more pipe after it.

$ 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

What is happening here?


I am using bash version GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15)

I can reproduce the wait error every time. I think it has something to do with each pipe being a separate subshell. https://unix.stackexchange.com/a/127346/212862

Maybe the wait $! command looks for the child process in the wrong shell. The error message mentions the 49493 pid. That is indeed the right pid for the bash -c … Command. The ps -T shows that.

There are relevant questions q1 and q2. But in them there is no pipe usage after the wait built-in.

Update

I had a misunderstanding about operator precedence in bash between the & and |. @randomir pointed that out in his answer. Adding curly braces makes the wait wait on the previously backgrounded process. For example:

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

This does not return the same wait error.

like image 341
Hakan Baba Avatar asked Oct 15 '17 07:10

Hakan Baba


1 Answers

There are two key points to observe here:

  • wait (a shell built-in) can wait only (the shell's) children
  • each command in a pipeline runs in a separate subshell

So, when you say:

cmd & wait $!

then cmd is run in your current shell, in background, and wait (being the shell's built-in) can wait on cmd's PID, since cmd is a child of that shell (and therefore a child of wait).

On the other hand, when you say:

cmd & wait $! | cmd2

then cmd is still run in your current shell, but the pipe induces a new subshell for wait (a new bash process) in which cmd is not its child, and wait can not wait on its sibling (a child of its parent).

As an additional clarification of shell's grammar - the & operator (along with ;, && and ||) separates pipelines, forming lists. So, a list is a sequence of pipelines, and a pipeline is a sequence of commands separated by |.

That means the last example above is equivalent to:

cmd & { wait $! | cmd2; }

and not to this:

{ cmd & wait $! ; } | cmd2

which is equivalent to what you have expected.

like image 198
randomir Avatar answered Oct 22 '22 11:10

randomir