linux command setsid - bash

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 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 or source ./test.sh or whatnot — or for that matter, when you just run setsid directly from the Bash prompt — Bash will launch setsid with a new process-group-ID for job control purposes, so setsid will have the same process-ID as its process-group-ID (that is, it's a process group leader), so it will fork() and won't wait().
when you run ./test.sh or bash test.sh or whatnot and it launches setsid, 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't fork(), so it'll give the appearance of waiting (without actually wait()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 1
child
parent
$ . test.sh 1
child
$ uname -r
3.1.10
$ echo $BASH_VERSION
4.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 &

Related

trap exec return code in shell script

I have to run a command using exec in a shell script, and I need to trap the exit code in case of error and run another command e.g
#!/bin/sh
set +e
exec command_that_will_fail
if [ $? -eq 1 ]; then
echo "command failed, running another command"
fi
I understand that exec replaces the current shell and carries on, my problem is that I need to run another command if the exec is not sucessfull.
Your code works if there's some immediate error when it tries to run the process:
$ echo 1
1
$ echo $?
0
$ exec asd123
-bash: exec: asd123: not found
$ echo $?
127
If the executable file was found, and is started, then it will not return, because it will overtake the whole script and never return to bash again.
For example this never returns:
$ exec grep asd /dev/null
(the exit code of grep is 1, but the parent shell is overtaken, so nobody can check)
If you want to get an exit code from the process in this case, you have to start it as a subprocess, i.e. not using exec (just command_that_will_fail). In this case the bash process will act as a supervisor that waits until the subprocess finishes and can inspect the exit code.

Run bash script in background by default

I know I can run my bash script in the background by using bash script.sh & disown or alternatively, by using nohup. However, I want to run my script in the background by default, so when I run bash script.sh or after making it executable, by running ./script.sh it should run in the background by default. How can I achieve this?
Self-contained solution:
#!/bin/sh
# Re-spawn as a background process, if we haven't already.
if [[ "$1" != "-n" ]]; then
nohup "$0" -n &
exit $?
fi
# Rest of the script follows. This is just an example.
for i in {0..10}; do
sleep 2
echo $i
done
The if statement checks if the -n flag has been passed. If not, it calls itself with nohup (to disassociate the calling terminal so closing it doesn't close the script) and & (to put the process in the background and return to the prompt). The parent then exits to leave the background version to run. The background version is explicitly called with the -n flag, so wont cause an infinite loop (which is hell to debug!).
The for loop is just an example. Use tail -f nohup.out to see the script's progress.
Note that I pieced this answer together with this and this but neither were succinct or complete enough to be a duplicate.
Simply write a wrapper that calls your actual script with nohup actualScript.sh &.
Wrapper script wrapper.sh
#! /bin/bash
nohup ./actualScript.sh &
Actual script in actualScript.sh
#! /bin/bash
for i in {0..10}
do
sleep 10 #script is running, test with ps -eaf|grep actualScript
echo $i
done
tail -f 10 nohup.out
0
1
2
3
4
...
Adding to Heath Raftery's answer, what worked for me is a variation of what he suggested such as this:
if [[ "$1" != "-n" ]]; then
$0 -n & disown
exit $?
fi

chain starting of programs in bash

i want to start programs in a chain, like start a bash script startScript.sh and then in the startScript.sh chain load certain programs.
startScript.sh
./program1 &
#PID=(echo $!)
PID=$!
echo "Wait for 10 seconds here"
if ps -p $PID > /dev/null; then echo "continue"; ./program2; else echo "PID is not running"; exit; fi
echo "Wait for 10 seconds here"
#if program1 is running and program2 is running, then ./program3, else exit.
I tried to find the running pid of program1 with $!, but the problem is that program1 is itself a shell script and they invokes further shell scripts in which I am not interested. Hence $! never gives me the pid of ./program1 but something irrelevant. HOw can I get the PID of program1?
Also how can I get the PID of program1 as well as program2 to see if they are running and then start program3.
The special parameter $! in shell (from man bash):
Expands to the process ID of the job most recently placed into the background, whether executed as an asynchronous command or using the bg builtin (see JOB CONTROL below).
It's important to notice that job control is shell-local (in your case local to the bash interpreter executing your startScript.sh).
That means the pid from below:
#!/bin/bash
./program.sh
pid=$!
will always contain the PID of the program.sh script (i.e. the process executing it, defined with a hashbang, in your case probably another bash process), regardless of the background processes spawned by that child process (remember that $! is local to your script; the $! in the child will be undefined, until a background process is spawned there).
What maybe sidetracked you initially is the line:
PID=(echo $!)
that didn't properly set the PID. The PID was set to an array containing two words (elements): echo and <pid>. You want this:
pid=$!
(and maybe to use lowercase name for the non-global variable pid).
A simple demo:
$ cat script1.sh
#!/bin/bash
./script2.sh &
echo "in script1: $!"
$ cat script2.sh
#!/bin/bash
echo "in script2: $!"
sleep 5 &
echo "in script2 after sleep: $!"
$ ./script1.sh
in script1: 19537
in script2:
in script2 after sleep: 19541
Notice how the $! in initially undefined in script2.sh - if $! were to return the last PID globally, it would be set to PID of script2.sh.

Bash - Hiding a command but not its output

I have a bash script (this_script.sh) that invokes multiple instances of another TCL script.
set -m
for vars in $( cat vars.txt );
do
exec tclsh8.5 the_script.tcl "$vars" &
done
while [ 1 ]; do fg 2> /dev/null; [ $? == 1 ] && break; done
The multi threading portion was taken from Aleksandr's answer on: Forking / Multi-Threaded Processes | Bash.
The script works perfectly (still trying to figure out the last line). However, this line is always displaed: exec tclsh8.5 the_script.tcl "$vars"
How do I hide that line? I tried running the script as :
bash this_script.sh > /dev/null
But this hides the output of the invoked tcl scripts too (I need the output of the TCL scripts).
I tried adding the /dev/null to the end of the statement within the for statement, but that too did not work either. Basically, I am trying to hide the command but not the output.
You should use $! to get the PID of the background process just started, accumulate those in a variable, and then wait for each of those in turn in a second for loop.
set -m
pids=""
for vars in $( cat vars.txt ); do
tclsh8.5 the_script.tcl "$vars" &
pids="$pids $!"
done
for pid in $pids; do
wait $pid
# Ought to look at $? for failures, but there's no point in not reaping them all
done

Can a Bashscript disown itself, and then kill the script that launched it in the first place?

script1.sh
script2.sh #launch another script
sudo reboot #reboot computer
script2.sh
# disown myself from script1
pkill script2.sh # hence preventing the reboot
# continue doing other stuff, even after the parent is dead
If possible, how is this done?
You don't need to disown the child script:
$ cat parent.sh
#!/usr/bin/env bash
echo 'Parent start'
./child.sh
echo 'Parent end'
$ cat child.sh
#!/usr/bin/env bash
echo 'Child start'
kill $PPID
sleep 1
echo 'Child end'
$ ./parent.sh
Parent start
Child start
Terminated
$ Child end
So the parent is terminated by the child, and one second later the child runs its own last line. The child has simply been reparented to the init process. If you up the timeout you can see the entry in the process table:
$ ps a | grep '[.]sh'
9752 pts/1 S 0:00 bash ./child.sh

Resources