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: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
:(Explanation: first we declare
pid
andstatus
as integers, so we don't have to escape them later on. After setting the trap forSIGINT
to a previously defined user functionctrl_c
, we run the user-supplied command in background and store itsPID
inpid
. Wewait
forpid
's completion in an infinite loop becausewait
will also break on trappedSIGINT
, in which case it exits with status>128
. We loop untilwait
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:When run, you get the expected result: