linux command setsid
The source code of the setsid
utility is actually very straightforward. You'll note that it only fork()
s if it sees that its process ID and process-group ID are equal (i.e., if it sees that it's a process group leader) — and that it never wait()
s for its child process: if it fork()
s, then the parent process just returns immediately. If it doesn't fork()
, then it gives the appearance of wait()
ing for a child, but really what happens is just that it is the child, and it's Bash that's wait()
ing (just as it always does). (Of course, when it really does fork()
, Bash can't wait()
for the child it creates, because processes wait()
for their children, not their grandchildren.)
So the behavior that you're seeing is a direct consequence of a different behavior:
- when you run
. ./test.sh
orsource ./test.sh
or whatnot — or for that matter, when you just runsetsid
directly from the Bash prompt — Bash will launchsetsid
with a new process-group-ID for job control purposes, sosetsid
will have the same process-ID as its process-group-ID (that is, it's a process group leader), so it willfork()
and won'twait()
. - when you run
./test.sh
orbash test.sh
or whatnot and it launchessetsid
,setsid
will be part of the same process group as the script that's running it, so its process-ID and process-group-ID will be different, so it won'tfork()
, so it'll give the appearance of waiting (without actuallywait()
ing).
The behavior I observe is what I expect, though different from yours. Can you use set -x
to make sure you're seeing things right?
$ ./test.sh 1childparent$ . test.sh 1child$ uname -r3.1.10$ echo $BASH_VERSION4.2.20(1)-release
When running ./test.sh 1
, the script's parent — the interactive shell — is the session leader, so $$ != $SID
and the conditional is true.
When running . test.sh 1
, the interactive shell is executing the script in-process, and is its own session leader, so $$ == $SID
and the conditional is false, thus never executing the inner child script.
I do not see any problem with your script as is. I added extra statements in your code to see what is happening:
#!/bin/bash ps -H -o pid,ppid,sid,cmd echo '$$' is $$ SID=`ps -p $$ --no-headers -o sid` if [ $# -ge 1 -a $$ -ne $SID ] ; then setsid bash test.sh echo pid=$$ ppid=$PPID sid=$SID parent else sleep 2 echo pid=$$ ppid=$PPID sid=$SID child sleep 2 fi
The case that concerns you is:
./test.sh 1
And trust me run this modified script and you will see exactly what is happening. If the shell which is not a session leader runs the script then it simply goes to else
block. Am I missing something?
I see now what you mean: When you do ./test.sh 1
with your script as is then parent waits for the child to complete. child blocks the parent. But if you start the child in background then you will notice that parent completes before child. So just make this change in your script:
setsid bash test.sh &