I am trying to write a wrapper which will execute a script as a session leader.
I am confused by the behaviour of the linux command setsid. Consider this script, called test.sh:
#!/bin/bash
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 output differs depending on whether it is executed or sourced:
$ bash
$ SID=$(ps -p $$ --no-headers -o sid)
$ echo pid=$$ ppid=$PPID sid=$SID
pid=9213 ppid=9104 sid= 9104
$ ./test.sh 1 ; sleep 5
pid=9326 ppid=9324 sid= 9326 child
pid=9324 ppid=9213 sid= 9104 parent
$ . ./test.sh 1 ; sleep 5
pid=9213 ppid=9104 sid= 9104 parent
pid=9336 ppid=1 sid= 9336 child
$ echo $BASH_VERSION
4.2.8(1)-release
$ exit
exit
So, it seems to me that setsid returns immediately when the script is sourced, but it waits for its child when the script is executed.
Why would the presence of a controlling tty have anything to do with setsid? Thanks!
Edit: For clarification I added pid/ppid/sid reporting to all relevant commands.
The source code of the
setsidutility is actually very straightforward. You’ll note that it onlyfork()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 neverwait()s for its child process: if itfork()s, then the parent process just returns immediately. If it doesn’tfork(), then it gives the appearance ofwait()ing for a child, but really what happens is just that it is the child, and it’s Bash that’swait()ing (just as it always does). (Of course, when it really doesfork(), Bash can’twait()for the child it creates, because processeswait()for their children, not their grandchildren.)So the behavior that you’re seeing is a direct consequence of a different behavior:
. ./test.shorsource ./test.shor whatnot — or for that matter, when you just runsetsiddirectly from the Bash prompt — Bash will launchsetsidwith a new process-group-ID for job control purposes, sosetsidwill have the same process-ID as its process-group-ID (that is, it’s a process group leader), so it willfork()and won’twait()../test.shorbash test.shor whatnot and it launchessetsid,setsidwill 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).