shell script with trapped signal does not ignore signal - linux

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"

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

bash - close script by error or by timeout [duplicate]

This question already has answers here:
Timeout a command in bash without unnecessary delay
(24 answers)
Closed 2 years ago.
On stackoverflow there are many solutions - how to close script by timeout or close script if there is an error.
But how to have both approaches together?
If during execution of the script there is an error - close script.
If timeout is out - close script.
I have following code:
#!/usr/bin/env bash
set -e
finish_time=$1
echo "finish_time=" ${finish_time}
(./execute_something.sh) & pid=$!
sleep ${finish_time}
kill $pid
But if there is an error while execution - script still waits, when timeout would be out.
First, I won't use set -e.
You'll explicitly wait on the job you want; the exit status of wait will be the exit status of the job itself.
echo "finish_time = $1"
./execute_something.sh & pid=$!
sleep "$1" & sleep_pid=$!
wait -n # Waits for either the sleep or the script to finish
rv=$?
if kill -0 $pid; then
# Script still running, kill it
# and exit
kill -s ALRM $pid
wait $pid # exit status will indicte it was killed by SIGALRM
exit
else
# Script exited before sleep
kill $sleep_pid
exit $rv
fi
There is a slight race condition here; it goes as follows:
wait -n returns after sleep exits, indicating the script will exit on its own
The script exits before we can check if it is still running
As a result, we assume it actually exited before sleep.
But that just means we'll create a script that ran slightly over the threshold as finishing on time. That's probably not a distinction you care about.
Ideally, wait would set some shell parameter that indicates which process caused it to return.

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

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.

Idle bash script until CTRL+c event is logged

I have a bash script which does some work, which is done fairly quick.
It should then idle until the user decides to terminate it, followed by some clean-up code.
This is why I trap the CTRL+c event with the following code:
control_c()
{
cleanup
exit 0
}
trap control_c SIGINT
But as my script is done quite quickly I never get to purposely terminate it, so it never gets to trap the CTRL+c and run the clean-up code.
I figured I could implement an endless do while loop, with sleep at the end of the script, but I assume there is a better solution.
How can I idle a script in bash, expecting the CTRL+c event?
Assuming you're connected to a TTY:
# idle waiting for abort from user
read -r -d '' _ </dev/tty
The following will wait for Ctrl-C, then continue running:
( trap exit SIGINT ; read -r -d '' _ </dev/tty ) ## wait for Ctrl-C
echo script still running...
I had problem with read -r -d '' _ </dev/tty, I placed it in a script to prevent it from exiting and killing jobs. I have another script that uses dialog and calls the first one as a job, when the script reaches read -r -d '' _ </dev/tty the parent script dialog have a very weird behavior, like someone is constantly pressing the Esc key, this makes the dialog try to quit.
What I recommend is to use sleep you can sleep for a long time like 999 days sleep 999d and if you need for sure for it to never stop you wan put it in a while loop.

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.

Resources