Bash: Maybe it takes double Ctrl-c to exit the script?

Final goal: BASH script, waiting for the completion of background jobs, is not interrupted at the first Ctrl-c ; instead, a second Ctrl-c required to exit.

I know well how the BASH -builtin trap works. You can:

  • Use it to completely ignore the signal (e.g. trap '' 2 ) ... or

  • Use it to execute arbitrary commands before the original signal function is allowed (for example, trap cmd 2 , where cmd is executed before the parent script is interrupted due to SIGINT ).

So, the question boils down to the following:

How can I effectively combine 1 and 2 , i.e. prevent the end result that will lead to a signal ( 1 - for example, stop script cancellation due to SIGINT ), and also do something else ( 2 ), for example, increase the counter, check the counter and conditionally either print a warning or exit).

Simply put:

How can I make a signal, do something else completely; don't just insert a task before it does its job.

Here is a sample code to demonstrate what I'm aiming for; however, it certainly does not work, because trap can only execute 1 or 2 on top.

 #!/bin/bash declare -i number_of_times_trap_triggered cleanup_bg_jobs() { number_of_times_trap_triggered+=1 if [[ ${number_of_times_trap_triggered} -eq 1 ]]; then echo "There are background jobs still running" echo "Hit Ctrl-c again to cancel all bg jobs & quit" else echo "Aborting background jobs" for pid in ${bg_jobs}; do echo " Killing ${pid}"; kill -9 ${pid}; done fi } f() { sleep 5m; } trap cleanup_bg_jobs 2 bg_jobs= for job in 1 2 3; do f & bg_jobs+=" $!" done wait 

So this is the result that you get when you press Ctrl-c once.

 [rsaw:~]$ ./zax ^CThere are background jobs still running Hit Ctrl-c again to cancel all bg jobs & quit [rsaw:~]$ ps axf|tail -6 24569 pts/3 S 0:00 /bin/bash ./zax 24572 pts/3 S 0:00 \_ sleep 5m 24570 pts/3 S 0:00 /bin/bash ./zax 24573 pts/3 S 0:00 \_ sleep 5m 24571 pts/3 S 0:00 /bin/bash ./zax 24574 pts/3 S 0:00 \_ sleep 5m 

Of course, I could change this to clear the tasks on the first Ctrl-c , but that’s not what I want. I want to stop BASH from quiting after starting the first trap ... until it is called a second time.

PS: The target platform is Linux (I didn't care about POSIX compliance) using BASH v4 +

+6
source share
4 answers

A colleague (Greg) just gave me a solution that ... well, I can't believe I didn't think about it at first.

"My approach ... would be to lay it down long enough, perhaps forever, using a function that just never comes back or something else (another wait?) So that the second handler can do its job correctly."

For the record, wait does not work here. (Recursive.) However, adding the sleep command to my original code function cleanup_bg_jobs() will take care of that .. but it will lead to orphaned processes. So I used process groups to make sure that all the children from the script are really killed. A simplified example for posterity:

 #!/bin/bash declare -i count= handle_interrupt() { count+=1 if [[ ${count} -eq 1 ]]; then echo "Background jobs still running" echo "Hit Ctrl-c again to cancel all bg jobs & quit" sleep 1h else echo "Aborting background jobs" pkill --pgroup 0 fi } f() { tload &>/dev/null; } trap handle_interrupt 2 for job in 1 2 3; do f & done wait 
+1
source

I did something like here , and it basically breaks down into this:

  ATTEMPT=0 handle_close() { if [ $ATTEMPT -eq 0 ]; then ATTEMPT=1 echo "Shutdown." else echo "Already tried to shutdown. Killing." exit 0 fi } trap handle_close SIGINT SIGTERM 

You can set a variable in your handler so that you can check it again at the next capture.

+4
source

I had a slightly different use case, and I wanted to leave the solution here, since Google brought me to this topic. You can continue to execute the command and allow the user to restart it with CTRL+C and kill it with double CTRL+C as follows:

 trap_ctrlC() { echo "Press CTRL-C again to kill. Restarting in 2 second" sleep 2 || exit 1 } trap trap_ctrlC SIGINT SIGTERM while true; do ... your stuff here ... done 
+3
source
  1. Use it to execute arbitrary commands before the original signal function is enabled (e.g. trap cmd 2, where cmd is run before the parent script is interrupted due to SIGINT)

Invalid in italics part above. The trap handler starts instead , allowing SIGINT (or something else) to interrupt the process. More precisely:

  • the default action of SIGINT (and most, but not all other signals) is to terminate the process
  • trap "command" SIGINT causes command to run instead of (not so good) the default action

Thus, with the SIGINT handler installed, SIGINT does not interrupt the entire script. But he does interrupt the wait command. When the trap handler ends, the script resumes after wait , i.e. Falls from the end and ends normally. You can see this by adding the debug code:

 echo Waiting wait echo Back from wait exit 55 # Arbitrary value that wouldn't otherwise occur 

This version produces the following:

 $ foo Waiting ^CThere are background jobs still running Hit Ctrl-c again to cancel all bg jobs & quit back from wait $ echo $? 55 $ 

What you need to do is repeat wait after the handler returns. This version:

 #!/bin/bash declare -i number_of_times_trap_triggered cleanup_bg_jobs() { number_of_times_trap_triggered+=1 if [[ ${number_of_times_trap_triggered} -eq 1 ]]; then echo "There are background jobs still running" echo "Hit Ctrl-c again to cancel all bg jobs & quit" else echo "Aborting background jobs" for pid in ${bg_jobs}; do echo " Killing ${pid}"; kill -9 ${pid}; done exit 1 fi } f() { sleep 5m; } trap cleanup_bg_jobs 2 bg_jobs= for job in 1 2 3; do f & bg_jobs+=" $!" done while [ 1 ]; do echo Waiting wait echo Back from wait done 

does according to your request:

 $ ./foo Waiting ^CThere are background jobs still running Hit Ctrl-c again to cancel all bg jobs & quit Back from wait Waiting ^CAborting background jobs Killing 24154 Killing 24155 Killing 24156 $ 

Notes:

  • I left in the debugging material; obviously you would remove it into production
  • Now the handler executes exit 1 after destroying the subprocesses. What breaks out of an infinite main loop
0
source

Source: https://habr.com/ru/post/972461/


All Articles