Unable to stop a bash script with Ctrl+C

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
32
down vote

favorite
11












I wrote a simple bash script with a loop for printing the date and ping to a remote machine:



#!/bin/bash
while true; do
# *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
echo -e "n*** DATE:" `date` " ***";
echo "********************************************"
ping -c5 $1;
done


When I run it from a terminal I am not able to stop it with Ctrl+C.
It seems it sends the ^C to the terminal, but the script does not stop.



MacAir:~ tomas$ ping-tester.bash www.google.com

*** DATE: Thu Sep 17 23:58:42 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

*** DATE: Thu Sep 17 23:58:48 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms


No matter how many times I press it or how fast I do it. I am not able to stop it.

Make the test and realize by yourself.



As a side solution, I am stopping it with Ctrl+Z, that stops it and then kill %1.



What is exactly happening here with ^C?







share|improve this question


























    up vote
    32
    down vote

    favorite
    11












    I wrote a simple bash script with a loop for printing the date and ping to a remote machine:



    #!/bin/bash
    while true; do
    # *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
    echo -e "n*** DATE:" `date` " ***";
    echo "********************************************"
    ping -c5 $1;
    done


    When I run it from a terminal I am not able to stop it with Ctrl+C.
    It seems it sends the ^C to the terminal, but the script does not stop.



    MacAir:~ tomas$ ping-tester.bash www.google.com

    *** DATE: Thu Sep 17 23:58:42 CEST 2015 ***
    ********************************************
    PING www.google.com (216.58.211.228): 56 data bytes
    64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
    64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
    ^C <= That is Ctrl+C press
    --- www.google.com ping statistics ---
    2 packets transmitted, 2 packets received, 0.0% packet loss
    round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

    *** DATE: Thu Sep 17 23:58:48 CEST 2015 ***
    ********************************************
    PING www.google.com (216.58.211.196): 56 data bytes
    64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
    64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms


    No matter how many times I press it or how fast I do it. I am not able to stop it.

    Make the test and realize by yourself.



    As a side solution, I am stopping it with Ctrl+Z, that stops it and then kill %1.



    What is exactly happening here with ^C?







    share|improve this question
























      up vote
      32
      down vote

      favorite
      11









      up vote
      32
      down vote

      favorite
      11






      11





      I wrote a simple bash script with a loop for printing the date and ping to a remote machine:



      #!/bin/bash
      while true; do
      # *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
      echo -e "n*** DATE:" `date` " ***";
      echo "********************************************"
      ping -c5 $1;
      done


      When I run it from a terminal I am not able to stop it with Ctrl+C.
      It seems it sends the ^C to the terminal, but the script does not stop.



      MacAir:~ tomas$ ping-tester.bash www.google.com

      *** DATE: Thu Sep 17 23:58:42 CEST 2015 ***
      ********************************************
      PING www.google.com (216.58.211.228): 56 data bytes
      64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
      64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
      ^C <= That is Ctrl+C press
      --- www.google.com ping statistics ---
      2 packets transmitted, 2 packets received, 0.0% packet loss
      round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

      *** DATE: Thu Sep 17 23:58:48 CEST 2015 ***
      ********************************************
      PING www.google.com (216.58.211.196): 56 data bytes
      64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
      64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms


      No matter how many times I press it or how fast I do it. I am not able to stop it.

      Make the test and realize by yourself.



      As a side solution, I am stopping it with Ctrl+Z, that stops it and then kill %1.



      What is exactly happening here with ^C?







      share|improve this question














      I wrote a simple bash script with a loop for printing the date and ping to a remote machine:



      #!/bin/bash
      while true; do
      # *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
      echo -e "n*** DATE:" `date` " ***";
      echo "********************************************"
      ping -c5 $1;
      done


      When I run it from a terminal I am not able to stop it with Ctrl+C.
      It seems it sends the ^C to the terminal, but the script does not stop.



      MacAir:~ tomas$ ping-tester.bash www.google.com

      *** DATE: Thu Sep 17 23:58:42 CEST 2015 ***
      ********************************************
      PING www.google.com (216.58.211.228): 56 data bytes
      64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
      64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
      ^C <= That is Ctrl+C press
      --- www.google.com ping statistics ---
      2 packets transmitted, 2 packets received, 0.0% packet loss
      round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

      *** DATE: Thu Sep 17 23:58:48 CEST 2015 ***
      ********************************************
      PING www.google.com (216.58.211.196): 56 data bytes
      64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
      64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms


      No matter how many times I press it or how fast I do it. I am not able to stop it.

      Make the test and realize by yourself.



      As a side solution, I am stopping it with Ctrl+Z, that stops it and then kill %1.



      What is exactly happening here with ^C?









      share|improve this question













      share|improve this question




      share|improve this question








      edited Sep 18 '15 at 2:55









      chicks

      7971621




      7971621










      asked Sep 17 '15 at 23:29









      nephewtom

      315139




      315139




















          8 Answers
          8






          active

          oldest

          votes

















          up vote
          21
          down vote













          What happens is that both bash and ping receive the SIGINT (bash being not interactive, both ping and bash run in the same process group which has been created and set as the terminal's foreground process group by the interactive shell you ran that script from).



          However, bash handles that SIGINT asynchronously, only after the currently running command has exited. bash only exits upon receiving that SIGINT if the currently running command dies of a SIGINT (i.e. its exit status indicates that it has been killed by SIGINT).



          $ bash -c 'sh -c "trap exit 0 INT; sleep 10; :"; echo here'
          ^Chere


          Above, bash, sh and sleep receive SIGINT when I press Ctrl-C, but sh exits normally with a 0 exit code, so bash ignores the SIGINT, which is why we see "here".



          ping, at least the one from iputils, behaves like that. When interrupted, it prints statistics and exits with a 0 or 1 exit status depending on whether or not its pings were replied. So, when you press Ctrl-C while ping is running, bash notes that you've pressed Ctrl-C in its SIGINT handlers, but since ping exits normally, bash does not exit.



          If you add a sleep 1 in that loop and press Ctrl-C while sleep is running, because sleep has no special handler on SIGINT, it will die and report to bash that it died of a SIGINT, and in that case bash will exit (it will actually kill itself with SIGINT so as to report the interruption to its parent).



          As to why bash behaves like that, I'm not sure and I note the behaviour is not always deterministic. I've just asked the question on the bash development mailing list (Update: @Jilles has now nailed down the reason in his answer).



          The only other shell I found that behave similarly is ksh93 (Update, as mentioned by @Jilles, so does FreeBSD sh). There, SIGINT seems to be plainly ignored. And ksh93 exits whenever a command is killed by SIGINT.



          You get the same behaviour as bash above but also:



          ksh -c 'sh -c "kill -INT $$"; echo test'


          Doesn't output "test". That is, it exits (by killing itself with SIGINT there) if the command it was waiting for dies of SIGINT, even if it, itself didn't receive that SIGINT.



          A work around would be to do add a:



          trap 'exit 130' INT


          At the top of the script to force bash to exit upon receiving a SIGINT (note that in any case, SIGINT won't be processed synchronously, only after the currently running command has exited).



          Ideally, we'd want to report to our parent that we died of a SIGINT (so that if it's another bash script for instance, that bash script is also interrupted). Doing an exit 130 is not the same as dying of SIGINT (though some shells will set $? to same value for both cases), however it's often used to report a death by SIGINT (on systems where SIGINT is 2 which is most).



          However for bash, ksh93 or FreeBSD sh, that doesn't work. That 130 exit status is not considered as a death by SIGINT and a parent script would not abort there.



          So, a possibly better alternative would be to kill ourself with SIGINT upon receiving SIGINT:



          trap '
          trap - INT # restore default INT handler
          kill -s INT "$$"
          ' INT





          share|improve this answer


















          • 1




            jilles’s answer explains the “why”.  As an illustrative example, consider  for f in *.txt; do vi "$f"; cp "$f" newdir; done. If the user types Ctrl+C while editing one of the files, vi just displays a message.  It seems reasonable that the loop should continue after the user finishes editing the file.  (And yes, I know that you could say vi *.txt; cp *.txt newdir; I’m just submitting the for loop as an example.)
            – Scott
            Sep 19 '15 at 18:51











          • @Scott, good point. Though vi (well vim at least) does disable tty isig when editing (it does not abviously when you run :!cmd though, and that would very much apply in that case).
            – Stéphane Chazelas
            Sep 19 '15 at 19:39










          • @Tim, see my edit for correction on your edit.
            – Stéphane Chazelas
            Aug 10 '17 at 5:53


















          up vote
          11
          down vote













          The explanation is that bash implements WCE (wait and cooperative exit) for SIGINT and SIGQUIT per http://www.cons.org/cracauer/sigint.html. That means that if bash receives SIGINT or SIGQUIT while waiting for a process to exit, it will wait until the process exits and will exit itself if the process exited on that signal. This ensures that programs that use SIGINT or SIGQUIT in their user interface will work as expected (if the signal did not cause the program to terminate, the script will continue normally).



          A downside appears with programs that catch SIGINT or SIGQUIT but then terminate because of it but using a normal exit() instead of by resending the signal to themselves. It may not be possible to interrupt scripts that call such programs. I think the real fix there is in such programs such as ping and ping6.



          Similar behaviour is implemented by ksh93 and FreeBSD's /bin/sh, but not by most other shells.






          share|improve this answer




















          • Thanks, that makes a lot of sense. I note FreeBSD sh doesn't abort either when the cmd exits with exit(130) either, which is a common way to report the death by SIGINT of a child (mksh does a exit(130) for instance if you interrupt mksh -c 'sleep 10;:').
            – Stéphane Chazelas
            Sep 19 '15 at 19:35

















          up vote
          5
          down vote













          As you surmise, this is due to the SIGINT being sent to the subordinate process, and the shell continuing on after that process exits.



          To handle this in a better way, you can check the exit status of the commands which are running. The Unix return code encodes both the method by which a process exited (system call or signal) and what value was passed to exit() or what signal terminated the process. This is all rather complicated, but the quickest way of using it is to know that a process that was terminated by signal will have a non-zero return code. Thus, if you check the return code in your script, you can exit yourself if the child process was terminated, removing the need for inelegancies like unnecessary sleep calls. A quick way to do this throughout your script is to use set -e, though it may require a few tweaks for commands whose exit status is an expected nonzero.






          share|improve this answer




















          • Set -e does not work correctly in bash unless you are using bash-4
            – schily
            Sep 18 '15 at 9:40










          • What's meant "does not work correctly"? I've used it on bash 3 successfully, but there's probably some edge cases.
            – Tom Hunt
            Sep 18 '15 at 15:03










          • In a few simple cases, bash3 did exit on error. This did however not happen in the general case. As a typical result, make did not stop when creating a target failed and this was from a makefile that worked on a list of targets in subdirectories. David Korn and I had to mail many weeks with the bash maintainer to convince him to fix the bug for bash4.
            – schily
            Sep 18 '15 at 15:37






          • 4




            Note that the problem here is that ping returns with a 0 exit status upon receiving SIGINT and that bash then ignores the SIGINT it received itself if that's the case. Adding a "set -e" or check the exit status won't help here. Adding an explicit trap on SIGINT would help.
            – Stéphane Chazelas
            Sep 18 '15 at 15:42

















          up vote
          4
          down vote













          The terminal notices the control-c and sends an INT signal to the foreground process group, which here includes the shell, as ping has not created a new foreground process group. This is easy to verify by trapping INT.



          #! /bin/bash
          trap 'echo oh, I am slain; exit' INT
          while true; do
          ping -c5 127.0.0.1
          done


          If the command being run has created a new foreground process group, then the control-c will go to that process group, and not to the shell. In that case, the shell will need to inspect exit codes, as it will not be signalled by the terminal.



          (INT handling in shells can be fabulously complicated, by the way, as the shell sometimes needs to ignore the signal, and sometimes not. Source dive if curious, or ponder: tail -f /etc/passwd; echo foo)






          share|improve this answer






















          • In this case, the problem is not signal handling but the fact that bash does jobcontrol in the script although it should not, see my answer for more information
            – schily
            Sep 18 '15 at 12:54










          • For the SIGINT to go to the new process group, the command would also have to do an ioctl() to the terminal to make it the foreground process group of the terminal. ping has no reason to start a new process group here and the version of ping (iputils on Debian) with which I can reproduce the OP's problem does not create a process group.
            – Stéphane Chazelas
            Sep 18 '15 at 16:04






          • 1




            Note that it's not the terminal that sends the SIGINT, it's the line discipline of the tty device (the driver (code in the kernel) of the /dev/ttysomething device) upon receiving an unescaped (by lnext usually ^V) ^C character from the terminal.
            – Stéphane Chazelas
            Sep 18 '15 at 16:17

















          up vote
          2
          down vote













          Well, I tried to add a sleep 1 to the bash script, and bang!

          Now I'm able to stop it with two Ctrl+C.



          When pressing Ctrl+C, a SIGINT signal is sent to the process currently executed, which command was run inside the loop. Then, the subshell process continues executing the next command in the loop, that starts another process.
          To be able to stop the script it is necessary to send two SIGINT signals, one to interrupt the current command in execution and one to interrupt the subshell process.



          In the script without the sleep call, pressing Ctrl+C really fast and many times does not seem to work, and it is not possible to exit the loop. My guess is that pressing twice is not enough fast to make it just in the right moment between the interruption of current executed process and the start of the next one. Every Ctrl+C pressed will send a SIGINT to a process executed inside the loop, but neither to the subshell.



          In the script with sleep 1, this call will suspend the execution for one second, and when interrupted by the first Ctrl+C (first SIGINT), the subshell will take more time to execute the next command. So now, the second Ctrl+C (second SIGINT) will go to the subshell, and the script execution will end.






          share|improve this answer






















          • You are mistaken, on a correctly working shell, a single ^C is sufficient see my answer for the background.
            – schily
            Sep 18 '15 at 12:51











          • Well, considering you've been down voted, and currently your answer has score -1, I'm not very convinced I should take your answer seriously.
            – nephewtom
            Sep 18 '15 at 22:44










          • The fact that some people downvote is not always related to the quality of a reply. If you need to type two times ^c, you definitely are a victim of a bash bug. Did you try a different shell? Did you try the real Bourne Shell?
            – schily
            Sep 19 '15 at 7:20










          • If the shell if working correctly, it runs everything from a script in the same process group and then a single ^c is sufficient.
            – schily
            Sep 19 '15 at 7:26






          • 1




            The behaviour @nephewtom describes in this answer can be explained by different commands in the script behaving differently when they receive Ctrl-C. If a sleep is present, it's overwhelmingly likely that Ctrl-C will be received while the sleep is executing (assuming everything else in the loop is fast). The sleep is killed, with exit value 130. The parent of sleep, a shell, notices that sleep was killed by sigint, and exits. But if the script contains no sleep, then the Ctrl-C goes to ping instead, which reacts by exiting with 0, so the parent shell carries on executing the next command.
            – Jonathan Hartley
            Oct 7 '15 at 15:15

















          up vote
          1
          down vote













          Try this:



          #!/bin/bash
          while true; do
          echo "Ctrl-c works during sleep 5"
          sleep 5
          echo "But not during ping -c 5"
          ping -c 5 127.0.0.1
          done


          Now change the first line to:



          #!/bin/sh


          and try again - see if the ping is now interruptible.






          share|improve this answer



























            up vote
            0
            down vote













            pgrep -f process_name > any_file_name
            sed -i 's/^/kill /' any_file_name
            chmod 777 any_file_name
            ./any_file_name


            for example pgrep -f firefox will grep the PID of running firefox and will save this PID to a file called any_file_name. 'sed' command will add the kill in the beginning of the PID number in 'any_file_name' file. Third line will any_file_name file executable. Now forth line will kill the PID available in the file any_file_name. Writing the above four lines in a file and executing that file can do the Control-C. Working absolutely fine for me.






            share|improve this answer





























              up vote
              -3
              down vote













              You are a victim of a well known bash bug. Bash does jobcontrol for scripts which is a mistake.



              What happens is that bash runs the external programs in a different process group than it uses for the script itself. As the TTY processgroup is set to the processgroup of the current foreground process, only this foreground process is killed and the loop in the shell script continues.



              To verify: Fetch and compile a recent Bourne Shell that implements pgrp(1) as a builtin program, then add a /bin/sleep 100 (or /usr/bin/sleep depending on your platform) to the script loop and then start the Bourne Shell. After you used ps(1) to obtain the process IDs for the sleep command and the bash that runs the script, call pgrp <pid> and replace "< pid >" by the process ID of the sleep and the bash that runs the script. You will see different process group IDs. Now call something like pgrp < /dev/pts/7 (replace the tty name by the tty used by the script) to obtain the current tty process group. The TTY process group equals the process group of the sleep command.



              To fix: use a different shell.



              The recent Bourne Shell sources are in my schily tools package which you can find here:



              http://sourceforge.net/projects/schilytools/files/






              share|improve this answer






















              • What version of bash is that? AFAIK bash only does that if you pass the -m or -i option.
                – Stéphane Chazelas
                Sep 18 '15 at 14:00










              • It seems that this does no longer apply to bash4 but when the OP has such problems, he seems to use bash3
                – schily
                Sep 18 '15 at 14:08










              • Can't reproduce with bash3.2.48 nor bash 3.0.16 nor bash-2.05b (tried with bash -c 'ps -j; ps -j; ps -j').
                – Stéphane Chazelas
                Sep 18 '15 at 15:00










              • This definitely happens when you call bash as /bin/sh -ce. I had to add an ugly workaround into smake that explicitely kills the process group for a currently running command in order to permit ^C to abort a layered make call. Did you check whether bash changed the process group from the process group id it was initiated with?
                – schily
                Sep 18 '15 at 15:41











              • ARGV0=sh bash -ce 'ps -j; ps -j; ps -j' does report the same pgid for ps and bash in all 3 ps invocations. (ARGV0=sh is zsh way to pass argv[0]).
                – Stéphane Chazelas
                Sep 18 '15 at 16:01










              Your Answer







              StackExchange.ready(function()
              var channelOptions =
              tags: "".split(" "),
              id: "106"
              ;
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function()
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled)
              StackExchange.using("snippets", function()
              createEditor();
              );

              else
              createEditor();

              );

              function createEditor()
              StackExchange.prepareEditor(
              heartbeatType: 'answer',
              convertImagesToLinks: false,
              noModals: false,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: null,
              bindNavPrevention: true,
              postfix: "",
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              );



              );








               

              draft saved


              draft discarded


















              StackExchange.ready(
              function ()
              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f230421%2funable-to-stop-a-bash-script-with-ctrlc%23new-answer', 'question_page');

              );

              Post as a guest






























              8 Answers
              8






              active

              oldest

              votes








              8 Answers
              8






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes








              up vote
              21
              down vote













              What happens is that both bash and ping receive the SIGINT (bash being not interactive, both ping and bash run in the same process group which has been created and set as the terminal's foreground process group by the interactive shell you ran that script from).



              However, bash handles that SIGINT asynchronously, only after the currently running command has exited. bash only exits upon receiving that SIGINT if the currently running command dies of a SIGINT (i.e. its exit status indicates that it has been killed by SIGINT).



              $ bash -c 'sh -c "trap exit 0 INT; sleep 10; :"; echo here'
              ^Chere


              Above, bash, sh and sleep receive SIGINT when I press Ctrl-C, but sh exits normally with a 0 exit code, so bash ignores the SIGINT, which is why we see "here".



              ping, at least the one from iputils, behaves like that. When interrupted, it prints statistics and exits with a 0 or 1 exit status depending on whether or not its pings were replied. So, when you press Ctrl-C while ping is running, bash notes that you've pressed Ctrl-C in its SIGINT handlers, but since ping exits normally, bash does not exit.



              If you add a sleep 1 in that loop and press Ctrl-C while sleep is running, because sleep has no special handler on SIGINT, it will die and report to bash that it died of a SIGINT, and in that case bash will exit (it will actually kill itself with SIGINT so as to report the interruption to its parent).



              As to why bash behaves like that, I'm not sure and I note the behaviour is not always deterministic. I've just asked the question on the bash development mailing list (Update: @Jilles has now nailed down the reason in his answer).



              The only other shell I found that behave similarly is ksh93 (Update, as mentioned by @Jilles, so does FreeBSD sh). There, SIGINT seems to be plainly ignored. And ksh93 exits whenever a command is killed by SIGINT.



              You get the same behaviour as bash above but also:



              ksh -c 'sh -c "kill -INT $$"; echo test'


              Doesn't output "test". That is, it exits (by killing itself with SIGINT there) if the command it was waiting for dies of SIGINT, even if it, itself didn't receive that SIGINT.



              A work around would be to do add a:



              trap 'exit 130' INT


              At the top of the script to force bash to exit upon receiving a SIGINT (note that in any case, SIGINT won't be processed synchronously, only after the currently running command has exited).



              Ideally, we'd want to report to our parent that we died of a SIGINT (so that if it's another bash script for instance, that bash script is also interrupted). Doing an exit 130 is not the same as dying of SIGINT (though some shells will set $? to same value for both cases), however it's often used to report a death by SIGINT (on systems where SIGINT is 2 which is most).



              However for bash, ksh93 or FreeBSD sh, that doesn't work. That 130 exit status is not considered as a death by SIGINT and a parent script would not abort there.



              So, a possibly better alternative would be to kill ourself with SIGINT upon receiving SIGINT:



              trap '
              trap - INT # restore default INT handler
              kill -s INT "$$"
              ' INT





              share|improve this answer


















              • 1




                jilles’s answer explains the “why”.  As an illustrative example, consider  for f in *.txt; do vi "$f"; cp "$f" newdir; done. If the user types Ctrl+C while editing one of the files, vi just displays a message.  It seems reasonable that the loop should continue after the user finishes editing the file.  (And yes, I know that you could say vi *.txt; cp *.txt newdir; I’m just submitting the for loop as an example.)
                – Scott
                Sep 19 '15 at 18:51











              • @Scott, good point. Though vi (well vim at least) does disable tty isig when editing (it does not abviously when you run :!cmd though, and that would very much apply in that case).
                – Stéphane Chazelas
                Sep 19 '15 at 19:39










              • @Tim, see my edit for correction on your edit.
                – Stéphane Chazelas
                Aug 10 '17 at 5:53















              up vote
              21
              down vote













              What happens is that both bash and ping receive the SIGINT (bash being not interactive, both ping and bash run in the same process group which has been created and set as the terminal's foreground process group by the interactive shell you ran that script from).



              However, bash handles that SIGINT asynchronously, only after the currently running command has exited. bash only exits upon receiving that SIGINT if the currently running command dies of a SIGINT (i.e. its exit status indicates that it has been killed by SIGINT).



              $ bash -c 'sh -c "trap exit 0 INT; sleep 10; :"; echo here'
              ^Chere


              Above, bash, sh and sleep receive SIGINT when I press Ctrl-C, but sh exits normally with a 0 exit code, so bash ignores the SIGINT, which is why we see "here".



              ping, at least the one from iputils, behaves like that. When interrupted, it prints statistics and exits with a 0 or 1 exit status depending on whether or not its pings were replied. So, when you press Ctrl-C while ping is running, bash notes that you've pressed Ctrl-C in its SIGINT handlers, but since ping exits normally, bash does not exit.



              If you add a sleep 1 in that loop and press Ctrl-C while sleep is running, because sleep has no special handler on SIGINT, it will die and report to bash that it died of a SIGINT, and in that case bash will exit (it will actually kill itself with SIGINT so as to report the interruption to its parent).



              As to why bash behaves like that, I'm not sure and I note the behaviour is not always deterministic. I've just asked the question on the bash development mailing list (Update: @Jilles has now nailed down the reason in his answer).



              The only other shell I found that behave similarly is ksh93 (Update, as mentioned by @Jilles, so does FreeBSD sh). There, SIGINT seems to be plainly ignored. And ksh93 exits whenever a command is killed by SIGINT.



              You get the same behaviour as bash above but also:



              ksh -c 'sh -c "kill -INT $$"; echo test'


              Doesn't output "test". That is, it exits (by killing itself with SIGINT there) if the command it was waiting for dies of SIGINT, even if it, itself didn't receive that SIGINT.



              A work around would be to do add a:



              trap 'exit 130' INT


              At the top of the script to force bash to exit upon receiving a SIGINT (note that in any case, SIGINT won't be processed synchronously, only after the currently running command has exited).



              Ideally, we'd want to report to our parent that we died of a SIGINT (so that if it's another bash script for instance, that bash script is also interrupted). Doing an exit 130 is not the same as dying of SIGINT (though some shells will set $? to same value for both cases), however it's often used to report a death by SIGINT (on systems where SIGINT is 2 which is most).



              However for bash, ksh93 or FreeBSD sh, that doesn't work. That 130 exit status is not considered as a death by SIGINT and a parent script would not abort there.



              So, a possibly better alternative would be to kill ourself with SIGINT upon receiving SIGINT:



              trap '
              trap - INT # restore default INT handler
              kill -s INT "$$"
              ' INT





              share|improve this answer


















              • 1




                jilles’s answer explains the “why”.  As an illustrative example, consider  for f in *.txt; do vi "$f"; cp "$f" newdir; done. If the user types Ctrl+C while editing one of the files, vi just displays a message.  It seems reasonable that the loop should continue after the user finishes editing the file.  (And yes, I know that you could say vi *.txt; cp *.txt newdir; I’m just submitting the for loop as an example.)
                – Scott
                Sep 19 '15 at 18:51











              • @Scott, good point. Though vi (well vim at least) does disable tty isig when editing (it does not abviously when you run :!cmd though, and that would very much apply in that case).
                – Stéphane Chazelas
                Sep 19 '15 at 19:39










              • @Tim, see my edit for correction on your edit.
                – Stéphane Chazelas
                Aug 10 '17 at 5:53













              up vote
              21
              down vote










              up vote
              21
              down vote









              What happens is that both bash and ping receive the SIGINT (bash being not interactive, both ping and bash run in the same process group which has been created and set as the terminal's foreground process group by the interactive shell you ran that script from).



              However, bash handles that SIGINT asynchronously, only after the currently running command has exited. bash only exits upon receiving that SIGINT if the currently running command dies of a SIGINT (i.e. its exit status indicates that it has been killed by SIGINT).



              $ bash -c 'sh -c "trap exit 0 INT; sleep 10; :"; echo here'
              ^Chere


              Above, bash, sh and sleep receive SIGINT when I press Ctrl-C, but sh exits normally with a 0 exit code, so bash ignores the SIGINT, which is why we see "here".



              ping, at least the one from iputils, behaves like that. When interrupted, it prints statistics and exits with a 0 or 1 exit status depending on whether or not its pings were replied. So, when you press Ctrl-C while ping is running, bash notes that you've pressed Ctrl-C in its SIGINT handlers, but since ping exits normally, bash does not exit.



              If you add a sleep 1 in that loop and press Ctrl-C while sleep is running, because sleep has no special handler on SIGINT, it will die and report to bash that it died of a SIGINT, and in that case bash will exit (it will actually kill itself with SIGINT so as to report the interruption to its parent).



              As to why bash behaves like that, I'm not sure and I note the behaviour is not always deterministic. I've just asked the question on the bash development mailing list (Update: @Jilles has now nailed down the reason in his answer).



              The only other shell I found that behave similarly is ksh93 (Update, as mentioned by @Jilles, so does FreeBSD sh). There, SIGINT seems to be plainly ignored. And ksh93 exits whenever a command is killed by SIGINT.



              You get the same behaviour as bash above but also:



              ksh -c 'sh -c "kill -INT $$"; echo test'


              Doesn't output "test". That is, it exits (by killing itself with SIGINT there) if the command it was waiting for dies of SIGINT, even if it, itself didn't receive that SIGINT.



              A work around would be to do add a:



              trap 'exit 130' INT


              At the top of the script to force bash to exit upon receiving a SIGINT (note that in any case, SIGINT won't be processed synchronously, only after the currently running command has exited).



              Ideally, we'd want to report to our parent that we died of a SIGINT (so that if it's another bash script for instance, that bash script is also interrupted). Doing an exit 130 is not the same as dying of SIGINT (though some shells will set $? to same value for both cases), however it's often used to report a death by SIGINT (on systems where SIGINT is 2 which is most).



              However for bash, ksh93 or FreeBSD sh, that doesn't work. That 130 exit status is not considered as a death by SIGINT and a parent script would not abort there.



              So, a possibly better alternative would be to kill ourself with SIGINT upon receiving SIGINT:



              trap '
              trap - INT # restore default INT handler
              kill -s INT "$$"
              ' INT





              share|improve this answer














              What happens is that both bash and ping receive the SIGINT (bash being not interactive, both ping and bash run in the same process group which has been created and set as the terminal's foreground process group by the interactive shell you ran that script from).



              However, bash handles that SIGINT asynchronously, only after the currently running command has exited. bash only exits upon receiving that SIGINT if the currently running command dies of a SIGINT (i.e. its exit status indicates that it has been killed by SIGINT).



              $ bash -c 'sh -c "trap exit 0 INT; sleep 10; :"; echo here'
              ^Chere


              Above, bash, sh and sleep receive SIGINT when I press Ctrl-C, but sh exits normally with a 0 exit code, so bash ignores the SIGINT, which is why we see "here".



              ping, at least the one from iputils, behaves like that. When interrupted, it prints statistics and exits with a 0 or 1 exit status depending on whether or not its pings were replied. So, when you press Ctrl-C while ping is running, bash notes that you've pressed Ctrl-C in its SIGINT handlers, but since ping exits normally, bash does not exit.



              If you add a sleep 1 in that loop and press Ctrl-C while sleep is running, because sleep has no special handler on SIGINT, it will die and report to bash that it died of a SIGINT, and in that case bash will exit (it will actually kill itself with SIGINT so as to report the interruption to its parent).



              As to why bash behaves like that, I'm not sure and I note the behaviour is not always deterministic. I've just asked the question on the bash development mailing list (Update: @Jilles has now nailed down the reason in his answer).



              The only other shell I found that behave similarly is ksh93 (Update, as mentioned by @Jilles, so does FreeBSD sh). There, SIGINT seems to be plainly ignored. And ksh93 exits whenever a command is killed by SIGINT.



              You get the same behaviour as bash above but also:



              ksh -c 'sh -c "kill -INT $$"; echo test'


              Doesn't output "test". That is, it exits (by killing itself with SIGINT there) if the command it was waiting for dies of SIGINT, even if it, itself didn't receive that SIGINT.



              A work around would be to do add a:



              trap 'exit 130' INT


              At the top of the script to force bash to exit upon receiving a SIGINT (note that in any case, SIGINT won't be processed synchronously, only after the currently running command has exited).



              Ideally, we'd want to report to our parent that we died of a SIGINT (so that if it's another bash script for instance, that bash script is also interrupted). Doing an exit 130 is not the same as dying of SIGINT (though some shells will set $? to same value for both cases), however it's often used to report a death by SIGINT (on systems where SIGINT is 2 which is most).



              However for bash, ksh93 or FreeBSD sh, that doesn't work. That 130 exit status is not considered as a death by SIGINT and a parent script would not abort there.



              So, a possibly better alternative would be to kill ourself with SIGINT upon receiving SIGINT:



              trap '
              trap - INT # restore default INT handler
              kill -s INT "$$"
              ' INT






              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Aug 10 '17 at 5:48

























              answered Sep 18 '15 at 14:58









              Stéphane Chazelas

              280k53516847




              280k53516847







              • 1




                jilles’s answer explains the “why”.  As an illustrative example, consider  for f in *.txt; do vi "$f"; cp "$f" newdir; done. If the user types Ctrl+C while editing one of the files, vi just displays a message.  It seems reasonable that the loop should continue after the user finishes editing the file.  (And yes, I know that you could say vi *.txt; cp *.txt newdir; I’m just submitting the for loop as an example.)
                – Scott
                Sep 19 '15 at 18:51











              • @Scott, good point. Though vi (well vim at least) does disable tty isig when editing (it does not abviously when you run :!cmd though, and that would very much apply in that case).
                – Stéphane Chazelas
                Sep 19 '15 at 19:39










              • @Tim, see my edit for correction on your edit.
                – Stéphane Chazelas
                Aug 10 '17 at 5:53













              • 1




                jilles’s answer explains the “why”.  As an illustrative example, consider  for f in *.txt; do vi "$f"; cp "$f" newdir; done. If the user types Ctrl+C while editing one of the files, vi just displays a message.  It seems reasonable that the loop should continue after the user finishes editing the file.  (And yes, I know that you could say vi *.txt; cp *.txt newdir; I’m just submitting the for loop as an example.)
                – Scott
                Sep 19 '15 at 18:51











              • @Scott, good point. Though vi (well vim at least) does disable tty isig when editing (it does not abviously when you run :!cmd though, and that would very much apply in that case).
                – Stéphane Chazelas
                Sep 19 '15 at 19:39










              • @Tim, see my edit for correction on your edit.
                – Stéphane Chazelas
                Aug 10 '17 at 5:53








              1




              1




              jilles’s answer explains the “why”.  As an illustrative example, consider  for f in *.txt; do vi "$f"; cp "$f" newdir; done. If the user types Ctrl+C while editing one of the files, vi just displays a message.  It seems reasonable that the loop should continue after the user finishes editing the file.  (And yes, I know that you could say vi *.txt; cp *.txt newdir; I’m just submitting the for loop as an example.)
              – Scott
              Sep 19 '15 at 18:51





              jilles’s answer explains the “why”.  As an illustrative example, consider  for f in *.txt; do vi "$f"; cp "$f" newdir; done. If the user types Ctrl+C while editing one of the files, vi just displays a message.  It seems reasonable that the loop should continue after the user finishes editing the file.  (And yes, I know that you could say vi *.txt; cp *.txt newdir; I’m just submitting the for loop as an example.)
              – Scott
              Sep 19 '15 at 18:51













              @Scott, good point. Though vi (well vim at least) does disable tty isig when editing (it does not abviously when you run :!cmd though, and that would very much apply in that case).
              – Stéphane Chazelas
              Sep 19 '15 at 19:39




              @Scott, good point. Though vi (well vim at least) does disable tty isig when editing (it does not abviously when you run :!cmd though, and that would very much apply in that case).
              – Stéphane Chazelas
              Sep 19 '15 at 19:39












              @Tim, see my edit for correction on your edit.
              – Stéphane Chazelas
              Aug 10 '17 at 5:53





              @Tim, see my edit for correction on your edit.
              – Stéphane Chazelas
              Aug 10 '17 at 5:53













              up vote
              11
              down vote













              The explanation is that bash implements WCE (wait and cooperative exit) for SIGINT and SIGQUIT per http://www.cons.org/cracauer/sigint.html. That means that if bash receives SIGINT or SIGQUIT while waiting for a process to exit, it will wait until the process exits and will exit itself if the process exited on that signal. This ensures that programs that use SIGINT or SIGQUIT in their user interface will work as expected (if the signal did not cause the program to terminate, the script will continue normally).



              A downside appears with programs that catch SIGINT or SIGQUIT but then terminate because of it but using a normal exit() instead of by resending the signal to themselves. It may not be possible to interrupt scripts that call such programs. I think the real fix there is in such programs such as ping and ping6.



              Similar behaviour is implemented by ksh93 and FreeBSD's /bin/sh, but not by most other shells.






              share|improve this answer




















              • Thanks, that makes a lot of sense. I note FreeBSD sh doesn't abort either when the cmd exits with exit(130) either, which is a common way to report the death by SIGINT of a child (mksh does a exit(130) for instance if you interrupt mksh -c 'sleep 10;:').
                – Stéphane Chazelas
                Sep 19 '15 at 19:35














              up vote
              11
              down vote













              The explanation is that bash implements WCE (wait and cooperative exit) for SIGINT and SIGQUIT per http://www.cons.org/cracauer/sigint.html. That means that if bash receives SIGINT or SIGQUIT while waiting for a process to exit, it will wait until the process exits and will exit itself if the process exited on that signal. This ensures that programs that use SIGINT or SIGQUIT in their user interface will work as expected (if the signal did not cause the program to terminate, the script will continue normally).



              A downside appears with programs that catch SIGINT or SIGQUIT but then terminate because of it but using a normal exit() instead of by resending the signal to themselves. It may not be possible to interrupt scripts that call such programs. I think the real fix there is in such programs such as ping and ping6.



              Similar behaviour is implemented by ksh93 and FreeBSD's /bin/sh, but not by most other shells.






              share|improve this answer




















              • Thanks, that makes a lot of sense. I note FreeBSD sh doesn't abort either when the cmd exits with exit(130) either, which is a common way to report the death by SIGINT of a child (mksh does a exit(130) for instance if you interrupt mksh -c 'sleep 10;:').
                – Stéphane Chazelas
                Sep 19 '15 at 19:35












              up vote
              11
              down vote










              up vote
              11
              down vote









              The explanation is that bash implements WCE (wait and cooperative exit) for SIGINT and SIGQUIT per http://www.cons.org/cracauer/sigint.html. That means that if bash receives SIGINT or SIGQUIT while waiting for a process to exit, it will wait until the process exits and will exit itself if the process exited on that signal. This ensures that programs that use SIGINT or SIGQUIT in their user interface will work as expected (if the signal did not cause the program to terminate, the script will continue normally).



              A downside appears with programs that catch SIGINT or SIGQUIT but then terminate because of it but using a normal exit() instead of by resending the signal to themselves. It may not be possible to interrupt scripts that call such programs. I think the real fix there is in such programs such as ping and ping6.



              Similar behaviour is implemented by ksh93 and FreeBSD's /bin/sh, but not by most other shells.






              share|improve this answer












              The explanation is that bash implements WCE (wait and cooperative exit) for SIGINT and SIGQUIT per http://www.cons.org/cracauer/sigint.html. That means that if bash receives SIGINT or SIGQUIT while waiting for a process to exit, it will wait until the process exits and will exit itself if the process exited on that signal. This ensures that programs that use SIGINT or SIGQUIT in their user interface will work as expected (if the signal did not cause the program to terminate, the script will continue normally).



              A downside appears with programs that catch SIGINT or SIGQUIT but then terminate because of it but using a normal exit() instead of by resending the signal to themselves. It may not be possible to interrupt scripts that call such programs. I think the real fix there is in such programs such as ping and ping6.



              Similar behaviour is implemented by ksh93 and FreeBSD's /bin/sh, but not by most other shells.







              share|improve this answer












              share|improve this answer



              share|improve this answer










              answered Sep 19 '15 at 16:36









              jilles

              21113




              21113











              • Thanks, that makes a lot of sense. I note FreeBSD sh doesn't abort either when the cmd exits with exit(130) either, which is a common way to report the death by SIGINT of a child (mksh does a exit(130) for instance if you interrupt mksh -c 'sleep 10;:').
                – Stéphane Chazelas
                Sep 19 '15 at 19:35
















              • Thanks, that makes a lot of sense. I note FreeBSD sh doesn't abort either when the cmd exits with exit(130) either, which is a common way to report the death by SIGINT of a child (mksh does a exit(130) for instance if you interrupt mksh -c 'sleep 10;:').
                – Stéphane Chazelas
                Sep 19 '15 at 19:35















              Thanks, that makes a lot of sense. I note FreeBSD sh doesn't abort either when the cmd exits with exit(130) either, which is a common way to report the death by SIGINT of a child (mksh does a exit(130) for instance if you interrupt mksh -c 'sleep 10;:').
              – Stéphane Chazelas
              Sep 19 '15 at 19:35




              Thanks, that makes a lot of sense. I note FreeBSD sh doesn't abort either when the cmd exits with exit(130) either, which is a common way to report the death by SIGINT of a child (mksh does a exit(130) for instance if you interrupt mksh -c 'sleep 10;:').
              – Stéphane Chazelas
              Sep 19 '15 at 19:35










              up vote
              5
              down vote













              As you surmise, this is due to the SIGINT being sent to the subordinate process, and the shell continuing on after that process exits.



              To handle this in a better way, you can check the exit status of the commands which are running. The Unix return code encodes both the method by which a process exited (system call or signal) and what value was passed to exit() or what signal terminated the process. This is all rather complicated, but the quickest way of using it is to know that a process that was terminated by signal will have a non-zero return code. Thus, if you check the return code in your script, you can exit yourself if the child process was terminated, removing the need for inelegancies like unnecessary sleep calls. A quick way to do this throughout your script is to use set -e, though it may require a few tweaks for commands whose exit status is an expected nonzero.






              share|improve this answer




















              • Set -e does not work correctly in bash unless you are using bash-4
                – schily
                Sep 18 '15 at 9:40










              • What's meant "does not work correctly"? I've used it on bash 3 successfully, but there's probably some edge cases.
                – Tom Hunt
                Sep 18 '15 at 15:03










              • In a few simple cases, bash3 did exit on error. This did however not happen in the general case. As a typical result, make did not stop when creating a target failed and this was from a makefile that worked on a list of targets in subdirectories. David Korn and I had to mail many weeks with the bash maintainer to convince him to fix the bug for bash4.
                – schily
                Sep 18 '15 at 15:37






              • 4




                Note that the problem here is that ping returns with a 0 exit status upon receiving SIGINT and that bash then ignores the SIGINT it received itself if that's the case. Adding a "set -e" or check the exit status won't help here. Adding an explicit trap on SIGINT would help.
                – Stéphane Chazelas
                Sep 18 '15 at 15:42














              up vote
              5
              down vote













              As you surmise, this is due to the SIGINT being sent to the subordinate process, and the shell continuing on after that process exits.



              To handle this in a better way, you can check the exit status of the commands which are running. The Unix return code encodes both the method by which a process exited (system call or signal) and what value was passed to exit() or what signal terminated the process. This is all rather complicated, but the quickest way of using it is to know that a process that was terminated by signal will have a non-zero return code. Thus, if you check the return code in your script, you can exit yourself if the child process was terminated, removing the need for inelegancies like unnecessary sleep calls. A quick way to do this throughout your script is to use set -e, though it may require a few tweaks for commands whose exit status is an expected nonzero.






              share|improve this answer




















              • Set -e does not work correctly in bash unless you are using bash-4
                – schily
                Sep 18 '15 at 9:40










              • What's meant "does not work correctly"? I've used it on bash 3 successfully, but there's probably some edge cases.
                – Tom Hunt
                Sep 18 '15 at 15:03










              • In a few simple cases, bash3 did exit on error. This did however not happen in the general case. As a typical result, make did not stop when creating a target failed and this was from a makefile that worked on a list of targets in subdirectories. David Korn and I had to mail many weeks with the bash maintainer to convince him to fix the bug for bash4.
                – schily
                Sep 18 '15 at 15:37






              • 4




                Note that the problem here is that ping returns with a 0 exit status upon receiving SIGINT and that bash then ignores the SIGINT it received itself if that's the case. Adding a "set -e" or check the exit status won't help here. Adding an explicit trap on SIGINT would help.
                – Stéphane Chazelas
                Sep 18 '15 at 15:42












              up vote
              5
              down vote










              up vote
              5
              down vote









              As you surmise, this is due to the SIGINT being sent to the subordinate process, and the shell continuing on after that process exits.



              To handle this in a better way, you can check the exit status of the commands which are running. The Unix return code encodes both the method by which a process exited (system call or signal) and what value was passed to exit() or what signal terminated the process. This is all rather complicated, but the quickest way of using it is to know that a process that was terminated by signal will have a non-zero return code. Thus, if you check the return code in your script, you can exit yourself if the child process was terminated, removing the need for inelegancies like unnecessary sleep calls. A quick way to do this throughout your script is to use set -e, though it may require a few tweaks for commands whose exit status is an expected nonzero.






              share|improve this answer












              As you surmise, this is due to the SIGINT being sent to the subordinate process, and the shell continuing on after that process exits.



              To handle this in a better way, you can check the exit status of the commands which are running. The Unix return code encodes both the method by which a process exited (system call or signal) and what value was passed to exit() or what signal terminated the process. This is all rather complicated, but the quickest way of using it is to know that a process that was terminated by signal will have a non-zero return code. Thus, if you check the return code in your script, you can exit yourself if the child process was terminated, removing the need for inelegancies like unnecessary sleep calls. A quick way to do this throughout your script is to use set -e, though it may require a few tweaks for commands whose exit status is an expected nonzero.







              share|improve this answer












              share|improve this answer



              share|improve this answer










              answered Sep 17 '15 at 23:38









              Tom Hunt

              6,02521334




              6,02521334











              • Set -e does not work correctly in bash unless you are using bash-4
                – schily
                Sep 18 '15 at 9:40










              • What's meant "does not work correctly"? I've used it on bash 3 successfully, but there's probably some edge cases.
                – Tom Hunt
                Sep 18 '15 at 15:03










              • In a few simple cases, bash3 did exit on error. This did however not happen in the general case. As a typical result, make did not stop when creating a target failed and this was from a makefile that worked on a list of targets in subdirectories. David Korn and I had to mail many weeks with the bash maintainer to convince him to fix the bug for bash4.
                – schily
                Sep 18 '15 at 15:37






              • 4




                Note that the problem here is that ping returns with a 0 exit status upon receiving SIGINT and that bash then ignores the SIGINT it received itself if that's the case. Adding a "set -e" or check the exit status won't help here. Adding an explicit trap on SIGINT would help.
                – Stéphane Chazelas
                Sep 18 '15 at 15:42
















              • Set -e does not work correctly in bash unless you are using bash-4
                – schily
                Sep 18 '15 at 9:40










              • What's meant "does not work correctly"? I've used it on bash 3 successfully, but there's probably some edge cases.
                – Tom Hunt
                Sep 18 '15 at 15:03










              • In a few simple cases, bash3 did exit on error. This did however not happen in the general case. As a typical result, make did not stop when creating a target failed and this was from a makefile that worked on a list of targets in subdirectories. David Korn and I had to mail many weeks with the bash maintainer to convince him to fix the bug for bash4.
                – schily
                Sep 18 '15 at 15:37






              • 4




                Note that the problem here is that ping returns with a 0 exit status upon receiving SIGINT and that bash then ignores the SIGINT it received itself if that's the case. Adding a "set -e" or check the exit status won't help here. Adding an explicit trap on SIGINT would help.
                – Stéphane Chazelas
                Sep 18 '15 at 15:42















              Set -e does not work correctly in bash unless you are using bash-4
              – schily
              Sep 18 '15 at 9:40




              Set -e does not work correctly in bash unless you are using bash-4
              – schily
              Sep 18 '15 at 9:40












              What's meant "does not work correctly"? I've used it on bash 3 successfully, but there's probably some edge cases.
              – Tom Hunt
              Sep 18 '15 at 15:03




              What's meant "does not work correctly"? I've used it on bash 3 successfully, but there's probably some edge cases.
              – Tom Hunt
              Sep 18 '15 at 15:03












              In a few simple cases, bash3 did exit on error. This did however not happen in the general case. As a typical result, make did not stop when creating a target failed and this was from a makefile that worked on a list of targets in subdirectories. David Korn and I had to mail many weeks with the bash maintainer to convince him to fix the bug for bash4.
              – schily
              Sep 18 '15 at 15:37




              In a few simple cases, bash3 did exit on error. This did however not happen in the general case. As a typical result, make did not stop when creating a target failed and this was from a makefile that worked on a list of targets in subdirectories. David Korn and I had to mail many weeks with the bash maintainer to convince him to fix the bug for bash4.
              – schily
              Sep 18 '15 at 15:37




              4




              4




              Note that the problem here is that ping returns with a 0 exit status upon receiving SIGINT and that bash then ignores the SIGINT it received itself if that's the case. Adding a "set -e" or check the exit status won't help here. Adding an explicit trap on SIGINT would help.
              – Stéphane Chazelas
              Sep 18 '15 at 15:42




              Note that the problem here is that ping returns with a 0 exit status upon receiving SIGINT and that bash then ignores the SIGINT it received itself if that's the case. Adding a "set -e" or check the exit status won't help here. Adding an explicit trap on SIGINT would help.
              – Stéphane Chazelas
              Sep 18 '15 at 15:42










              up vote
              4
              down vote













              The terminal notices the control-c and sends an INT signal to the foreground process group, which here includes the shell, as ping has not created a new foreground process group. This is easy to verify by trapping INT.



              #! /bin/bash
              trap 'echo oh, I am slain; exit' INT
              while true; do
              ping -c5 127.0.0.1
              done


              If the command being run has created a new foreground process group, then the control-c will go to that process group, and not to the shell. In that case, the shell will need to inspect exit codes, as it will not be signalled by the terminal.



              (INT handling in shells can be fabulously complicated, by the way, as the shell sometimes needs to ignore the signal, and sometimes not. Source dive if curious, or ponder: tail -f /etc/passwd; echo foo)






              share|improve this answer






















              • In this case, the problem is not signal handling but the fact that bash does jobcontrol in the script although it should not, see my answer for more information
                – schily
                Sep 18 '15 at 12:54










              • For the SIGINT to go to the new process group, the command would also have to do an ioctl() to the terminal to make it the foreground process group of the terminal. ping has no reason to start a new process group here and the version of ping (iputils on Debian) with which I can reproduce the OP's problem does not create a process group.
                – Stéphane Chazelas
                Sep 18 '15 at 16:04






              • 1




                Note that it's not the terminal that sends the SIGINT, it's the line discipline of the tty device (the driver (code in the kernel) of the /dev/ttysomething device) upon receiving an unescaped (by lnext usually ^V) ^C character from the terminal.
                – Stéphane Chazelas
                Sep 18 '15 at 16:17














              up vote
              4
              down vote













              The terminal notices the control-c and sends an INT signal to the foreground process group, which here includes the shell, as ping has not created a new foreground process group. This is easy to verify by trapping INT.



              #! /bin/bash
              trap 'echo oh, I am slain; exit' INT
              while true; do
              ping -c5 127.0.0.1
              done


              If the command being run has created a new foreground process group, then the control-c will go to that process group, and not to the shell. In that case, the shell will need to inspect exit codes, as it will not be signalled by the terminal.



              (INT handling in shells can be fabulously complicated, by the way, as the shell sometimes needs to ignore the signal, and sometimes not. Source dive if curious, or ponder: tail -f /etc/passwd; echo foo)






              share|improve this answer






















              • In this case, the problem is not signal handling but the fact that bash does jobcontrol in the script although it should not, see my answer for more information
                – schily
                Sep 18 '15 at 12:54










              • For the SIGINT to go to the new process group, the command would also have to do an ioctl() to the terminal to make it the foreground process group of the terminal. ping has no reason to start a new process group here and the version of ping (iputils on Debian) with which I can reproduce the OP's problem does not create a process group.
                – Stéphane Chazelas
                Sep 18 '15 at 16:04






              • 1




                Note that it's not the terminal that sends the SIGINT, it's the line discipline of the tty device (the driver (code in the kernel) of the /dev/ttysomething device) upon receiving an unescaped (by lnext usually ^V) ^C character from the terminal.
                – Stéphane Chazelas
                Sep 18 '15 at 16:17












              up vote
              4
              down vote










              up vote
              4
              down vote









              The terminal notices the control-c and sends an INT signal to the foreground process group, which here includes the shell, as ping has not created a new foreground process group. This is easy to verify by trapping INT.



              #! /bin/bash
              trap 'echo oh, I am slain; exit' INT
              while true; do
              ping -c5 127.0.0.1
              done


              If the command being run has created a new foreground process group, then the control-c will go to that process group, and not to the shell. In that case, the shell will need to inspect exit codes, as it will not be signalled by the terminal.



              (INT handling in shells can be fabulously complicated, by the way, as the shell sometimes needs to ignore the signal, and sometimes not. Source dive if curious, or ponder: tail -f /etc/passwd; echo foo)






              share|improve this answer














              The terminal notices the control-c and sends an INT signal to the foreground process group, which here includes the shell, as ping has not created a new foreground process group. This is easy to verify by trapping INT.



              #! /bin/bash
              trap 'echo oh, I am slain; exit' INT
              while true; do
              ping -c5 127.0.0.1
              done


              If the command being run has created a new foreground process group, then the control-c will go to that process group, and not to the shell. In that case, the shell will need to inspect exit codes, as it will not be signalled by the terminal.



              (INT handling in shells can be fabulously complicated, by the way, as the shell sometimes needs to ignore the signal, and sometimes not. Source dive if curious, or ponder: tail -f /etc/passwd; echo foo)







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Sep 18 '15 at 0:25

























              answered Sep 18 '15 at 0:02









              thrig

              22.3k12852




              22.3k12852











              • In this case, the problem is not signal handling but the fact that bash does jobcontrol in the script although it should not, see my answer for more information
                – schily
                Sep 18 '15 at 12:54










              • For the SIGINT to go to the new process group, the command would also have to do an ioctl() to the terminal to make it the foreground process group of the terminal. ping has no reason to start a new process group here and the version of ping (iputils on Debian) with which I can reproduce the OP's problem does not create a process group.
                – Stéphane Chazelas
                Sep 18 '15 at 16:04






              • 1




                Note that it's not the terminal that sends the SIGINT, it's the line discipline of the tty device (the driver (code in the kernel) of the /dev/ttysomething device) upon receiving an unescaped (by lnext usually ^V) ^C character from the terminal.
                – Stéphane Chazelas
                Sep 18 '15 at 16:17
















              • In this case, the problem is not signal handling but the fact that bash does jobcontrol in the script although it should not, see my answer for more information
                – schily
                Sep 18 '15 at 12:54










              • For the SIGINT to go to the new process group, the command would also have to do an ioctl() to the terminal to make it the foreground process group of the terminal. ping has no reason to start a new process group here and the version of ping (iputils on Debian) with which I can reproduce the OP's problem does not create a process group.
                – Stéphane Chazelas
                Sep 18 '15 at 16:04






              • 1




                Note that it's not the terminal that sends the SIGINT, it's the line discipline of the tty device (the driver (code in the kernel) of the /dev/ttysomething device) upon receiving an unescaped (by lnext usually ^V) ^C character from the terminal.
                – Stéphane Chazelas
                Sep 18 '15 at 16:17















              In this case, the problem is not signal handling but the fact that bash does jobcontrol in the script although it should not, see my answer for more information
              – schily
              Sep 18 '15 at 12:54




              In this case, the problem is not signal handling but the fact that bash does jobcontrol in the script although it should not, see my answer for more information
              – schily
              Sep 18 '15 at 12:54












              For the SIGINT to go to the new process group, the command would also have to do an ioctl() to the terminal to make it the foreground process group of the terminal. ping has no reason to start a new process group here and the version of ping (iputils on Debian) with which I can reproduce the OP's problem does not create a process group.
              – Stéphane Chazelas
              Sep 18 '15 at 16:04




              For the SIGINT to go to the new process group, the command would also have to do an ioctl() to the terminal to make it the foreground process group of the terminal. ping has no reason to start a new process group here and the version of ping (iputils on Debian) with which I can reproduce the OP's problem does not create a process group.
              – Stéphane Chazelas
              Sep 18 '15 at 16:04




              1




              1




              Note that it's not the terminal that sends the SIGINT, it's the line discipline of the tty device (the driver (code in the kernel) of the /dev/ttysomething device) upon receiving an unescaped (by lnext usually ^V) ^C character from the terminal.
              – Stéphane Chazelas
              Sep 18 '15 at 16:17




              Note that it's not the terminal that sends the SIGINT, it's the line discipline of the tty device (the driver (code in the kernel) of the /dev/ttysomething device) upon receiving an unescaped (by lnext usually ^V) ^C character from the terminal.
              – Stéphane Chazelas
              Sep 18 '15 at 16:17










              up vote
              2
              down vote













              Well, I tried to add a sleep 1 to the bash script, and bang!

              Now I'm able to stop it with two Ctrl+C.



              When pressing Ctrl+C, a SIGINT signal is sent to the process currently executed, which command was run inside the loop. Then, the subshell process continues executing the next command in the loop, that starts another process.
              To be able to stop the script it is necessary to send two SIGINT signals, one to interrupt the current command in execution and one to interrupt the subshell process.



              In the script without the sleep call, pressing Ctrl+C really fast and many times does not seem to work, and it is not possible to exit the loop. My guess is that pressing twice is not enough fast to make it just in the right moment between the interruption of current executed process and the start of the next one. Every Ctrl+C pressed will send a SIGINT to a process executed inside the loop, but neither to the subshell.



              In the script with sleep 1, this call will suspend the execution for one second, and when interrupted by the first Ctrl+C (first SIGINT), the subshell will take more time to execute the next command. So now, the second Ctrl+C (second SIGINT) will go to the subshell, and the script execution will end.






              share|improve this answer






















              • You are mistaken, on a correctly working shell, a single ^C is sufficient see my answer for the background.
                – schily
                Sep 18 '15 at 12:51











              • Well, considering you've been down voted, and currently your answer has score -1, I'm not very convinced I should take your answer seriously.
                – nephewtom
                Sep 18 '15 at 22:44










              • The fact that some people downvote is not always related to the quality of a reply. If you need to type two times ^c, you definitely are a victim of a bash bug. Did you try a different shell? Did you try the real Bourne Shell?
                – schily
                Sep 19 '15 at 7:20










              • If the shell if working correctly, it runs everything from a script in the same process group and then a single ^c is sufficient.
                – schily
                Sep 19 '15 at 7:26






              • 1




                The behaviour @nephewtom describes in this answer can be explained by different commands in the script behaving differently when they receive Ctrl-C. If a sleep is present, it's overwhelmingly likely that Ctrl-C will be received while the sleep is executing (assuming everything else in the loop is fast). The sleep is killed, with exit value 130. The parent of sleep, a shell, notices that sleep was killed by sigint, and exits. But if the script contains no sleep, then the Ctrl-C goes to ping instead, which reacts by exiting with 0, so the parent shell carries on executing the next command.
                – Jonathan Hartley
                Oct 7 '15 at 15:15














              up vote
              2
              down vote













              Well, I tried to add a sleep 1 to the bash script, and bang!

              Now I'm able to stop it with two Ctrl+C.



              When pressing Ctrl+C, a SIGINT signal is sent to the process currently executed, which command was run inside the loop. Then, the subshell process continues executing the next command in the loop, that starts another process.
              To be able to stop the script it is necessary to send two SIGINT signals, one to interrupt the current command in execution and one to interrupt the subshell process.



              In the script without the sleep call, pressing Ctrl+C really fast and many times does not seem to work, and it is not possible to exit the loop. My guess is that pressing twice is not enough fast to make it just in the right moment between the interruption of current executed process and the start of the next one. Every Ctrl+C pressed will send a SIGINT to a process executed inside the loop, but neither to the subshell.



              In the script with sleep 1, this call will suspend the execution for one second, and when interrupted by the first Ctrl+C (first SIGINT), the subshell will take more time to execute the next command. So now, the second Ctrl+C (second SIGINT) will go to the subshell, and the script execution will end.






              share|improve this answer






















              • You are mistaken, on a correctly working shell, a single ^C is sufficient see my answer for the background.
                – schily
                Sep 18 '15 at 12:51











              • Well, considering you've been down voted, and currently your answer has score -1, I'm not very convinced I should take your answer seriously.
                – nephewtom
                Sep 18 '15 at 22:44










              • The fact that some people downvote is not always related to the quality of a reply. If you need to type two times ^c, you definitely are a victim of a bash bug. Did you try a different shell? Did you try the real Bourne Shell?
                – schily
                Sep 19 '15 at 7:20










              • If the shell if working correctly, it runs everything from a script in the same process group and then a single ^c is sufficient.
                – schily
                Sep 19 '15 at 7:26






              • 1




                The behaviour @nephewtom describes in this answer can be explained by different commands in the script behaving differently when they receive Ctrl-C. If a sleep is present, it's overwhelmingly likely that Ctrl-C will be received while the sleep is executing (assuming everything else in the loop is fast). The sleep is killed, with exit value 130. The parent of sleep, a shell, notices that sleep was killed by sigint, and exits. But if the script contains no sleep, then the Ctrl-C goes to ping instead, which reacts by exiting with 0, so the parent shell carries on executing the next command.
                – Jonathan Hartley
                Oct 7 '15 at 15:15












              up vote
              2
              down vote










              up vote
              2
              down vote









              Well, I tried to add a sleep 1 to the bash script, and bang!

              Now I'm able to stop it with two Ctrl+C.



              When pressing Ctrl+C, a SIGINT signal is sent to the process currently executed, which command was run inside the loop. Then, the subshell process continues executing the next command in the loop, that starts another process.
              To be able to stop the script it is necessary to send two SIGINT signals, one to interrupt the current command in execution and one to interrupt the subshell process.



              In the script without the sleep call, pressing Ctrl+C really fast and many times does not seem to work, and it is not possible to exit the loop. My guess is that pressing twice is not enough fast to make it just in the right moment between the interruption of current executed process and the start of the next one. Every Ctrl+C pressed will send a SIGINT to a process executed inside the loop, but neither to the subshell.



              In the script with sleep 1, this call will suspend the execution for one second, and when interrupted by the first Ctrl+C (first SIGINT), the subshell will take more time to execute the next command. So now, the second Ctrl+C (second SIGINT) will go to the subshell, and the script execution will end.






              share|improve this answer














              Well, I tried to add a sleep 1 to the bash script, and bang!

              Now I'm able to stop it with two Ctrl+C.



              When pressing Ctrl+C, a SIGINT signal is sent to the process currently executed, which command was run inside the loop. Then, the subshell process continues executing the next command in the loop, that starts another process.
              To be able to stop the script it is necessary to send two SIGINT signals, one to interrupt the current command in execution and one to interrupt the subshell process.



              In the script without the sleep call, pressing Ctrl+C really fast and many times does not seem to work, and it is not possible to exit the loop. My guess is that pressing twice is not enough fast to make it just in the right moment between the interruption of current executed process and the start of the next one. Every Ctrl+C pressed will send a SIGINT to a process executed inside the loop, but neither to the subshell.



              In the script with sleep 1, this call will suspend the execution for one second, and when interrupted by the first Ctrl+C (first SIGINT), the subshell will take more time to execute the next command. So now, the second Ctrl+C (second SIGINT) will go to the subshell, and the script execution will end.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Sep 18 '15 at 7:59

























              answered Sep 17 '15 at 23:29









              nephewtom

              315139




              315139











              • You are mistaken, on a correctly working shell, a single ^C is sufficient see my answer for the background.
                – schily
                Sep 18 '15 at 12:51











              • Well, considering you've been down voted, and currently your answer has score -1, I'm not very convinced I should take your answer seriously.
                – nephewtom
                Sep 18 '15 at 22:44










              • The fact that some people downvote is not always related to the quality of a reply. If you need to type two times ^c, you definitely are a victim of a bash bug. Did you try a different shell? Did you try the real Bourne Shell?
                – schily
                Sep 19 '15 at 7:20










              • If the shell if working correctly, it runs everything from a script in the same process group and then a single ^c is sufficient.
                – schily
                Sep 19 '15 at 7:26






              • 1




                The behaviour @nephewtom describes in this answer can be explained by different commands in the script behaving differently when they receive Ctrl-C. If a sleep is present, it's overwhelmingly likely that Ctrl-C will be received while the sleep is executing (assuming everything else in the loop is fast). The sleep is killed, with exit value 130. The parent of sleep, a shell, notices that sleep was killed by sigint, and exits. But if the script contains no sleep, then the Ctrl-C goes to ping instead, which reacts by exiting with 0, so the parent shell carries on executing the next command.
                – Jonathan Hartley
                Oct 7 '15 at 15:15
















              • You are mistaken, on a correctly working shell, a single ^C is sufficient see my answer for the background.
                – schily
                Sep 18 '15 at 12:51











              • Well, considering you've been down voted, and currently your answer has score -1, I'm not very convinced I should take your answer seriously.
                – nephewtom
                Sep 18 '15 at 22:44










              • The fact that some people downvote is not always related to the quality of a reply. If you need to type two times ^c, you definitely are a victim of a bash bug. Did you try a different shell? Did you try the real Bourne Shell?
                – schily
                Sep 19 '15 at 7:20










              • If the shell if working correctly, it runs everything from a script in the same process group and then a single ^c is sufficient.
                – schily
                Sep 19 '15 at 7:26






              • 1




                The behaviour @nephewtom describes in this answer can be explained by different commands in the script behaving differently when they receive Ctrl-C. If a sleep is present, it's overwhelmingly likely that Ctrl-C will be received while the sleep is executing (assuming everything else in the loop is fast). The sleep is killed, with exit value 130. The parent of sleep, a shell, notices that sleep was killed by sigint, and exits. But if the script contains no sleep, then the Ctrl-C goes to ping instead, which reacts by exiting with 0, so the parent shell carries on executing the next command.
                – Jonathan Hartley
                Oct 7 '15 at 15:15















              You are mistaken, on a correctly working shell, a single ^C is sufficient see my answer for the background.
              – schily
              Sep 18 '15 at 12:51





              You are mistaken, on a correctly working shell, a single ^C is sufficient see my answer for the background.
              – schily
              Sep 18 '15 at 12:51













              Well, considering you've been down voted, and currently your answer has score -1, I'm not very convinced I should take your answer seriously.
              – nephewtom
              Sep 18 '15 at 22:44




              Well, considering you've been down voted, and currently your answer has score -1, I'm not very convinced I should take your answer seriously.
              – nephewtom
              Sep 18 '15 at 22:44












              The fact that some people downvote is not always related to the quality of a reply. If you need to type two times ^c, you definitely are a victim of a bash bug. Did you try a different shell? Did you try the real Bourne Shell?
              – schily
              Sep 19 '15 at 7:20




              The fact that some people downvote is not always related to the quality of a reply. If you need to type two times ^c, you definitely are a victim of a bash bug. Did you try a different shell? Did you try the real Bourne Shell?
              – schily
              Sep 19 '15 at 7:20












              If the shell if working correctly, it runs everything from a script in the same process group and then a single ^c is sufficient.
              – schily
              Sep 19 '15 at 7:26




              If the shell if working correctly, it runs everything from a script in the same process group and then a single ^c is sufficient.
              – schily
              Sep 19 '15 at 7:26




              1




              1




              The behaviour @nephewtom describes in this answer can be explained by different commands in the script behaving differently when they receive Ctrl-C. If a sleep is present, it's overwhelmingly likely that Ctrl-C will be received while the sleep is executing (assuming everything else in the loop is fast). The sleep is killed, with exit value 130. The parent of sleep, a shell, notices that sleep was killed by sigint, and exits. But if the script contains no sleep, then the Ctrl-C goes to ping instead, which reacts by exiting with 0, so the parent shell carries on executing the next command.
              – Jonathan Hartley
              Oct 7 '15 at 15:15




              The behaviour @nephewtom describes in this answer can be explained by different commands in the script behaving differently when they receive Ctrl-C. If a sleep is present, it's overwhelmingly likely that Ctrl-C will be received while the sleep is executing (assuming everything else in the loop is fast). The sleep is killed, with exit value 130. The parent of sleep, a shell, notices that sleep was killed by sigint, and exits. But if the script contains no sleep, then the Ctrl-C goes to ping instead, which reacts by exiting with 0, so the parent shell carries on executing the next command.
              – Jonathan Hartley
              Oct 7 '15 at 15:15










              up vote
              1
              down vote













              Try this:



              #!/bin/bash
              while true; do
              echo "Ctrl-c works during sleep 5"
              sleep 5
              echo "But not during ping -c 5"
              ping -c 5 127.0.0.1
              done


              Now change the first line to:



              #!/bin/sh


              and try again - see if the ping is now interruptible.






              share|improve this answer
























                up vote
                1
                down vote













                Try this:



                #!/bin/bash
                while true; do
                echo "Ctrl-c works during sleep 5"
                sleep 5
                echo "But not during ping -c 5"
                ping -c 5 127.0.0.1
                done


                Now change the first line to:



                #!/bin/sh


                and try again - see if the ping is now interruptible.






                share|improve this answer






















                  up vote
                  1
                  down vote










                  up vote
                  1
                  down vote









                  Try this:



                  #!/bin/bash
                  while true; do
                  echo "Ctrl-c works during sleep 5"
                  sleep 5
                  echo "But not during ping -c 5"
                  ping -c 5 127.0.0.1
                  done


                  Now change the first line to:



                  #!/bin/sh


                  and try again - see if the ping is now interruptible.






                  share|improve this answer












                  Try this:



                  #!/bin/bash
                  while true; do
                  echo "Ctrl-c works during sleep 5"
                  sleep 5
                  echo "But not during ping -c 5"
                  ping -c 5 127.0.0.1
                  done


                  Now change the first line to:



                  #!/bin/sh


                  and try again - see if the ping is now interruptible.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Feb 10 '16 at 22:10









                  Sparrow

                  111




                  111




















                      up vote
                      0
                      down vote













                      pgrep -f process_name > any_file_name
                      sed -i 's/^/kill /' any_file_name
                      chmod 777 any_file_name
                      ./any_file_name


                      for example pgrep -f firefox will grep the PID of running firefox and will save this PID to a file called any_file_name. 'sed' command will add the kill in the beginning of the PID number in 'any_file_name' file. Third line will any_file_name file executable. Now forth line will kill the PID available in the file any_file_name. Writing the above four lines in a file and executing that file can do the Control-C. Working absolutely fine for me.






                      share|improve this answer


























                        up vote
                        0
                        down vote













                        pgrep -f process_name > any_file_name
                        sed -i 's/^/kill /' any_file_name
                        chmod 777 any_file_name
                        ./any_file_name


                        for example pgrep -f firefox will grep the PID of running firefox and will save this PID to a file called any_file_name. 'sed' command will add the kill in the beginning of the PID number in 'any_file_name' file. Third line will any_file_name file executable. Now forth line will kill the PID available in the file any_file_name. Writing the above four lines in a file and executing that file can do the Control-C. Working absolutely fine for me.






                        share|improve this answer
























                          up vote
                          0
                          down vote










                          up vote
                          0
                          down vote









                          pgrep -f process_name > any_file_name
                          sed -i 's/^/kill /' any_file_name
                          chmod 777 any_file_name
                          ./any_file_name


                          for example pgrep -f firefox will grep the PID of running firefox and will save this PID to a file called any_file_name. 'sed' command will add the kill in the beginning of the PID number in 'any_file_name' file. Third line will any_file_name file executable. Now forth line will kill the PID available in the file any_file_name. Writing the above four lines in a file and executing that file can do the Control-C. Working absolutely fine for me.






                          share|improve this answer














                          pgrep -f process_name > any_file_name
                          sed -i 's/^/kill /' any_file_name
                          chmod 777 any_file_name
                          ./any_file_name


                          for example pgrep -f firefox will grep the PID of running firefox and will save this PID to a file called any_file_name. 'sed' command will add the kill in the beginning of the PID number in 'any_file_name' file. Third line will any_file_name file executable. Now forth line will kill the PID available in the file any_file_name. Writing the above four lines in a file and executing that file can do the Control-C. Working absolutely fine for me.







                          share|improve this answer














                          share|improve this answer



                          share|improve this answer








                          edited May 3 '17 at 19:00









                          phk

                          3,80852147




                          3,80852147










                          answered Apr 17 '17 at 6:44









                          user2176228

                          1




                          1




















                              up vote
                              -3
                              down vote













                              You are a victim of a well known bash bug. Bash does jobcontrol for scripts which is a mistake.



                              What happens is that bash runs the external programs in a different process group than it uses for the script itself. As the TTY processgroup is set to the processgroup of the current foreground process, only this foreground process is killed and the loop in the shell script continues.



                              To verify: Fetch and compile a recent Bourne Shell that implements pgrp(1) as a builtin program, then add a /bin/sleep 100 (or /usr/bin/sleep depending on your platform) to the script loop and then start the Bourne Shell. After you used ps(1) to obtain the process IDs for the sleep command and the bash that runs the script, call pgrp <pid> and replace "< pid >" by the process ID of the sleep and the bash that runs the script. You will see different process group IDs. Now call something like pgrp < /dev/pts/7 (replace the tty name by the tty used by the script) to obtain the current tty process group. The TTY process group equals the process group of the sleep command.



                              To fix: use a different shell.



                              The recent Bourne Shell sources are in my schily tools package which you can find here:



                              http://sourceforge.net/projects/schilytools/files/






                              share|improve this answer






















                              • What version of bash is that? AFAIK bash only does that if you pass the -m or -i option.
                                – Stéphane Chazelas
                                Sep 18 '15 at 14:00










                              • It seems that this does no longer apply to bash4 but when the OP has such problems, he seems to use bash3
                                – schily
                                Sep 18 '15 at 14:08










                              • Can't reproduce with bash3.2.48 nor bash 3.0.16 nor bash-2.05b (tried with bash -c 'ps -j; ps -j; ps -j').
                                – Stéphane Chazelas
                                Sep 18 '15 at 15:00










                              • This definitely happens when you call bash as /bin/sh -ce. I had to add an ugly workaround into smake that explicitely kills the process group for a currently running command in order to permit ^C to abort a layered make call. Did you check whether bash changed the process group from the process group id it was initiated with?
                                – schily
                                Sep 18 '15 at 15:41











                              • ARGV0=sh bash -ce 'ps -j; ps -j; ps -j' does report the same pgid for ps and bash in all 3 ps invocations. (ARGV0=sh is zsh way to pass argv[0]).
                                – Stéphane Chazelas
                                Sep 18 '15 at 16:01














                              up vote
                              -3
                              down vote













                              You are a victim of a well known bash bug. Bash does jobcontrol for scripts which is a mistake.



                              What happens is that bash runs the external programs in a different process group than it uses for the script itself. As the TTY processgroup is set to the processgroup of the current foreground process, only this foreground process is killed and the loop in the shell script continues.



                              To verify: Fetch and compile a recent Bourne Shell that implements pgrp(1) as a builtin program, then add a /bin/sleep 100 (or /usr/bin/sleep depending on your platform) to the script loop and then start the Bourne Shell. After you used ps(1) to obtain the process IDs for the sleep command and the bash that runs the script, call pgrp <pid> and replace "< pid >" by the process ID of the sleep and the bash that runs the script. You will see different process group IDs. Now call something like pgrp < /dev/pts/7 (replace the tty name by the tty used by the script) to obtain the current tty process group. The TTY process group equals the process group of the sleep command.



                              To fix: use a different shell.



                              The recent Bourne Shell sources are in my schily tools package which you can find here:



                              http://sourceforge.net/projects/schilytools/files/






                              share|improve this answer






















                              • What version of bash is that? AFAIK bash only does that if you pass the -m or -i option.
                                – Stéphane Chazelas
                                Sep 18 '15 at 14:00










                              • It seems that this does no longer apply to bash4 but when the OP has such problems, he seems to use bash3
                                – schily
                                Sep 18 '15 at 14:08










                              • Can't reproduce with bash3.2.48 nor bash 3.0.16 nor bash-2.05b (tried with bash -c 'ps -j; ps -j; ps -j').
                                – Stéphane Chazelas
                                Sep 18 '15 at 15:00










                              • This definitely happens when you call bash as /bin/sh -ce. I had to add an ugly workaround into smake that explicitely kills the process group for a currently running command in order to permit ^C to abort a layered make call. Did you check whether bash changed the process group from the process group id it was initiated with?
                                – schily
                                Sep 18 '15 at 15:41











                              • ARGV0=sh bash -ce 'ps -j; ps -j; ps -j' does report the same pgid for ps and bash in all 3 ps invocations. (ARGV0=sh is zsh way to pass argv[0]).
                                – Stéphane Chazelas
                                Sep 18 '15 at 16:01












                              up vote
                              -3
                              down vote










                              up vote
                              -3
                              down vote









                              You are a victim of a well known bash bug. Bash does jobcontrol for scripts which is a mistake.



                              What happens is that bash runs the external programs in a different process group than it uses for the script itself. As the TTY processgroup is set to the processgroup of the current foreground process, only this foreground process is killed and the loop in the shell script continues.



                              To verify: Fetch and compile a recent Bourne Shell that implements pgrp(1) as a builtin program, then add a /bin/sleep 100 (or /usr/bin/sleep depending on your platform) to the script loop and then start the Bourne Shell. After you used ps(1) to obtain the process IDs for the sleep command and the bash that runs the script, call pgrp <pid> and replace "< pid >" by the process ID of the sleep and the bash that runs the script. You will see different process group IDs. Now call something like pgrp < /dev/pts/7 (replace the tty name by the tty used by the script) to obtain the current tty process group. The TTY process group equals the process group of the sleep command.



                              To fix: use a different shell.



                              The recent Bourne Shell sources are in my schily tools package which you can find here:



                              http://sourceforge.net/projects/schilytools/files/






                              share|improve this answer














                              You are a victim of a well known bash bug. Bash does jobcontrol for scripts which is a mistake.



                              What happens is that bash runs the external programs in a different process group than it uses for the script itself. As the TTY processgroup is set to the processgroup of the current foreground process, only this foreground process is killed and the loop in the shell script continues.



                              To verify: Fetch and compile a recent Bourne Shell that implements pgrp(1) as a builtin program, then add a /bin/sleep 100 (or /usr/bin/sleep depending on your platform) to the script loop and then start the Bourne Shell. After you used ps(1) to obtain the process IDs for the sleep command and the bash that runs the script, call pgrp <pid> and replace "< pid >" by the process ID of the sleep and the bash that runs the script. You will see different process group IDs. Now call something like pgrp < /dev/pts/7 (replace the tty name by the tty used by the script) to obtain the current tty process group. The TTY process group equals the process group of the sleep command.



                              To fix: use a different shell.



                              The recent Bourne Shell sources are in my schily tools package which you can find here:



                              http://sourceforge.net/projects/schilytools/files/







                              share|improve this answer














                              share|improve this answer



                              share|improve this answer








                              edited Sep 19 '15 at 22:12









                              terdon♦

                              122k28230401




                              122k28230401










                              answered Sep 18 '15 at 9:54









                              schily

                              8,99731435




                              8,99731435











                              • What version of bash is that? AFAIK bash only does that if you pass the -m or -i option.
                                – Stéphane Chazelas
                                Sep 18 '15 at 14:00










                              • It seems that this does no longer apply to bash4 but when the OP has such problems, he seems to use bash3
                                – schily
                                Sep 18 '15 at 14:08










                              • Can't reproduce with bash3.2.48 nor bash 3.0.16 nor bash-2.05b (tried with bash -c 'ps -j; ps -j; ps -j').
                                – Stéphane Chazelas
                                Sep 18 '15 at 15:00










                              • This definitely happens when you call bash as /bin/sh -ce. I had to add an ugly workaround into smake that explicitely kills the process group for a currently running command in order to permit ^C to abort a layered make call. Did you check whether bash changed the process group from the process group id it was initiated with?
                                – schily
                                Sep 18 '15 at 15:41











                              • ARGV0=sh bash -ce 'ps -j; ps -j; ps -j' does report the same pgid for ps and bash in all 3 ps invocations. (ARGV0=sh is zsh way to pass argv[0]).
                                – Stéphane Chazelas
                                Sep 18 '15 at 16:01
















                              • What version of bash is that? AFAIK bash only does that if you pass the -m or -i option.
                                – Stéphane Chazelas
                                Sep 18 '15 at 14:00










                              • It seems that this does no longer apply to bash4 but when the OP has such problems, he seems to use bash3
                                – schily
                                Sep 18 '15 at 14:08










                              • Can't reproduce with bash3.2.48 nor bash 3.0.16 nor bash-2.05b (tried with bash -c 'ps -j; ps -j; ps -j').
                                – Stéphane Chazelas
                                Sep 18 '15 at 15:00










                              • This definitely happens when you call bash as /bin/sh -ce. I had to add an ugly workaround into smake that explicitely kills the process group for a currently running command in order to permit ^C to abort a layered make call. Did you check whether bash changed the process group from the process group id it was initiated with?
                                – schily
                                Sep 18 '15 at 15:41











                              • ARGV0=sh bash -ce 'ps -j; ps -j; ps -j' does report the same pgid for ps and bash in all 3 ps invocations. (ARGV0=sh is zsh way to pass argv[0]).
                                – Stéphane Chazelas
                                Sep 18 '15 at 16:01















                              What version of bash is that? AFAIK bash only does that if you pass the -m or -i option.
                              – Stéphane Chazelas
                              Sep 18 '15 at 14:00




                              What version of bash is that? AFAIK bash only does that if you pass the -m or -i option.
                              – Stéphane Chazelas
                              Sep 18 '15 at 14:00












                              It seems that this does no longer apply to bash4 but when the OP has such problems, he seems to use bash3
                              – schily
                              Sep 18 '15 at 14:08




                              It seems that this does no longer apply to bash4 but when the OP has such problems, he seems to use bash3
                              – schily
                              Sep 18 '15 at 14:08












                              Can't reproduce with bash3.2.48 nor bash 3.0.16 nor bash-2.05b (tried with bash -c 'ps -j; ps -j; ps -j').
                              – Stéphane Chazelas
                              Sep 18 '15 at 15:00




                              Can't reproduce with bash3.2.48 nor bash 3.0.16 nor bash-2.05b (tried with bash -c 'ps -j; ps -j; ps -j').
                              – Stéphane Chazelas
                              Sep 18 '15 at 15:00












                              This definitely happens when you call bash as /bin/sh -ce. I had to add an ugly workaround into smake that explicitely kills the process group for a currently running command in order to permit ^C to abort a layered make call. Did you check whether bash changed the process group from the process group id it was initiated with?
                              – schily
                              Sep 18 '15 at 15:41





                              This definitely happens when you call bash as /bin/sh -ce. I had to add an ugly workaround into smake that explicitely kills the process group for a currently running command in order to permit ^C to abort a layered make call. Did you check whether bash changed the process group from the process group id it was initiated with?
                              – schily
                              Sep 18 '15 at 15:41













                              ARGV0=sh bash -ce 'ps -j; ps -j; ps -j' does report the same pgid for ps and bash in all 3 ps invocations. (ARGV0=sh is zsh way to pass argv[0]).
                              – Stéphane Chazelas
                              Sep 18 '15 at 16:01




                              ARGV0=sh bash -ce 'ps -j; ps -j; ps -j' does report the same pgid for ps and bash in all 3 ps invocations. (ARGV0=sh is zsh way to pass argv[0]).
                              – Stéphane Chazelas
                              Sep 18 '15 at 16:01












                               

                              draft saved


                              draft discarded


























                               


                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function ()
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f230421%2funable-to-stop-a-bash-script-with-ctrlc%23new-answer', 'question_page');

                              );

                              Post as a guest













































































                              Popular posts from this blog

                              Peggy Mitchell

                              Palaiologos

                              The Forum (Inglewood, California)