Why bash background task ignores SIGINT?
This construct, (sleep 1000 &)
puts your sleep command in a grandchild subshell with no job control:
your-shell
\
a compound-list in a subshell
\
an asynchronous list in a subshell
The first subshell, ( compound-list ) (a grouping command construct), simply runs the backgrounded command & (an asynchronous list) and then exits. The asynchronous list is run in its own subshell.
That final subshell is too far removed from your initial shell for job control to be meaningful.
Per POSIX, "[i]f job control is disabled ... when the shell executes an asynchronous list, the commands in the list shall inherit from the shell a signal action of ignored (SIG_IGN) for the SIGINT and SIGQUIT signals."
Thus your sleep is run with SIGINT set to ignore.
bash: Why can't I set a trap for SIGINT in a background shell?
SIGINT
and SIGQUIT
are ignored in backgrounded processes (unless they're backgrounded with set -m
on). It's a (weird) POSIX requirement (see
2. Shell Command Language or my SO question Why do shells ignore SIGINT and SIGQUIT in backgrounded processes? for more details).
Additionally, POSIX requires that:
When a subshell is entered, traps that are not being ignored shall be
set to the default actions, except in the case of a command
substitution containing only a single trap command ..
However, even if you set the INT
handler in the subshell again after it was reset, the subshell won't be able to receive it because it's ignored (you can try it or you can inspect the signal ignore mask using ps
, for example).
Handling signals in bash script when it started as a background process
Bash manual states:
- Background processes (...) are immune to keyboard-generated signals.
- If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.
Consequently:
- If your script runs in the foreground: when you press "Ctrl-C", a SIGINT is sent to the currently running process (i.e. the
sleep
command). The exit status ofsleep
tells Bash that it was interrupted by the SIGINT signal andbash
calls your trap. - If your script runs in the background, then the backgrounded
sleep
does not receive the signal and the SIGINT trap is only executed oncesleep
has ended.
Sending SIGINT to foreground process works but not background
I guess what you are trying to achieve is that when script2
receives the SIGINT it continues and prints the message. Then, you need
#!/bin/bash
echo "~~ENTRY"
trap 'echo you hit ctrl-c, waking up...; CONT=true' SIGINT
CONT=false
while ! $CONT
do
sleep 1
done
echo "~~EXIT"
Unable to trap SIGINT signal in a background shell
The bash
manpage on OSX (but it should be the same in other versions) has this to say about signal handling:
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 ignoreSIGINT
andSIGQUIT
in addition to
these inherited handlers.
and further on, under the trap
command:
Signals ignored upon entry to the shell cannot
be trapped or reset.
Since scripts don't use job control by default, this means the case you're talking about.
Why does POSIX demand that system(3) ignores SIGINT and SIGQUIT?
SIGINT
and SIGQUIT
are terminal generated signals. By default, they're sent to the foreground process group when you press Ctrl+C
or Ctrl+\
respectively.
I believe the idea for ignoring them while running a child via system
is that the terminal should be as if it was temporarily owned by the child and Ctrl+C
or Ctrl+\
should temporarily only affect the child and its descendants, not the parent.
SIGCHLD
is blocked so that system
's the SIGCHLD
caused by the child terminating won't trigger a SIGCHLD
handler if you have one, because such a SIGCHLD
handler might reap the child started by system
before system
reaps it.
How do I launch a background process through a wrapper that'll undo the SIGINT ignore?
If the process is execed (=isn't just a subshell but is based on binary), you can run it through a wrapper that'll undo the SIGINT/SIGQUIT ignore:
reset_sigint.c:
#include <signal.h>
#include <unistd.h>
int main(int C, char**V)
{
sigaction(SIGINT,&(struct sigaction){.sa_handler=SIG_DFL}, 0);
execvp(V[1],V+1);
return 127;
}
To prevent the SIGINT/SIGQUIT ignore more generically for any process run as a command in the shell, you can run it with set -m
on
sh -c 'set -m; ( sleep 10 )& ps -p $! -o ignored'
#compare with: sh -c '( sleep 10 )& ps -p $! -o ignored'
but that'll run the command in a separate process group as a (possibly undesirable) sideffect.
Related Topics
Creating Executable Files in Linux
Getting CPU Cycles Using Rdtsc - Why Does the Value of Rdtsc Always Increase
Joining Multiple Fields in Text Files on Unix
How to Determine If a Detached Pthread Is Alive
Remove a Symlink to a Directory
How to Get the Start Time of a Long-Running Linux Process
How to Automatically Add User Account and Password with a Bash Script
How to Monitor the Thread Count of a Process on Linux
How to Use Xargs to Copy Files That Have Spaces and Quotes in Their Names
What Is the Default Register State When Program Launches (Asm, Linux)
Linux Terminal Input: Reading User Input from Terminal Truncating Lines at 4095 Character Limit
Init Function Invocation of Drivers Compiled into Kernel
Why Does Sed Fail with International Characters and How to Fix