sh trap SIGINT failed, but trap SIGQUIT success - linux

I want to trap the CtrL+c and CtrL+\, then the cmd below added into my script:
trap _trapException SIGINT SIGQUIT
function _trapException(){
echo "The job is canceled!"
exit
}
However, this can trap CtrL+\ but can not trap CtrL+c,
I delete the SIGQUIT, it still does not trap CtrL+c.
Otherwise, I used tee function in my script at the same time.

Your handler function and trap call are fine. The function will be called when you raise either SIGINT or SIGQUIT for the first time. However, in the signal handler, you are also calling exit. That means it's going to kill the process.
Try removing the exit call from the function _trapException.

#Blue Moon I found what causes the problem when I rewrite a demo code to reproduce it. The demo as below:
test.sh
#!/bin/sh
#encoding:UTF-8
trap _trapException SIGINT SIGQUIT
function _trapException(){
echo "INFO: The job is canceled!"
exit 1
}
sh trap_test.sh | tee -a test.log
while the trap_test.sh also has a trap function, the function like below:
#!/bin/shell
trap test SIGINT SIGQUIT
function test(){
echo "trap test!"
exit 1
}
while true
do
echo "test"
sleep 10
done
when I run sh test.sh, then trap the CtrL+c failed, however trap Ctrl+\ success;
when I delete the trap code in trap_test.sh , it can trap both signal when run sh test.sh.
The deep reason still unknown?

Related

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

Bash not trapping interrupts during rsync/subshell exec statements

Context:
I have a bash script that contains a subshell and a trap for the EXIT pseudosignal, and it's not properly trapping interrupts during an rsync. Here's an example:
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT
(
#main script logic, including the following lines:
(exec sleep 10;);
(exec rsync --progress -av --delete $directory1 /var/tmp/$directory2;);
) | 2>&1 tee -a $logfile
trap - EXIT #just in case cleanup isn't called for some reason
The idea of the script is this: most of the important logic runs in a subshell which is piped through tee and to a logfile, so I don't have to tee every single line of the main logic to get it all logged. Whenever the subshell ends, or the script is stopped for any reason (the EXIT pseudosignal should capture all of these cases), the trap will intercept it and run the cleanup() function, and then remove the trap. The rsync and sleep commands (the sleep is just an example) are run through exec to prevent the creation of zombie processes if I kill the parent script while they're running, and each potentially-long-running command is wrapped in its own subshell so that when exec finishes, it won't terminate the whole script.
The problem:
If I interrupt the script (via kill or CTRL+C) during the exec/subshell wrapped sleep command, the trap works properly, and I see "Cleaning up!" echoed and logged. If I interrupt the script during the rsync command, I see rsync end, and write rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6] to the screen, and then the script just dies; no cleanup, no trapping. Why doesn't an interrupting/killing of rsync trigger the trap?
I've tried using the --no-detach switch with rsync, but it didn't change anything.
I have bash 4.1.2, rsync 3.0.6, centOS 6.2.
How about just having all the output from point X be redirected to tee without having to repeat it everywhere and mess with all the sub-shells and execs ... (hope I didn't miss something)
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
exec > >(exec tee -a $logfile) 2>&1
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap cleanup EXIT
sleep 10
rsync --progress -av --delete $directory1 /var/tmp/$directory2
In addition to set -e, I think you want set -E:
If set, any trap on ERR is inherited by shell functions, command substitutions, and commands executed in a sub‐shell environment. The ERR trap is normally not inherited in such cases.
Alternatively, instead of wrapping your commands in subshells use curly braces which will still give you the ability to redirect command outputs but will execute them in the current shell.
The interupt will be properly caught if you add INT to the trap
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash is trapping interrupts correctly. However, this does not anwer the question, why the script traps on exit if sleep is interupted, nor why it does not trigger on rsync, but makes the script work as it is supposed to. Hope this helps.
Your shell might be configured to exit on error:
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
If you interrupt sleep (^C) then the subshell will exit due to set -e and print woah in the process.
Also, slightly unrelated: your trap - EXIT is in a subshell (explicitly), so it won't have an effect after the cleanup function returns
It's pretty clear from experimentation that rsync behaves like other tools such as ping and do not inherit signals from the calling Bash parent.
So you have to get a little creative with this and do something like the following:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root#host.mydom.com:/tmp/. &
wait
echo FIN
Now when I run it:
$ ./rsync.bash
X11 forwarding request failed
building file list ... done
LargeTestFile.500M
^C^C^C^C^C^C^C^C^C^C
sent 509984 bytes received 42 bytes 92732.00 bytes/sec
total size is 524288000 speedup is 1027.96
FIN
And we can see the file did fully transfer:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
How it works
The trick here is we're telling Bash via set -m to disable job controls on any background jobs within it. We're then backgrounding the rsync and then running a wait command which will wait on the last run command, rsync, until it's complete.
We then guard the entire script with the trap '' SIGINT SIGTERM EXIT.
References
https://access.redhat.com/solutions/360713
https://access.redhat.com/solutions/1539283

Resources