Signal handling in a shell script - linux

Following is a shell script (myscript.sh) I have:
#!/bin/bash
sleep 500 &
Aprogram arg1 arg2 # Aprogram is a program which runs for an hour.
echo "done"
I launched this in one terminal, and from another terminal I issued 'kill -INT 12345'. 12345 is the pid of myscript.sh.
After a while I can see that both myscript.sh and Aprogram have been dead. However 'sleep 500 &' is still running.
Can anyone explain why is this behavior?
Also, when I issued SIGINT signal to the 'myscript.sh' what exactly is happening? Why is 'Aprogram' getting killed and why not 'sleep' ? How is the signal INT getting transmitted to it's child processes?

You need to use trap to catch signals:
To just ignore SIGINT use:
trap '' 2
if you want to specify some special action for this you can make it that in line:
trap 'some commands here' 2
or better wrap it into a function
function do_for_sigint() {
...
}
trap 'do_for_sigint' 2
and if you wish to allow your script to finish all it's tasks first:
keep_running="yes"
trap 'keep_running="no"' 2
while [ $keep_running=="yes" ]; do
# main body of your script here
done

You start sleep in the background. As such, it is not killed when you kill the script.
If you want to kill sleep too when the script is terminated, you'd need to trap it.
sleep 500 &
sid=($!) # Capture the PID of sleep
trap "kill ${sid[#]}" INT # Define handler for SIGINT
Aprogram arg1 arg2 & # Aprogram is a program which runs for an hour.
sid+=($!)
echo "done"
Now sending SIGINT to your script would cause sleep to terminate as well.

After a while I can see that both myscript.sh and Aprogram have been dead. However 'sleep 500 &' is still running.
As soon as Aprogram is finished myscript.sh prints "Done" and is also finised. sleep 500 gets process with PID 1 as a parent. That is it.
Can anyone explain why is this behavior?
SIGINT is not deliverd to Aprogram when myscript.sh gets it. Use strace to make sure that Aprogram does not receive a signal.
Also, when I issued SIGINT signal to the 'myscript.sh' what exactly is happening?
I first thought that it is the situation like when a user presses Ctrl-C and read this http://www.cons.org/cracauer/sigint.html. But it is not exactly the same situation. In your case shell received SIGINT but the child process didn't. However, shell had at that moment a child process and it did not do anything and kept waiting for a child. This is strace output on my computer after sending SIGINT to a shell script waiting for a child process:
>strace -p 30484
Process 30484 attached - interrupt to quit
wait4(-1, 0x7fffc0cd9abc, 0, NULL) = ? ERESTARTSYS (To be restarted)
--- SIGINT (Interrupt) # 0 (0) ---
rt_sigreturn(0x2) = -1 EINTR (Interrupted system call)
wait4(-1,
Why is 'Aprogram' getting killed and why not 'sleep' ? How is the signal INT getting transmitted to it's child processes?
As far as I can see with strace a child program like your Aprogram is not getting killed. It did not receive SIGINT and finished normally. As soon as it finished your shell script also finished.

Related

How to catch SIGINT within a Bash subshell

If I run a command, such as grep, at the command line and hit ^C, the command is properly killed (with SIGINT I think). And if I run the grep in background and then run a kill SIGINT on its PID, it similarly gets terminated. But if I'm inside a script and run grep in background from the script, get its PID and then use 'kill -s SIGINT $PID', grep does not get killed. Why? If I use SIGTERM, instead of SIGINT, the kill does work.
#!/bin/bash
grep -rqa shazam /usr &
PID=$!
kill -s SIGINT $PID
Even if I put the grep in a subprocess, preceded by a SIGINT handler (in the subprocess), and hit the subprocess with SIGINT, the handler is not invoked.
#!/bin/bash
( trap 'echo "caught signal"' SIGINT; grep -rqa shazam /usr ) &
PID=$!
kill -s SIGINT $PID
The trap handler is invoked if I use SIGTERM, instead of SIGINT, but does not interrupt grep. If I add '/bin/kill -s SIGTERM 0' to the trap handler, there is an indication that the grep process gets terminated, but grep has already completed its work by then. I realize that Bash may have different default behaviors for the different signals, but I don't understand why my call to kill SIGINT is different than a ^C, why the trap call works for SIGTERM, but not for SIGINT, nor why SIGTERM isn't handled by the subprocess immediately.
Well, with further digging, I figured out 2 of my 3 questions. When I backgrounded grep within the script, the shell told it to ignore SIGINT. And Bash says it will wait to handle the signal until the subcommand is complete in some situations (which I don't fully follow at the moment), but the signal is handled immediately if hit the grep process directly with pkill.
"Actually bash will disable SIGINT (and SIGQUIT) on background processes and they can't be enabled" Background process and signals How SIGINT works
"Further background jobs are not supposed to be tied to the shell that started them. If you exit a shell, they will continue running. As such they shouldn't be interrupted by SIGINT, not by default. When job control is enabled, that is fulfilled automatically, since background jobs are running in separate process groups. When job control is disabled (generally in non-interactive shells), bash makes the asynchronous commands ignore SIGINT." Independent Program
Reason why SIGTERM works

`trap`-ing Linux Signals - SIGSTOP?

The Docker docs note the following (w/ code) on how to run clean up upon a container's shutdown:
Lastly, if you need to do some extra cleanup ... on shutdown, ..., you
may need to ensure that the ENTRYPOINT script receives the Unix
signals, passes them on, and then does some more work
#!/bin/sh
trap "echo TRAPed signal" HUP INT QUIT KILL TERM
/usr/sbin/apachectl start
I think that this trap will catch a KILL signal. However, I read in this post:
All signals except for SIGKILL and SIGSTOP can be intercepted by the process.
However, another post states:
There is one signal that you cannot trap: SIGKILL or signal 9.
Which is it?
You cannot catch SIGSTOP. The page you referenced was mistaken.
Yes, the above script tries to catch the KILL signal. It fails. You can verify this yourself quite easily with the following script:
#!/bin/sh
echo "Running shell in pid $$"
trap "echo TRAPed signal" HUP INT QUIT KILL TERM STOP
sleep 20
Try sending it the KILL and STOP signals, you will see that the process dies and halts, respectively, without the message being printed. If you try any other caught signal, you will see those are handled as expected.

ctrl+c to kill a bash script with child processes

I have a script whose internals boil down to:
trap "exit" SIGINT SIGTERM
while :
do
mplayer sound.mp3
sleep 3
done
(yes, it is a bit more meaningful than the above, but that's not relevant to the problem). Several instances of the script may be running at the same time.
Sometimes I want to ^C the script... but that does not succeed. As I understand, when ^C kills mplayer, it continues to sleep, and when ^C kills sleep, it continues to mplayer, and I never happen to catch it in between. As I understand, trap just never works.
How do I terminate the script?
You can get the PID of mplayer and upon trapping send the kill signal to mplayer's PID.
function clean_up {
# Perform program exit housekeeping
KILL $MPLAYER_PID
exit
}
trap clean_up SIGHUP SIGINT SIGTERM
mplayer sound.mp3 &
MPLAYER_PID=$!
wait $MPLAYER_PID
mplayer returns 1 when it is stopped with Ctrl-C so:
mplayer sound.mp3 || break
will do the work.
One issue of this method is that if mplayer exits 1 for another reason (i.e., sound file has a bad format), it will exit anyway, and it's maybe not the desired behaviour.

How to get right PID of a group of background command and kill it?

Ok, just like in this thread, How to get PID of background process?, I know how to get the PID of background process. However, what I need to do countains more than one operation.
{
sleep 300;
echo "Still running after 5 min, killing process manualy.";
COMMAND COMMAND COMMAND
echo "Shutdown complete"
}&
PID_CHECK_STOP=$!
some stuff...
kill -9 $PID_CHECK_STOP
But it doesn't work. It seems i get either a bad PID or I just can't kill it. I tried to run ps | grep sleep and the pid it gives is always right next to the one i get in PID_CHECK_STOP. Is there a way to make it work? Can i wrap those commands an other way so i can kill them all when i need to?
Thx guys!
kill -9 kills the process before it can do anything else, including signalling its children to exit. Use a gentler signal (kill by itself, which sends a TERM, should be sufficient). You do need to have the process signal its children to exit (if any) explicitly, though, via a trap command.
I'm assuming sleep is a placeholder for the real command. sleep is tricky, however, as it ignores any signals until it returns (i.e., it is non-interruptible). To make your example work, put sleep itself in the background and immediately wait on it. When you kill the "outer" background process, it will interrupt the wait call, which will allow sleep to be killed as well.
{
trap 'kill $(jobs -p)' EXIT
sleep 300 & wait
echo "Still running after 5 min, killing process manualy.";
COMMAND COMMAND COMMAND
echo "Shutdown complete"
}&
PID_CHECK_STOP=$!
some stuff...
kill $PID_CHECK_STOP
UPDATE: COMMAND COMMAND COMMAND includes a command that runs via sudo. To kill that process, kill must also be run via sudo. Keep in mind that doing so will run the external kill program, not the shell built-in (there is little difference between the two; the built-in exists to allow you to kill a process when your process quota has been reached).
You can have another script containing those commands and kill that script. If you are dynamically generating code for the block, just write out a script, execute it and kill when you are done.
The { ... } surrounding the statements starts a new shell, and you get its PID afterwards. sleep and other commands within the block get separate PIDs.
To illustrate, look for your process in ps afux | less - the parent shell process (above the sleep) has the PID you were just given.

Bash script: can not properly handle SIGTSTP

I have a bash script that mounts and unmounts a device, which performing some read operations in between. Since the device is very slow, the script takes about 15 seconds to complete (the mount taking atleast 5-6 seconds). Since leaving this device mounted can cause other problems, I don't want this script to be interrupted.
Having said that, I can correctly handle SIGINT (Ctrl+c), but when I try to handle SIGTSTP (Ctrl+z), the script freezes. Which means the signal is trapped but the handler doesn't run.
#!/bin/sh
cleanup()
{
# Don't worry about unmounting yet. Just checking if trap works.
echo "Quitting..." > /dev/tty
exit 0
}
trap 'cleanup' SIGTSTP
...
I manually have to send the KILL signal to the process. Any idea why this is happening and how I can fix it?
The shell does not execute the trap until the currently executing process terminates. (at least, that is the behavior of bash 3.00.15). If you send SIGINT via ^c, it is sent to all processes in the foreground process group; if the program currently executing receives it and terminates then bash can execute the trap. Similarly with SIGTSTP via ^z; bash receives the signal but does not execute the trap until the program that was being run terminates, which it does not do if it takes the default behavior and is suspended. Try replacing ... with a simple read f and note that the trap executes immediately.

Resources