Why does `cat <(cat)` produce EIO?
1. Explain why cat <(cat)
produces EIO
( I'm using Debian Linux 8.7, Bash 4.4.12 )
Let's replace <(cat)
with the long running <(sleep)
to see what's happening.
From pty #1:
$ echo $$
906
$ tty
/dev/pts/14
$ cat <(sleep 12345)
Go to another pty #2:
$ ps t pts/14 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
903 906 906 906 pts/14 29999 Ss 0 0:00 bash
906 29998 906 906 pts/14 29999 S 0 0:00 bash
29998 30000 906 906 pts/14 29999 S 0 0:00 sleep 12345
906 29999 29999 906 pts/14 29999 S+ 0 0:00 cat /dev/fd/63
$ ps p 903 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 903 903 903 ? -1 Ss 0 0:07 SCREEN -T linux -U
$
Let me explain it (according to the APUE book, 2nd edition):
- The
TPGID
being29999
indicates thatcat
(PID29999
) is the foreground process group which is now controlling the terminal (pts/14
). Andsleep
is in the background process group (PGID906
). - The process group of
906
is now an orphaned process group because "the parent of every member is either itself a member of the group or is not a member of the group’s session". (The PID906
's PPID is903
and903
is in a different session.) - When the process in an orphaned background process group reads from its controlling terminal,
read()
would fail withEIO
.
2. Explain why cat <(cat)
sometimes works (not really!)
Daniel Voina mentioned in a comment that cat <(cat)
works on OS X with Bash 3.2.57
. I just managed to reproduce it also on Linux with Bash 4.4.12
.
From pty #1:
bash-4.4# echo $$
10732
bash-4.4# tty
/dev/pts/0
bash-4.4# cat <(cat)
cat: -: Input/output error
bash-4.4#
bash-4.4#
bash-4.4# bash --norc --noprofile # start a new bash
bash-4.4# tac <(cat)
<-- It's waiting here so looks like it's working.
(The first cat <(cat)
failing with EIO
was explained in the first part of my answer.)
Go to another pty #2:
bash-4.4# ps t pts/0 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
10527 10732 10732 10732 pts/0 10805 Ss 0 0:00 bash
10732 10803 10803 10732 pts/0 10805 S 0 0:00 bash --norc --noprofile
10803 10804 10803 10732 pts/0 10805 S 0 0:00 bash --norc --noprofile
10804 10806 10803 10732 pts/0 10805 T 0 0:00 cat
10803 10805 10805 10732 pts/0 10805 S+ 0 0:00 tac /dev/fd/63
bash-4.4# ps p 10527 j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
10526 10527 10527 10527 ? -1 Ss 0 0:00 SCREEN -T dtterm -U
bash-4.4#
Let's see what's happening:
The
TPGID
being10805
indicates thattac
(PID10805
) is the foreground process group which is now controlling the terminal (pts/0
). Andcat
(PID10806
) is in the background process group (PGID10803
).But this time the pgrp
10803
is not orphanded because its member PID10803
(bash
)'s parent (PID10732
,bash
) is in another pgrp (PGID10732
) and it's in the same session (SID10732
).According to the APUE book,
SIGTTIN
will be "generated by the terminal driver when a process in a (non-orphaned) background process group tries to read from its controlling terminal". So whencat
reads stdin,SIGTTIN
will be sent to it and by default this signal would stop the process. That's why thecat
'sSTAT
column was shown asT
(stopped) in theps
output. Since it's stopped the data we input from keyboard are not sent to it at all. So it just looks like it's working but it's not really.
Conclusion:
So the different behaviors (EIO
vs. SIGTTIN
) depend on whether the current Bash is a session leader or not. (In the 1st part of my answer, the bash of PID 906
is the session leader, but the bash of PID 10803
in the 2nd part is not the session leader.)
The accepted answer explained why, but I saw one solution which can solve it. It is by subshelling it with additional ()
, such as:
(cat <(cat))
Please find the solution details here: https://unix.stackexchange.com/a/244333/89706