Shell – Why $! Returns Wrong Process ID for Background Subshell

processshellsubshell

$(sleep 5) &
echo $!
sleep 1
echo "done"

The above outputs:

$ ./test.sh
7483
done

However if I search for sleep in ps aux I see:

 ps aux | rg sleep
chris     7484  0.0  0.0 132788  1432 pts/6    S+   12:10   0:00 sleep 1
chris     7485  0.0  0.0 132788  1432 pts/6    S+   12:10   0:00 sleep 5
chris     7519  0.0  0.0  10376  5744 pts/7    S+   12:10   0:00 rg --no-ignore-vcs sleep

The sleep 5 process is 7485 and not 7483 as output from the script. What is the cause of this behavior?

Best Answer

$(sleep 5)

runs a shell which runs sleep. The pid given by $! is the shell’s.

You don’t need the substitution here,

 sleep 5 &

would give you sleep’s pid.

I imagine sleep in your example replaces something else, but in any case a background substitution is probably unnecessary. Using only a subshell,

(sleep 5) &

will typically give you sleep’s pid, because many shells replace themselves with the child command when they run the last command in a subshell, which effectively reuses the subshell’s pid for sleep; but

(sleep 5; sleep 5) &

will give you the shell’s pid, not sleep’s.

Related Question