I would like to catch a signal ( let's focus on INT
) and not finish the currently running command so that it finishes after running signal handler.
Let's say I have the following script:
#!/bin/bash
READY=0
ctrl_c(){
READY=1
}
trap ctrl_c INT
while true; do
echo first sleep
sleep 1
echo second sleep
sleep 1
if [ $READY -ne 0 ] ; then
echo -e "\n$READY"
echo Exit
exit
fi
done
When I hit CtrlC after first sleep
then I get immediately dropped down to second sleep
, second sleep 1
and script finished.
When I hit CtrlC after second sleep
(while second sleep 1
is being run) then I get immediately script termination.
How can I ensure that this script runs in whole seconds
that is: sleep
s are not terminated?
You can do it by exploiting the fact that only the foreground process will receive the signal. So, you can run your command in background, trap in foreground and wait
until the command exits.
But in addition, since wait
will also exit on reception of a trapped signal:
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.
we'll have to wait in a loop, aborting only on exit status below (or equal to) 128 -- assuming the command will never exit with status above 128. If this assumption is not valid in your case, then this solution won't work.
We can wrap all this in a function, let's call it trapwrap
:
trapwrap() {
declare -i pid status=255
# set the trap for the foreground process
trap ctrl_c INT
# run the command in background
"$@" & pid=$!
# wait until bg command finishes, handling interruptions by trapped signals
while (( status > 128 )); do
wait $pid
status=$?
done
# restore the trap
trap - INT
# return the command exit status
return $status
}
(Explanation: first we declare pid
and status
as integers, so we don't have to escape them later on. After setting the trap for SIGINT
to a previously defined user function ctrl_c
, we run the user-supplied command in background and store its PID
in pid
. We wait
for pid
's completion in an infinite loop because wait
will also break on trapped SIGINT
, in which case it exits with status >128
. We loop until wait
exits with <=128
, as that signifies the exit status is actually coming from the background process which just finished. Finally, we restore the trap and return the command exit status.)
Then, we can use the trapwrap
like this:
#!/bin/bash
READY=0
ctrl_c() {
READY=1
}
while true; do
echo first sleep
trapwrap sleep 1
echo second sleep
trapwrap sleep 1
if [ $READY -ne 0 ] ; then
echo -e "\n$READY"
echo Exit
exit
fi
done
When run, you get the expected result:
first sleep
^C^C^C^Csecond sleep
^C^C
1
Exit