16

If in bash I run a | b | c | d on the command line and then press ^C, which process gets the signal?

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
pythonic metaphor
  • 10,296
  • 18
  • 68
  • 110

3 Answers3

17

In short, they all do.

When setting up a pipeline, the shell creates a process group. ^C is interpreted by the kernel's line discipline as the user's request to interrupt the process group currently running in the foreground. Sending a signal such as SIGINT to a process group automatically delivers the signal to all processes in the group.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
7

I like experimentation better:

#!/bin/bash
# FILE /tmp/bla.sh
# trap ctrl-c and call ctrl_c()
trap ctrl_c INT

MY_ID=$1 # Identifier for messages

function ctrl_c() {
    echo >&2 "GOODBYE $MY_ID"
    exit
}

# This will continue until interrupted, e.g. if the input/output get closed
cat
# If we somehow got to the end
echo >&2 "grace $MY_ID"

Chaining them, running and breaking them

nitz@mars:~$ /tmp/bla.sh 1 | /tmp/bla.sh 2
^CGOODBYE 2
GOODBYE 1
0

As you can see, both executions got the interrupt signal, meaning they all get killed. Furthermore, the order in which they output that they were killed is random, e.g.:

nitz@mars:~$ /tmp/bla.sh 1 | /tmp/bla.sh 2 | /tmp/bla.sh 3 | /tmp/bla.sh 4
^CGOODBYE 2
GOODBYE 4
GOODBYE 1
GOODBYE 3
Dan Yard
  • 147
  • 1
  • 5
Nitz
  • 320
  • 2
  • 9
  • 1
    "Furthermore, the order in which they output that they were killed is random" thanks a ton! I think this is really important. – Bora M. Alper Jul 29 '18 at 17:11
0

Building on the excellent (I also love experimentation) answer by @Nitz, if you do not want all the processes to get the signal, you can use bash process substitution (thanks to this answer).

Here's an example that shows the 1 process does get the signal, but the 2 and 3 processes do not.

$ /tmp/bla.sh 1 > >(/tmp/bla.sh 2 | /tmp/bla.sh 3)
^CGOODBYE 1
grace 2
grace 3

You can also redirect stderr and it still works.

My use case was trying to filter output from a command that is the entrypoint for a docker container, but I don't want to mess with signal handling of the main process.

Edit: there seem to be some buffering issues for some combinations of commands, but I don't understand why. So, this may not suit your use case.

# no buffering issues here
(echo 1; sleep 1; echo 2; sleep 2; echo 3) > >(cat)
(echo 1; sleep 1; echo 2; sleep 2; echo 3) > >(cat | cat | cat)
(echo 1; sleep 1; echo 2; sleep 2; echo 3) > >(cat | grep -v blah)
# no output until original command finishes for this *shrugs*
(echo 1; sleep 1; echo 2; sleep 2; echo 3) > >(grep -v blah | cat)
Tom Saleeba
  • 4,031
  • 4
  • 41
  • 36