Is it possible in bash to intercept a SIGINT, do something, and then ignore it (keep bash running).
I know that I can ignore the SIGINT with
trap '' SIGINT
And I can also do something on the sigint with
trap handler SIGINT
But that will still stop the script after the handler
executes. E.g.
#!/bin/bash handler() { kill -s SIGINT $PID } program & PID=$! trap handler SIGINT wait $PID #do some other cleanup with results from program
When I press ctrl+c, the SIGINT to program will be sent, but bash will skip the wait
BEFORE program was properly shut down and created its output in its signal handler.
Using @suspectus answer I can change the wait $PID
to:
while kill -0 $PID > /dev/null 2>&1 do wait $PID done
This actually works for me I am just not 100% sure if this is 'clean' or a 'dirty workaround'.
trap will return from the handler, but after the command called when the handler was invoked.
So the solution is a little clumsy but I think it does what is required. trap handler INT
also will work.
trap 'echo "Be patient"' INT for ((n=20; n; n--)) do sleep 1 done
The short answer: SIGINT in bash can be caught, handled and then ignored, assumed that "ignored" here means that bash continues to run the script. The wanted actions of the handler can even be postponed to build a kind of "transaction" so that SIGINT will be fired (or "ignored") AFTER a group of statements have done their work.
But since the above example touches many aspects of bash (foreground vs. background behavior, trap and wait) AND 8 years went away since then, the solution discussed here may not immediately work on all systems without further finetuning.
The solution discussed here was successfully tested on a "Linux mint-mate 5.4.0-73-generic x86_64" system with "GNU bash, Version 4.4.20(1)-release":
wait
shell builtin command IS DESIGNED to be interruptable. But one can examine the exit status of wait
, which is 128 + signal number = 130 (in the case of SIGINT). So if you want to trick around and wait til the background is process really finished, one can also do something like this:wait ${programPID} while [ $? -ge 128 ]; do # 1st opportunity to place your **handler actions** is here wait ${programPID} done
But let it also said that we ran into a bug/feature while testing all of this. The problem was that wait
kept on returning 130 even after the process in the background was no longer there. The documentation says that wait
will return 127 in the case of a false process id, but this did not happen in our tests. Keep in mind to check the existence of the background process before running the wait
command in the while loop, if you also run into this problem.
program
, which simply counts down from 5 to 0 and also tee's its output to a file named program.out. The while loop here is considered as a "transaction", which shall not be disturbed by SIGINT. And one last comment: This code does NOT ignore SIGINT after doing postponed actions, but instead restores the old SIGINT handler and raises a SIGINT:#!/bin/bash rm -f program.out # Will be set to 1 by the SIGINT ignoring/postponing handler declare -ig SIGINT_RECEIVED=0 # On <CTRL>+C or "kill -s SIGINT $$" set flag for [later|postponed] examination function _set_SIGINT_RECEIVED { SIGINT_RECEIVED=1 } # Remember current SIGINT handler old_SIGINT_handler=$(trap -p SIGINT) # Prepare for later restoration via ${old_SIGINT_handler} old_SIGINT_handler=${old_SIGINT_handler:-trap - SIGINT} # Start your "transaction", which should NOT be disturbed by SIGINT trap -- '_set_SIGINT_RECEIVED' SIGINT count=5 echo $count | tee -a program.out while (( count-- )); do sleep 1 echo $count | tee -a program.out done # End of your "transaction" # Look whether SIGINT was received if [ ${SIGINT_RECEIVED} -eq 1 ]; then # Your **handler actions** are here echo "SIGINT was received during transaction..." | tee -a program.out echo "... doing postponed work now..." | tee -a program.out echo "... restoring old SIGINT handler and sending SIGINT" | tee -a program.out echo "program finished after SIGINT postponed." | tee -a program.out ${old_SIGINT_handler} kill -s SIGINT $$ fi echo "program finished without having received SIGINT." | tee -a program.out
But let it also be said here that we ran into problems after sending program
in the background. The problem was that program
inherited a trap '' SIGINT
which means that SIGINT was generally ignored and program
was NOT able to set another handler via trap -- '_set_SIGINT_RECEIVED' SIGINT
.
program
in a subshell and sending this subshell in the background, as you will see now in the MAIN
script example, which runs in the foreground. And one last comment also: In this script you can decide via variable ignore_SIGINT_after_handling
whether to finally ignore SIGINT and continue to run the script OR to execute the default SIGINT behavior after your handler action has finished its work:#!/bin/bash # Will be set to 1 by the SIGINT ignoring/postponing handler declare -ig SIGINT_RECEIVED=0 # On <CTRL>+C or "kill -s SIGINT $$" set flag for later examination function _set_SIGINT_RECEIVED { SIGINT_RECEIVED=1 } # Set to 1 if you want to keep bash running after handling SIGINT in a particular way # or to 0 (or any other value) to run original SIGINT action after postponing SIGINT ignore_SIGINT_after_handling=1 # Remember current SIGINT handler old_SIGINT_handler=$(trap -p SIGINT) # Prepare for later restoration via ${old_SIGINT_handler} old_SIGINT_handler=${old_SIGINT_handler:-trap - SIGINT} # Start your "transaction", which should NOT be disturbed by SIGINT trap -- '_set_SIGINT_RECEIVED' SIGINT # Do your work, for eample (./program) & programPID=$! wait ${programPID} while [ $? -ge 128 ]; do # 1st opportunity to place a part of your **handler actions** is here # i.e. send SIGINT to ${programPID} and make sure that it is only sent once # even if MAIN receives more SIGINT's during this loop wait ${programPID} done # End of your "transaction" # Look whether SIGINT was received if [ ${SIGINT_RECEIVED} -eq 1 ]; then # Your postponed **handler actions** are here echo -e "\nMAIN is doing postponed work now..." if [ ${ignore_SIGINT_after_handling} -eq 1 ]; then echo "... and continuing with normal program execution..." else echo "... and restoring old SIGINT handler and sending SIGINT via 'kill -s SIGINT \$\$'" ${old_SIGINT_handler} kill -s SIGINT $$ fi fi # Restore "old" SIGINT behaviour ${old_SIGINT_handler} # Prepare for next "transaction" SIGINT_RECEIVED=0 echo "" echo "This message has to be shown in the case of normal program execution" echo "as well as after a caught and handled and then ignored SIGINT" echo "End of MAIN script received"
Hope this helps a bit. Shall everybody have a good time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With