If I run a command like:
cat <(echo 1 | pv) | pv
cat <(echo 1 | pv) | less
cat <(echo 1 | pv) | cat
The command seems to run forever. Entering ^C
(SIGINT) kills the entire shell instead of just the commands executed. Why is this the case?
Relevant output of ps xf
from another shell on minimal case cat <(pv) | less
:
Ss /bin/bash
S+ \_ cat /dev/fd/XX
S | \_ /bin/bash
T | \_ pv
S+ \_ less
With these file descriptors open:
bash
0 -> /dev/pts/YY
1 -> /dev/pts/YY
2 -> /dev/pts/YY
255 -> /dev/pts/YY
cat /dev/fd/ZZ
0 -> /dev/pts/YY
1 -> pipe:[RRRRRRRR]
2 -> /dev/pts/YY
3 -> pipe:[QQQQQQQQ]
ZZ -> pipe:[QQQQQQQQ]
bash
0 -> /dev/pts/YY
1 -> pipe:[QQQQQQQQ]
2 -> /dev/pts/YY
255 -> /dev/pts/YY
pv
0 -> /dev/pts/YY
1 -> pipe:[QQQQQQQQ]
2 -> /dev/pts/YY
less
0 -> pipe:[RRRRRRRR]
1 -> /dev/pts/YY
2 -> /dev/pts/YY
3 -> /dev/tty
Using the original example, cat <(echo 1 | pv) | less
(this also happens when echo
is not a bash builtin but another program like dd if=/dev/zero bs=1 count=1
:
Ss /bin/bash
S+ \_ cat /dev/fd/63
S | \_ /bin/bash
T | \_ pv
S+ \_ less
bash
0 -> /dev/pts/18
1 -> /dev/pts/18
2 -> /dev/pts/18
255 -> /dev/pts/18
cat /dev/fd/63
0 -> /dev/pts/18
1 -> pipe:[36932796]
2 -> /dev/pts/18
3 -> pipe:[36929317]
63 -> pipe:[36929317]
bash
0 -> /dev/pts/18
1 -> pipe:[36929317]
2 -> /dev/pts/18
255 -> /dev/pts/18
pv
0 -> pipe:[36930391]
1 -> pipe:[36929317]
2 -> /dev/pts/18
less
0 -> pipe:[36932796]
1 -> /dev/pts/18
2 -> /dev/pts/18
3 -> /dev/tty
Best Answer
It happens because the
<(
process)
isn't properly job-controlled - it's just forked and forgot. That doesn't matter most of the time, because almost as soon as it is born that process is placed in a separate process group and backgrounded. For the instant that the shell requires to open input and output for that process, however, that is the tty's foreground process group, and, as such, vulnerable to SIGINT - unless it is otherwise trapped or ignored as your interactive shell generally does.The thing is, though: you have a pipe deadlock there. When the parent attempts to open output for that process the pipe blocks. It never gets the chance to change the process group and all the rest because when you
CTRL+C
the foreground group is killed - it's sent SIGINT - and when the foreground group dies and the parent cannot resume control because it's still blocked on a pipe, the terminal sends a HUP because there's nobody home. kaboomYou need a writer first, then a reader for every pipe you open before you can open another.