Why can't I catch process signals when jobs control is enabled in a script? - linux

When I enable jobs control into a shell script (using set -m), I am no longer able to catch process signals. Take a look at the following code:
#!/bin/bash
set -m
for i in `seq 15`; do
trap 'echo " Signal $i catched"' $i
done
while true; do
echo " Waiting for a process signal"
sleep 999
done
When I run the above code and I press for example Ctrl + C, nothing happens:
Waiting for a process signal
^CWaiting for a process signal
However, when I run the same code deleting set -m, I do get an answer:
Waiting for a process signal
^C Signal 15 catched
My questions are:
Why is it not working?
Is it possible to catch process signals when jobs control is enabled?
Note: It doesn't happen with all processes, if I use read instead of sleep, it does work.

The solution is to put sleep in the background and use a bash builtin, wait, to wait while sleep completes. Thus, try this:
#!/bin/bash
set -m
for i in `seq 15`; do
trap 'echo " Signal $i catched"' $i
done
while true; do
echo " Waiting for a process signal"
sleep 999 & wait $!
done
Sample run:
$ bash script
Waiting for a process signal
^C Signal 15 catched
Waiting for a process signal
^C Signal 15 catched
Waiting for a process signal
For more details, see Greg's FAQ.

Related

How to stop all background processes(running functions) by using Trap?

I have two long running functions which needs to be executed asynchronously. For simplicity, assume one function sends Mail to client every 10 seconds & other logs text file every two seconds.
I cannot use cron jobs to do this. Everything has to be in one script. Thus I have used infinite loops and sleep with & achieve asynchronous behavior.
Used to trap 'pkill -P $$' SIGINT SIGTERM to end all child processes(to end program) when user hits CTRL+Z (SIGINT) but this doesn't work. It again starts script execution even after pressing CTRL+Z.
How can I give user the ability to end program with keyboard clicks from same terminal?
Note: Those two functions are never ending until user manually stops the program.
echo "Press: CTRL+Z to Close program"
trap 'pkill -P $$' SIGINT SIGTERM
first_fun()
{
while :; do
echo "send Mail every 10 seconds"
sleep 10
done
}
second_fun()
{
while :; do
echo "log text file every 2 seconds"
sleep 2
done
}
first_fun &
second_fun &
Suggesting to use " to let the shell interpret $$. Like this:
trap "pkill -9 -P $$"
Also suggesting to kill all process running from current directory, because process ancestory is not always working (e.g using nohup command):
trap "pkill -9 -f $PWD"
Also suggesting to kill/stop a process with CTRL-C (the standard) and avoid CTRL-Z used for suspending processes.
When problem with your script was that the script exists after runs those two functions. So "$$" is no longer refers to the script. An easy fix is to put a wait at the end of the script. Also change to this might help
trap "pkill -P $$" INT TERM
But, what I would do is to kill those functions rather than killing the script:
echo "Press: CTRL+Z to Close program"
first_fun()
{
while :; do
echo "send Mail every 10 seconds"
sleep 10
done
}
second_fun()
{
while :; do
echo "log text file every 2 seconds"
sleep 2
done
}
_INTERRUPTED
_PID1
_PID2
interrupt()
{
# Do this once.
if [[ -z "$_INTERRUPTED" ]]; then
_INTERRUPTED='true'
kill -KILL "$_PID1"
kill -KILL "$_PID2"
fi
}
trap interrupt INT TERM EXIT
first_fun &
_PID1="$!"
second_fun &
_PID2="$!"
wait

shell script with trapped signal does not ignore signal

I'm working on a test automation system and I'm coming up with misbehaving programs. With the first one I'm already encountering some unexpected behavior.
trap "echo No thanks" INT
echo Let me just chill for $1 sec
sleep $1
echo All finished
Observed behavior:
sending SIGINT causes "No thanks" to be printed, the sleep is apparently interrupted immediately and "All finished" is also printed immediately after that.
behavior is same whether signal is sent separately or performed with keyboard ctrl+c.
same behavior is observed if sleep is backgrounded and we wait for it.
Expected behavior:
sending SIGINT to the process should result in "No thanks" to be printed for as long as the sleep runs, and then "All finished" will be printed before exiting, after the sleep finishes.
If the sleep is backgrounded, issuing keyboard ctrl+c should send SIGINT to the process group, which would include the sleep, so that should stop it prematurely. I am not sure what to expect
Questions:
How can I obtain desired behavior?
Why exactly does it behave like this (different from my expectation)?
The question is essentially a dupe of this but there are no satisfactory explanations in that answer.
Currently:
bash waits for sleep to exit
bash and sleep receive sigint
sleep dies
bash finishes waiting and runs the trap
This prevents your desired behavior because:
You didn't want sleep to die
You didn't want bash to wait for the command to complete before you run the trap
To fix this, you can have sleep ignore the sigint, and have bash run wait in a loop so that the main script gets back control after the ctrl-c, but still waits for the sleep to complete:
trap 'echo "No thanks"' INT
echo "Let me just chill for $1 sec"
# Run sleep in the background
sleep "$1" &
# Loop until we've successfully waited for all processes
until wait; do true; done
echo "All finished"

Signal handling in a shell script

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.

Shell Script get CTRL+Z with Trap

I am trying to get the SIGSTOP CTRL+Z signal in my script's trap.
When my script is executing, if I temporarily suspend from execution, send a SIGSTOP signalCTRL+Z, it needs to remove the files I create in it and to kill the execution.
I don't understand why the following script doesn't work. But, more important, what is the correct way to do it?
#!/bin/bash
DIR="temp_folder"
trap "rm -r $DIR; kill -SIGINT $$" SIGSTP
if [ -d $DIR ]
then
rm -r $DIR
else
mkdir $DIR
fi
sleep 5
EDIT:
SIGSTOP cannot be trapped, however SIGTSTP can be trapped, and from what I understood after searching on the internet and the answer below it's the correct to trap when sending signal with CTRL+Z. However, when I press CTRL+Z while running the script it will get stuck, meaning that the script will be endlessly execute no matter what signals I send afterwards.
The problem here is you are trying to suspend a process that is already sleeping.
It is also good practice to use DIR=$(mktemp -d) in shell scripts to create temp directories.
CTRL-C is signal (2) / CTRL-Z (20):
catch_exits() {
printf "\n$(basename $0): exiting\n" 1>&2
rm -rf $DIR
exit 1
}
trap catch_exits 1 2 3 15 20
DIR="$(mktemp -d)"
read -p "not sleeping" test
if you send a function to the background (such as for a cursor spinner) - then you need to disable CTRL-Z while the long process is running with:
trap "" SIGTSTP
There are two signals you can't trap*, SIGKILL and SIGSTOP. Use another signal.
*: without modifying the kernel
IEEE standard:
Setting a trap for SIGKILL or SIGSTOP produces undefined results.

Prevent a bash script from terminating after handling a SIGINT

I am writing a bash wrapper for an application. This wrapper is responsible for changing the user, running the software and logging its output.
I also want it to propagate the SIGINT signal.
Here is my code so far :
#!/bin/bash
set -e; set -u
function child_of {
ps --ppid $1 -o "pid" --no-headers | head -n1
}
function handle_int {
echo "Received SIGINT"
kill -int $(child_of $SU_PID)
}
su myuser -p -c "bash /opt/loop.sh 2>&1 | tee -i >(logger -t mytag)" &
SU_PID=$!
trap "handle_int" SIGINT
wait $SU_PID
echo "This is the end."
My problem is that when I send a SIGINT to this wrapper, handle_int gets called but then the script is over, while I want it to continue to wait for $SU_PID.
Is there a way to catch the int signal, do something and then prevent the script from terminating ?
You have a gotcha: after Ctrl-C, "This is the end." is expected but it never comes because the script has exited prematurely. The reason is wait has (unexpectedly) returned non-zero while running under set -e.
According to "man bash":
If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap
will not be executed until the command completes. When bash is waiting for an asynchronous command via the
wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return
immediately with an exit status greater than 128, immediately after which the trap is executed.
You should wrap your wait call in set +e so that your program can continue running after handling a trapped signal while waiting for an asynchronous command.
Like this:
# wait function that handles trapped signal on asynchronous commands.
function safe_async_wait {
set +e
wait $1 # returns >128 on asynchronous commands
set -e
}
#...
safe_async_wait $SU_PID

Resources