bash: daemonizing by forking process as a new child - linux

I have a bash script which should daemonize itself after being run. My solution looks as follows:
#!/bin/sh -xe
child() {
echo child
}
child & # fork child
echo parent
kill $$ # kill parent
However, putting the whole script itself inside the function child does not seem the correct thing to do. Unfortunately exec & won't fork-off the whole process into a backgrounded child.
How can a achieve the desired effect?

I usually do something like this:
#!/bin/bash
if [ -z "$_IS_DAEMON" ]; then
_IS_DAEMON=1 /bin/bash $0 "$#" &
exit
fi
echo "I'm a deamon!"
The script effectively restarts itself in the background, while exiting the script started by user.
To recognize the daemonization status, it uses an environment variable (the $_IS_DAEMON in the example above): if not set, assume started by user; if set, assume started as part of daemonization.
To restart itself, the script simply invokes $0 "$#": the $0 is the name of the script as was started by the user, and the "$#" is the arguments passed to the script, preserved with white-spaces and all (unlike the $*). I also typically call needed shell explicitly, as to avoid confusion between /bin/bash and /bin/sh which are on most *nix systems are not the same.

Related

How to kill shell script without killing currently executed line

I am running a shell script, something like sh script.sh in bash. The script contains many lines, some of which take seconds and others take days to execute. How can I kill the sh command but not kill its command currently running (the current line from the script)?
You haven't specified exactly what should happen when you 'kill' your script., but I'm assuming that you'd like the currently executing line to complete and then exit before doing any more work.
This is probably best achieved only by coding your script to behave in such a way as to receive such a kill command and respond in an appropriate way - I don't think that there is any magic to do this in linux.
for example:
You could trap a signal and then set a variable
Check for existence of a file (e.g touch /var/tmp/trigger)
Then after each line in your script, you'd need to check to see if each the trap had been called (or your trigger file created) - and then exit. If the trigger has not been set, then you continue on and do the next piece of work.
To the best of my knowledge, you can't trap a SIGKILL (-9) - if someone sends that to your process, then it will die.
HTH, Ace
The only way I can think of achieving this is for the parent process to trap the kill signal, set a flag, and then repeatedly check for this flag before executing another command in your script.
However the subprocesses need to also be immune to the kill signal. However bash seems to behave different to ksh in this manner and the below seems to work fine.
#!/bin/bash
QUIT=0
trap "QUIT=1;echo 'term'" TERM
function terminated {
if ((QUIT==1))
then
echo "Terminated"
exit
fi
}
function subprocess {
typeset -i N
while ((N++<3))
do
echo $N
sleep 1
done
}
while true
do
subprocess
terminated
sleep 3
done
I assume you have your script running for days and then you don't just want to kill it without knowing if one of its children finished.
Find the pid of your process, using ps.
Then
child=$(pgrep -P $pid)
while kill -s 0 $child
do
sleep 1
done
kill $pid

Bash: Why does parent script not terminate on SIGINT when child script traps SIGINT?

script1.sh:
#!/bin/bash
./script2.sh
echo after-script
script2.sh:
#!/bin/bash
function handler {
exit 130
}
trap handler SIGINT
while true; do true; done
When I start script1.sh from a terminal, and then use Ctrl+C to send SIGINT to its process group, the signal is trapped by script2.sh and when script2.sh terminates, script1.sh prints "after-script". However, I would have expected script1.sh to immediately terminate after the line that invokes script2.sh. Why is this not the case in this example?
Additional remarks (edit):
As script1.sh and script2.sh are in the same process group, SIGINT gets sent to both scripts when Ctrl+C is pressed on the command line. That's why I wouldn't expect script1.sh to continue when script2.sh exits.
When the line "trap handler SIGINT" in script2.sh is commented out, script1.sh does exit immediately after script2.sh exists. I want to know why it behaves differently then, as script2.sh produces just the same exit code (130) then.
New answer:
This question is far more interesting than I originally suspected. The answer is essentially given here:
What happens to a SIGINT (^C) when sent to a perl script containing children?
Here's the relevant tidbit. I realize you're not using Perl, but I assume Bash is using C's convention.
Perl’s builtin system function works just like the C system(3)
function from the standard C library as far as signals are concerned.
If you are using Perl’s version of system() or pipe open or backticks,
then the parent — the one calling system rather than the one called by
it — will IGNORE any SIGINT and SIGQUIT while the children are
running.
This explanation is the best I've seen about the various choices that can be made. It also says that Bash does the WCE approach. That is, when a parent process receives SIGINT, it waits until its child process returns. If that process handled exited from a SIGINT, it also exits with SIGINT. If the child exited any other way it ignores SIGINT.
There is also a way that the calling shell can tell whether the called
program exited on SIGINT and if it ignored SIGINT (or used it for
other purposes). As in the WUE way, the shell waits for the child to
complete. It figures whether the program was ended on SIGINT and if
so, it discontinue the script. If the program did any other exit, the
script will be continued. I will call the way of doing things the
"WCE" (for "wait and cooperative exit") for the rest of this document.
I can't find a reference to this in the Bash man page, but I'll keep looking in the info docs. But I'm 99% confident this is the correct answer.
Old answer:
A nonzero exit status from a command in a Bash script does not terminate the program. If you do an echo $? after ./script2.sh it will show 130. You can terminate the script by using set -e as phs suggests.
$ help set
...
-e Exit immediately if a command exits with a non-zero status.
The second part of #seanmcl's updated answer is correct and the link to http://www.cons.org/cracauer/sigint.html is a really good one to read through carefully.
From that link, "You cannot 'fake' the proper exit status by an exit(3) with a special numeric value, even if you look up the numeric value for your system". In fact, that's what is being attempted in #Hermann Speiche's script2.sh.
One answer is to modify function handler in script2.sh as follows:
function handler {
# ... do stuff ...
trap INT
kill -2 $$
}
This effectively removes the signal handler and "rethrows" the SIGINT, causing the bash process to exit with the appropriate flags such that its parent bash process then correctly handles the SIGINT that was originally sent to it. This way, using set -e or any other hack is not actually required.
It's also worth noting that if you have an executable that behaves incorrectly when sent a SIGINT (it doesn't conform to "How to be a proper program" in the above link, e.g. it exits with a normal return-code), one way of working around this is to wrap the call to that process with a script like the following:
#!/bin/bash
function handler {
trap INT
kill -2 $$
}
trap handler INT
badprocess "$#"
The reason is your script1.sh doesn't terminate is that script2.sh is running in a subshell. To make the former script exit, you can either set -e as suggested by phs and seanmcl or force the script2.sh to run in the same shell by saying:
. ./script2.sh
in your first script. What you're observing would be apparent if you were to do set -x before executing your script. help set tells:
-x Print commands and their arguments as they are executed.
You can also let your second script send a terminating signal on its parent script by SIGHUP, or other safe and usable signals like SIGQUIT in which the parent script may consider or trap as well (sending SIGINT doesn't work).
script1.sh:
#!/bin/bash
trap 'exit 0' SIQUIT ## We could also just accept SIGHUP if we like without traps but that sends a message to the screen.
./script2.sh ## or "bash script.sh" or "( . ./script.sh; ) which would run it on another process
echo after-script
script2.sh:
#!/bin/bash
SLEEPPID=''
PID=$BASHPID
read PPID_ < <(exec ps -p "$PID" -o "$ppid=")
function handler {
[[ -n $SLEEPPID ]] && kill -s SIGTERM "$SLEEPPID" &>/dev/null
kill -s SIGQUIT "$PPID_"
exit 130
}
trap handler SIGINT
# better do some sleeping:
for (( ;; )); do
[[ -n $SLEEPPID ]] && kill -s 0 "$SLEEPPID" &>/dev/null || {
sleep 20 &
SLEEPPID=$!
}
wait
done
Your original last line in your script1.sh could have just like this as well depending on your scripts intended implementation.
./script2.sh || exit
...
Or
./script2.sh
[[ $? -eq 130 ]] && exit
...
The correct way this should work is through setpgrp(). All children of shell should be placed in the same pgrp. When SIGINT is signaled by the tty driver, it will be summarily delivered to all processes. The shell at any level should note the receipt of the signal, wait for children to exit and then kill themselves, again, with no signal handler, with sigint, so that their exit code is correct.
Additionally, when SIGINT is set to ignore at startup by their parent process, they should ignore SIGINT.
A shell should not "check if a child exited with sigint" as any part of the logic. The shell should always just honor the signal it receives directly as the reason to act and then exit.
Back in the day of real UNIX, SIGINT stopped the shell and all sub processes with a single key stroke. There was never any problem with the exit of a shell and child processes continuing to run, unless they themselves had set SIGINT to ignore.
For any shell pipeline, their should be a child process relationship created from pipelines going right to left. The right most command is the immediate child of the shell since thats the last process to exit normally. Each command line before that, is a child of the process immediately to the right of the next pipe symbol or && or || symbol. There are obvious groups of children around && and || which fall out naturally.
in the end, process groups keep things clean so that nohup works as well as all children receiving SIGINT or SIGQUIT or SIGHUP or other tty driver signals.

Difference between bash pid and $$

I'm a bash scripting beginner, and I have a "homework" to do. I figured most of the stuff out but there is a part which says that I have to echo the pid of the parent bash and the pid of the two subshells that I will be running. So I looked online and found this (The Linux documentation project):
#!/bin/bash4
echo "\$\$ outside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL outside of subshell = $BASH_SUBSHELL" # 0
echo "\$BASHPID outside of subshell = $BASHPID" # 9602
echo
( echo "\$\$ inside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL inside of subshell = $BASH_SUBSHELL" # 1
echo "\$BASHPID inside of subshell = $BASHPID" ) # 9603
# Note that $$ returns PID of parent process.
So here are my questions:
1) What does the first echo print? Is this the pid of the parent bash?
2) Why does the 2nd echo print out 0?
3) Is $BASH_SUBSHELL a command or a variable?
4) I'm doing everything on a mac, I will try all of this on a Linux machine in some days but
whenever I run this script $BASHPID doesn't return anything, I just get a new line. Is this because I'm running this on a mac and $BASHPID doesn't work on a mac?
Looking at documentation on this, it looks like:
$$ means the process ID that the script file is running under. For any given script, when it is run, it will have only one "main" process ID. Regardless of how many subshells you invoke, $$ will always return the first process ID associated with the script. BASHPID will show you the process ID of the current instance of bash, so in a subshell it will be different than the "top level" bash which may have invoked it.
BASH_SUBSHELL indicates the "subshell level" you're in. If you're not in any subshell level, your level is zero. If you start a subshell within your main program, that subshell level is 1. If you start a subshell within that subshell, the level would be 2, and so on.
BASH_SUBSHELL is a variable.
Maybe BASHPID isn't supported by the version of bash you have? I doubt it's a "Mac" problem.
It'd be best to get well-acquainted with bash(1):
BASHPID
Expands to the process ID of the current bash process.
This differs from $$ under certain circumstances, such
as subshells that do not require bash to be re-
initialized.
[...]
BASH_SUBSHELL
Incremented by one each time a subshell or subshell
environment is spawned. The initial value is 0.
$BASHPID was introduced with bash-4.0-alpha. If you run bash --version you can find out what version of bash(1) you're using.
If you're going to be doing much bash(1) work, you'll also need the following:
Greg's bash FAQ
TLDP bash reference card

invoking a bash script by itself

I need to invoke a bash script by itself with a different set of arguments that would make it run as a background process, so I am using something like:
if [[ $a == $b ]]
then
$0 -v &> /dev/null
fi
The issue is that though I am invoking the same script as a background process using '&' as suffix and redirecting all outputs to /dev/null, the terminal that I invoke the script is not released, I am assuming that is because of the script which initially was invoked has a process which is running as a foreground process, so the query is, how to call a bash script by itself such that when it calls itself the process which is responsible for running the script for the first time is killed and the console released and the second call to itself runs as a background process?
You're not running it as a background process using &. &> is a completely separate token that redirects stdout and stderr at the same time. If you wanted to put that command in the background it would be $0 -v &>/dev/null &.
Try something like this:
nohup $0 -v &
The nohup command does the job of detaching the background job and ignoring signals, see the man page.

What is '$$' in the bash shell?

I'm beginner at bash shell programming. Can you tell me about '$$' symbols in the bash shell?
If I try the following
#> echo $$
it prints
#>18756
Can you tell me what this symbol is used for and when?
It's the process id of the bash process itself.
You might use it to track your process over its life - use ps -p to see if it's still running, send it a signal using kill (to pause the process for example), change its priority with renice, and so on.
Process ids are often written to log files, especially when multiple instances of a script run at once, to help track performance or diagnose problems.
Here's the bash documentation outlining special parameters.
BASHPID, mentioned by ghostdog74, was added at version 4.0. Here's an example from Mendel Cooper's Advanced Bash-Scripting Guide that shows the difference between $$ and $BASHPID:
#!/bin/bash4
echo "\$\$ outside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL outside of subshell = $BASH_SUBSHELL" # 0
echo "\$BASHPID outside of subshell = $BASHPID" # 9602
echo
( echo "\$\$ inside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL inside of subshell = $BASH_SUBSHELL" # 1
echo "\$BASHPID inside of subshell = $BASHPID" ) # 9603
# Note that $$ returns PID of parent process.
if you have bash, a relatively close equivalent is the BASHPID variable. See man bash
BASHPID
Expands to the process id of the current bash process. This differs from $$ under certain circumstances, such as subshells
that do not require bash to be re-initialized.

Resources