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

i33*_*36_ 10 io bash stdin subshell process-substitution

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

$program1 <(cat) <($program2)
Run Code Online (Sandbox Code Playgroud)

但我刚刚发现了

cat <(cat)
Run Code Online (Sandbox Code Playgroud)

产生

....
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
Run Code Online (Sandbox Code Playgroud)

同样地,

$ cat <(read -n 1)
bash: read: read error: 0: Input/output error
Run Code Online (Sandbox Code Playgroud)

所以...... Linux无法进入read系统调用级别.那很有意思.是bash不是将stdin连接到子壳?:(

这个问题有方法解决吗?我特别需要使用进程替换(... <(...)格式),因为$program1(tail顺便说一下)需要文件,我需要od在标准输入上做一些预处理(with )才能传递给它tail- 我不能只指定/dev/stdin等等.

编辑:

我真正想要做的是从一个文件(另一个进程将写入)读取,同时我从标准输入读取,所以我可以接受命令等.我希望我能做到

tail -f <(od -An -vtd1 -w1) <(cat fifo)
Run Code Online (Sandbox Code Playgroud)

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

编辑2:我问了一个新问题,更全面地解释了我刚才描述的背景.

pyn*_*exj 10

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

(我使用的是Debian Linux 8.7,Bash 4.4.12)

让我们<(cat)用长跑<(sleep)来代替看看发生了什么.

pty#1:

$ echo $$
906
$ tty
/dev/pts/14
$ cat <(sleep 12345)
Run Code Online (Sandbox Code Playgroud)

去另一个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
$
Run Code Online (Sandbox Code Playgroud)

让我解释一下(根据APUE的书,第2版):

  1. TPGID存在29999表明,cat(PID 29999)是目前正在控制终端前台进程组(pts/14).并且sleep是在后台进程组(PGID 906)中.
  2. 进程组906现在是一个孤立的进程组,因为"每个成员的父成员本身就是该成员的成员或者不是该成员的成员".(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.
Run Code Online (Sandbox Code Playgroud)

(我的答案的第一部分解释了第一个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#
Run Code Online (Sandbox Code Playgroud)

让我们看看发生了什么:

  1. TPGID存在10805表明,tac(PID 10805)是目前正在控制终端前台进程组(pts/0).并且cat(PID 10806)在后台进程组(PGID 10803)中.

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

  3. 根据APUE书,SIGTTIN"当(非孤立的)后台进程组中的进程试图从其控制终端读取时由终端驱动程序生成".因此,当cat读取stdin时,SIGTTIN将被发送到它,并且默认情况下此信号将停止该过程.这就是为什么catSTAT柱显示为T中(停止)ps输出.由于它已停止,我们从键盘输入的数据根本不会发送到它.所以看起来看起来很有效,但事实并非如此.

结论:

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