bash getting background process id gives parent pid

Viewed 938

Creating a bash script with this command:

cat <<"END"> z
#! /bin/bash

sleep 20 && exit 1 &
ret=$!

ps $ret | grep $ret
END

and then running it gives:

7230 pts/39   S+     0:00 /bin/bash ./z

I was expecting to see sleep 20 ... which is the child process. If I remove the && exit 1 it does return the child process.

Whats the reason? How can I get the child process id in above statement?

3 Answers

What you see is not parent pid, but sub-shell pid

When you run :

sleep 20 && exit 1 &

The processes tree is like :

current-shell ---> sub-shell ---> 'sleep 20 && exit 1'

When you run :

sleep 20 &

The processes tree is like :

current-shell ---> 'sleep 20'

Reason why you see pid for 'sleep 20'

Whats the reason?

The reason is that some entity has to do &&. It can't be sleep, because sleep only sleeps, and after sleep terminates (so there is no longer sleep to make any decision), some "entity" needs to compare the exit status of sleep and decide and then execute exit 1. That "entity" is the shell, that has to be "above" sleep to do the action. So the "real" background process is the shell, and sleep is it's child process.

In case of only sleep 20 & there is an optimization in bash that the parent shell in case bash sees there is only a single command to do. So bash scans the whole command command bla bla & and sees there is only one command to do. Because of that bash does only call to exec instead of the standard fork+exec and becomes sleep itself instead of running a child process. Because of the exec the subshell becomes sleep, so you see it in process name. It's a resource optimization done bash.

You already get the right information about the child process. Only in your case, ps doesn't know or want to show a proper COMMAND name for your chained sub-process you start in the background - what probably confused you.

Looks like this is the case with the chained commands (.. && ..., thus it has nothing to do with exit 1 could be also echo 5 etc.) where the process group leader name is showed as cmd name instead.

From the (ps man page)

`cmd | COMMAND`: simple name of executable

# Process state codes
`S`: interruptible sleep (waiting for an event to complete)
`+`: is in the foreground process group

See the S+ in your ps | grep output.

So, you can adapt your script a bit to confirm that you actually capture(d) the right information about the child process, like so:

cat <<"END"> z
#! /bin/bash

sleep 20 && exit 1 &
ret=$!

echo $ret

jobs -l

# display parent and child process info
# -j Jobs format
ps -j  $$ $ret 
END

Output of echo $ret:

30274

Output of jobs -l:

[1]+ 30274 Running                 sleep 20 && exit 1 &

Output of ps -j $$ $ret:

PID   PGID   SID   TTY    STAT    TIME COMMAND
30273 30273 21804 pts/0    S+     0:00 /bin/bash ./z
30274 30273 21804 pts/0    S+     0:00 /bin/bash ./z

Note that both the parent and child have the same PGID, whereas the pid 30274 of the child process displayed by jobs -l and ps ... matches.

Further, if you change sleep 20 && exit 1 & as bash -c 'sleep 20 && exit 1' & you would get a proper command name for the child this time, as follows (cf. output order above):

30384

[1]+ 30384 Running                 bash -c 'sleep 20 && exit 1' &

PID    PGID  SID   TTY    STAT    TIME COMMAND
30383 30383 21804 pts/0    S+     0:00 /bin/bash ./z
30384 30383 21804 pts/0    S+     0:00 bash -c sleep 20 && exit 1

Last but not least, in your original version instead of ps $ret | grep $ret you could also try

pstree -s $ret

From pstree man page

-s: Show parent processes of the specified process.

Which will provide you with an output similar to that one below, which would also confirm that you get the right process info for sleep 20 && exit 1 &:

systemd───systemd───gnome-terminal-───bash───bash───sleep
Related