Home » Why is SIGINT not propagated to child process when sent to its parent process?

Why is SIGINT not propagated to child process when sent to its parent process?


How CTRL+C works

The first thing is to understand how CTRL+C works.

When you press CTRL+C, your terminal emulator sends an ETX character (end-of-text / 0x03).
The TTY is configured such that when it receives this character, it sends a SIGINT to the foreground process group of the terminal. This configuration can be viewed by doing stty -a and looking at intr = ^C;.
The POSIX specification says that when INTR is received, it should send a SIGINT to the foreground process group of that terminal.

What is the foreground process group?

So, now the question is, how do you determine what the foreground process group is?
The foreground process group is simply the group of processes which will receive any signals generated by the keyboard (SIGTSTP, SIGINT, etc).

Simplest way to determine the process group ID is to use ps:

ps ax -O tpgid

The second column will be the process group ID.

How do I send a signal to the process group?

Now that we know what the process group ID is, we need to simulate the POSIX behavior of sending a signal to the entire group.

This can be done with kill by putting a - in front of the group ID.
For example, if your process group ID is 1234, you would use:

kill -INT -1234

Simulate CTRL+C using the terminal number.

So the above covers how to simulate CTRL+C as a manual process. But what if you know the TTY number, and you want to simulate CTRL+C for that terminal?

This becomes very easy.

Lets assume $tty is the terminal you want to target (you can get this by running tty | sed 's#^/dev/##' in the terminal).

kill -INT -$(ps h -t $tty -o tpgid | uniq)

This will send a SIGINT to whatever the foreground process group of $tty is.

As vinc17 says, there’s no reason for this to happen. 
When you type a signal-generating key sequence
(e.g., Ctrl+C),
the signal is sent to all processes that are attached to
(associated with) the terminal. 
There is no such mechanism for signals generated by kill.

However, a command like

kill -SIGINT -12345

will send the signal to all processes in process group 12345;
see kill(1)
and kill(2). 
Children of a shell are typically in the shell’s process group
(at least, if they’re not asynchronous),
so sending the signal to the negative of the PID of the shell may do what you want.


As vinc17 points out, this doesn’t work for interactive shells. 
Here’s an alternative that might work:

kill -SIGINT -$(echo $(ps -pPID_of_shell o tpgid=))

ps -pPID_of_shell gets process information on the shell. 
o tpgid= tells ps to output only the terminal process group ID, with no header. 
If this is less than 10000, ps will display it with leading space(s);
the $(echo …) is a quick trick to strip off leading (and trailing) spaces.

I did get this to work in cursory testing on a Debian machine.

The question contains its own answer. Sending the SIGINT to the cat process with kill is a perfect simulation of what happens when you press Ctrl+C.

To be more precise, the interrupt character (^C by default) sends SIGINT to every process in the terminal’s foreground process group. If instead of cat you were running a more complicated command involving multiple processes, you’d have to kill the process group to achieve the same effect as ^C.

When you run any external command without the & background operator, the shell creates a new process group for the command and notifies the terminal that this process group is now in the foreground. The shell is still in its own process group, which is no longer in the foreground. Then the shell waits for the command to exit.

That’s where you seem to have become the victim by a common misconception: the idea that the shell is doing something to facilitate the interaction between its child process(es) and the terminal. That’s just not true. Once it has done the setup work (process creation, terminal mode setting, creation of pipes and redirection of other file descriptors, and executing the target program) the shell just waits. What you type into cat isn’t going through the shell, whether it’s normal input or a signal-generating special character like ^C. The cat process has direct access to the terminal through its own file descriptors, and the terminal has the ability to send signals directly to the cat process because it’s the foreground process group. The shell has gotten out of the way.

After the cat process dies, the shell will be notified, because it’s the parent of the cat process. Then the shell becomes active and puts itself in the foreground again.

Here is an exercise to increase your understanding.

At the shell prompt in a new terminal, run this command:

exec cat

The exec keyword causes the shell to execute cat without creating a child process. The shell is replaced by cat. The PID that formerly belonged to the shell is now the PID of cat. Verify this with ps in a different terminal. Type some random lines and see that cat repeats them back to you, proving that it’s still behaving normally in spite of not having a shell process as a parent. What will happen when you press Ctrl+C now?


SIGINT is delivered to the cat process, which dies. Because it was the only process on the terminal, the session ends, just as if you’d said “exit” at a shell prompt. In effect cat was your shell for a while.

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...