Why Sigint Can Stop Bash in Terminal But Not via Kill -Int

Sending SIGINT to forked exec process which runs script does not kill it

A SOLUTION:

Add this line on top of your work.sh script trap exit SIGINT to have an explicit SIGINT handler:

#! /bin/bash

trap exit SIGINT

COUNTER=0
while true
do
((COUNTER+=1))
echo "#${COUNTER} Working..."
sleep 1
done

Running work executable now prints:

#1 Working...
#2 Working...
#3 Working...
#4 Working...
#5 Working...

after which it returns back to shell.

THE PROBLEM:

I found this webpage linked in a comment to this question on Unix stackexchange (For the sake of completeness, here also the webpage linked in the accepted answer.) Here's a quote that might explain what's going on:

bash is among a few shells that implement a wait and cooperative exit approach at handling SIGINT/SIGQUIT delivery. When interpreting a script, upon receiving a SIGINT, it doesn't exit straight away but instead waits for the currently running command to return and only exits (by killing itself with SIGINT) if that command was also killed by that SIGINT. The idea is that if your script calls vi for instance, and you press Ctrl+C within vi to cancel an action, that should not be considered as a request to abort the script.

So imagine you're writing a script and that script exits normally upon receiving SIGINT. That means that if that script is invoked from another bash script, Ctrl-C will no longer interrupt that other script.

This kind of problem can be seen with actual commands that do exit normally upon SIGINT by design.

EDIT:

I found another Unix stackexchange answer that explains it even better. If you look at bash(1) man pages the following is also quite explanatory:

Non-builtin commands run by bash have signal handlers set to the values inherited by the shell from its parent. When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.

especially when considering that:

Signals ignored upon entry to the shell cannot be trapped, reset or listed.

Basically, running work.sh runs it in a separate execution environment:

When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment.

This includes the signal handlers which (if not explicitly present) will ignore SIGINT and SIGQUIT by default.

Kill -INT not working on bash script with trap INT set

From the POSIX standard for the shell:

When a signal for which a trap has been set is received while the shell is waiting for the completion of a utility executing a foreground command, the trap associated with that signal shall not be executed until after the foreground command has completed.

To verify this, we can run strace on the shell executing your script. Upon sending the SIGINT from another terminal, bash just notes that the signal has been received and returns to waiting for its child, the sleep command, to finish:

rt_sigaction(SIGINT, {0x808a550, [], 0}, {0x80a4600, [], 0}, 8) = 0
waitpid(-1, 0xbfb56324, 0) = ? ERESTARTSYS
--- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=3556, si_uid=1000} ---
sigreturn({mask=[CHLD]}) = -1 EINTR (Interrupted system call)
waitpid(-1,

To make kill have the same effect as Ctrl-C, you should send SIGINT to the process group. The shell by default will put every process of a new command into its own pgrp:

$ ps -f -o pid,ppid,pgid,tty,comm -t pts/1
PID PPID PGID TT COMMAND
3460 3447 3460 pts/1 bash
29087 3460 29087 pts/1 \_ foo.sh
29120 29087 29087 pts/1 \_ sleep

$ kill -2 -29087

And now the trap runs and the shell exits:

waitpid(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGINT}], 0) = 29120
--- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=3556, si_uid=1000} ---
sigreturn({mask=[CHLD]}) = 29120
...
write(1, "CTRL+C pressed\n", 15) = 15
...
exit_group(0) = ?

Bash: Why does parent script not terminate on SIGINT when child script traps SIGINT?

New answer:

This question is far more interesting than I originally suspected. The answer is essentially given here:

What happens to a SIGINT (^C) when sent to a perl script containing children?

Here's the relevant tidbit. I realize you're not using Perl, but I assume Bash is using C's convention.

Perl’s builtin system function works just like the C system(3)
function from the standard C library as far as signals are concerned.
If you are using Perl’s version of system() or pipe open or backticks,
then the parent — the one calling system rather than the one called by
it — will IGNORE any SIGINT and SIGQUIT while the children are
running.

This explanation is the best I've seen about the various choices that can be made. It also says that Bash does the WCE approach. That is, when a parent process receives SIGINT, it waits until its child process returns. If that process handled exited from a SIGINT, it also exits with SIGINT. If the child exited any other way it ignores SIGINT.

There is also a way that the calling shell can tell whether the called
program exited on SIGINT and if it ignored SIGINT (or used it for
other purposes). As in the WUE way, the shell waits for the child to
complete. It figures whether the program was ended on SIGINT and if
so, it discontinue the script. If the program did any other exit, the
script will be continued. I will call the way of doing things the
"WCE" (for "wait and cooperative exit") for the rest of this document.

I can't find a reference to this in the Bash man page, but I'll keep looking in the info docs. But I'm 99% confident this is the correct answer.

Old answer:

A nonzero exit status from a command in a Bash script does not terminate the program. If you do an echo $? after ./script2.sh it will show 130. You can terminate the script by using set -e as phs suggests.

$ help set
...
-e Exit immediately if a command exits with a non-zero status.

Prevent SIGINT from interrupting current task while still passing information about SIGINT (and preserve the exit code)

Both answers from @PSkocik and @WilliamPursell have helped me to get on the right track.

I have a fully working solution. It ain't pretty because it needs to use an external file to indicate that the signal didn't occurred but beside that it should work reliably.

#!/bin/sh

touch ./continue
trap 'rm -f ./continue' 2

( # the whole main body of the script is in a separate background process
trap '' 2 # ignore SIGINT
while true ; do
result="$(sleep 2 ; echo success)" # run some program
echo "result: '$result'"
echo "Cleaning up..." # clean up temporary files
if [ ! -e ./continue ] ; then # exit the loop if file "./continue" is deleted
echo 'OK, time to stop this.'
break
fi
done
) & # end of the main body of the script
while ! wait ; do : ; done # wait for the background process to end (ignore signals)
wait $! # wait again to get the exit code
result=$? # exit code of the background process

rm -f ./continue # clean up if the background process ended without a signal

exit $result

EDIT: There are some problems with this code in Cygwin.

The main functionality regarding signals work.
However, it seems like the finished background process doesn't stay in the system as a zombie. This makes the wait $! to not work. The exit code of the script has incorrect value of 127.

Solution to that would be removing lines wait $!, result=$? and result=$? so the script always returns 0.
It should be also possible to keep the proper error code by using another layer of subshell and temporarily store the exit code in a file.

kill -INT $pid won't kill process, but ctrl+c will

Prepend process id with dash sign "-"

kill -SIGINT -<pid>

This will kill the process with exit code 130.

UPD: Why not to use SIGTERM(15) which is send by kill command by default (with no signal number or name)?

Why doesnt SIGINT get caught here?

from perldoc system:

Since SIGINT and SIGQUIT are ignored during the execution of system,
if you expect your program to terminate on receipt of these signals you will need to arrange to do so yourself based on the return value.

@args = ("command", "arg1", "arg2");
system(@args) == 0
or die "system @args failed: $?"

If you'd like to manually inspect system's failure, you can check all possible failure
modes by inspecting $? like this:

if ($? == -1) {
print "failed to execute: $!\n";
}
elsif ($? & 127) {
printf "child died with signal %d, %s coredump\n",
($? & 127), ($? & 128) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $? >> 8;
}

Alternatively, you may inspect the value of ${^CHILD_ERROR_NATIVE} with the W*() calls from the POSIX module

bash trap will echo from keyboard Ctrl-C while not kill 2

With kill -s SIGINT 50138, you are only sending the signal to the shell's process, and that has to wait for sleep 1000 to finish, because sleep doesn't receive the signal.

Control-C, though, causes the terminal to send SIGINT to every process in the current process group, so both your shell script and sleep receive it. Your script still doesn't process the trap command until sleep completes, but sleep exits immediately in response to the SIGINT it just received from the terminal.

If your kill supports it, you can also use kill -s SIGINT -50138 (note the negative process id) to send SIGINT to the entire process group.

Kill background process on SIGINT

Looks like dash isn't supporting SIGINT or INT (at least this is the case in the Linux subsystem for Windows). Solution is to use bash and SIGINT will work as it should.

sublime.sh:

#!/bin/bash

set -e

# Just to make sure, this line takes PID1 in Docker environments
# So SIGINT/SIGKILL won't be blocked
echo "pid1" > /dev/null

echo -e "\n///////////////////////////////////////////////\n"

# Keep alive (in background) hack from Docker environments
tail -f /dev/null &
pid[0]=$!

echo "tail process id: ${pid[0]}"
echo -e "Keep-alive process started with Sublime Text 3\nPress SIGINT (CTRL+C) to kill it..."

# Start Sublime Text 3
DISPLAY=localhost:0 /usr/bin/sublime

# http://stackoverflow.com/a/19274804/1442219
# http://stackoverflow.com/a/360275/1442219
trap "kill ${pid[0]}; exit 1" SIGINT

echo -e "\n///////////////////////////////////////////////\n"

wait

sublime.cmd

@ECHO OFF
"C:\Windows\System32\bash.exe" -c "./sublime.sh"

Killing Subshell with SIGTERM

The reason this happens is that the Ctrl-C is delivered to the sleep process, whereas the sigint you are sending is delivered only to the script itself. See Child process receives parent's SIGINT for details on this.

You can verify this yourself by using strace -p when hitting ctrl-c or sending sigint; strace will tell you what signals are delivered.

EDIT: I don't think you are dumb. Processes and how they work are seemingly simple, but the details are often complicated, and even experts get confused by this sort of thing.



Related Topics



Leave a reply



Submit