为什么 `cat <(cat)` 会产生 EIO?

Why does `cat <(cat)` produce EIO?

我有一个程序可以同时读取两个输入文件。我想从标准输入中读取这个程序。我以为我会使用这样的东西:

$program1 <(cat) <($program2)

但我刚刚发现

cat <(cat)

产生

....
mmap2(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb758e000
read(0, 0xb758f000, 131072)             = -1 EIO (Input/output error)
....
cat: -: Input/output error

同样,

$ cat <(read -n 1)
bash: read: read error: 0: Input/output error

所以... Linux 在系统调用级别 read 失败。那很有意思。 bash 没有将标准输入连接到子外壳吗? :(

有解决办法吗?我特别需要使用进程替换(... <(...) 格式),因为 $program1tail,顺便说一下)需要文件,我需要做一些预处理(使用 od)在我将它传递给 tail 之前的标准输入 - 我不能只指定 /dev/stdin

编辑:

我真正想做的是从文件中读取(另一个进程将写入该文件),而我从标准输入中读取以便我可以接受命令等。我希望我能做到

tail -f <(od -An -vtd1 -w1) <(cat fifo)

同时从标准输入 FIFO 读取并将其放入单个标准输出流中,我可以 运行 通过 awk(或类似的)。我知道我可以用任何脚本语言轻松解决这个问题,但我喜欢学习如何让 bash 做所有事情 :P

编辑 2:我问 a new question 更全面地解释了我在上面描述的上下文。

1。解释为什么 cat <(cat) 产生 EIO

( 我正在使用 Debian Linux 8.7, Bash 4.4.12 )

让我们用长 运行 <(sleep) 替换 <(cat) 看看发生了什么。

来自 pty #1:

$ echo $$
906
$ tty
/dev/pts/14
$ cat <(sleep 12345)

转到另一个 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
$

我来解释一下(根据APUE book,第二版):

  1. TPGID29999 表示 cat (PID 29999) 是现在控制终端的前台进程组 (pts/14) .而sleep在后台进程组(PGID906).
  2. 906 的进程组现在是一个孤立进程组,因为"the parent of every member is either itself a member of the group or is not a member of the group’s session"。 (PID 906 的 PPID 是 903903 在不同的会话中。)
  3. 当孤立的后台进程组中的进程从其控制终端读取时,read() 将失败并显示 EIO

2。解释为什么 cat <(cat) 有时 有效 (不是真的!)

Daniel Voina 在评论中提到 cat <(cat) 在 OS X 上 与 Bash 3.2.57 有效。我刚刚也在 Linux 上用 Bash 4.4.12.

重现了它

来自 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.

(第一个 cat <(cat)EIO 失败已在我的回答的第一部分进行了解释。)

转到另一个 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#

让我们看看发生了什么:

  1. TPGID10805 表示 tac (PID 10805) 是前台进程组,现在正在控制终端 (pts/0).而cat(PID10806)在后台进程组(PGID10803).

  2. 但这次 pgrp 10803 不是孤立的,因为它的成员 PID 10803 (bash) 的父级 (PID 10732, bash) 在另一个 pgrp (PGID 10732) 中并且在同一个会话 (SID 10732) 中。

  3. 根据APUE bookSIGTTIN将是"generated by the terminal driver when a process in a (non-orphaned) background process group tries to read from its controlling terminal"。因此,当 cat 读取标准输入时,SIGTTIN 将发送给它,默认情况下,此信号将 停止 进程。这就是 catSTAT 列在 ps 输出中显示为 T(已停止)的原因。由于它已停止,我们从键盘输入的数据根本不会发送给它。所以它只是 看起来像 它在工作,但实际上不是。

结论:

因此不同的行为(EIOSIGTTIN)取决于当前 Bash 是否是会话领导者。 (在我的回答的第一部分,PID 906的bash是session leader,但是第二部分PID 10803的bash不是session leader .)

接受的答案解释了原因,但我看到了一个可以解决它的解决方案。它是通过附加 () 对其进行子壳化,例如:

(cat <(cat))

请在此处找到解决方案的详细信息: https://unix.stackexchange.com/a/244333/89706