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

11

我有一个同时从两个输入文件读取数据的程序。我想让这个程序从标准输入读取数据。我打算使用类似以下的代码:

$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在系统调用级别上无法“读取”。这很有趣。是bash没有将stdin连接到子shell吗? :(

是否有解决方案?我需要特别使用进程替换(... <(...)格式),因为$program1(顺便说一下,是tail)期望文件,并且我需要对标准输入进行一些预处理(使用od),然后才能将其传递给tail - 我不能只指定/dev/stdin等。

编辑:

实际上,我想要做的是从一个文件中读取(另一个进程将写入该文件),同时我还可以从标准输入中读取,以便我可以接受命令等等。我希望我可以这样做:

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

我希望能够同时从标准输入和FIFO中读取,并将其传输到单个stdout流中,以便可以通过awk(或类似工具)运行。我知道可以在任何脚本语言中轻松解决此问题,但我喜欢学习如何让bash做一切:P

编辑2:我已经提出了一个新问题,更充分地解释了我上面描述的背景。


3
有趣的是它在OS-X上运行。所以不是像我最初怀疑的那样来自bash。这很有趣。 - Daniel Voina
@DanielVoina:谢谢你提供的信息,我对其他平台非常感兴趣! - i336_
2
在StackOverflow上看到这样的问题让我对编程行业充满了希望。 - Leo Izen
@DanielVoina -- 你能试着在你的OSX上按照我在另一个答案中提到的方式尝试 ssh user@host 吗? - pynexj
@whjm:在ssh会话中,行为也存在缺陷,请参见此处:http://pastebin.com/BRQCDV2G - Daniel Voina
显示剩余4条评论
2个回答

13

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(PID29999)是前台进程组,现在正在控制终端(pts/14)。而sleep处于后台进程组(PGID906)。
  2. 906的进程组现在是一个孤立的进程组,因为“每个成员的父进程要么是它自己的成员,要么不是该组会话的成员”(PID906的PPID是903903在不同的会话中)。
  3. 当孤立的后台进程组中的进程从其控制终端读取时,read()将失败并返回EIO

2. 解释一下为什么cat <(cat)有时可以“工作”(实际上不行!)

Daniel Voina在评论中提到,在带有Bash 3.2.57的OS X上,cat <(cat)是可以正常工作的。我刚刚也在带有Bash 4.4.12的Linux上成功复制了它。

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

(第一个出现EIO错误的cat <(cat)已在我的回答的第一部分中解释。)

进入另一个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 (PID 10806) 是后台进程组 (PGID 10803) 的成员。

  2. 但是这次,pgrp 10803 没有变成孤儿进程,因为它的成员 PID 10803 (bash) 的父进程 (PID 10732, bash) 在另一个进程组 (PGID 10732) 中,并且它们在同一个会话中 (SID 10732)。

  3. 根据APUE book,当处于非孤儿状态下的后台进程组尝试从其控制终端读取数据时,终端驱动程序将会生成SIGTTIN信号。所以当cat读取标准输入时,SIGTTIN信号将被发送给它,默认情况下此信号会停止该进程。这就是为什么ps输出中catSTAT列显示为T (已停止)。由于它已经被停止,我们从键盘输入的数据根本没有被发送给它。所以它只是看起来在工作,但实际上并不是。

结论:

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


增加了关于903的信息。但是在这里,我们只需要知道它在另一个会话中。 - pynexj
a) 现在我得想办法找到第二个途径。 b) 非常感谢这份有用的信息 :) (PS:GNU的sleep命令可以接受未记录的“infinity”参数。) - i336_
我从来不知道。并不像输入一个大数字那么容易。 :) - pynexj
有点傻的问题,为什么我们不能使用 /dev/stdin 并与管道结合起来呢? 类似 some_filtering_cmd | real_cmd /dev/stdin <(other_cmd) | tail 等。 - Leo Izen
@LeoIzen:我发布了一个新问题(http://stackoverflow.com/questions/42859785/how-can-i-select-ie-simultaneously-read-from-standard-input-and-a-file-in),提供了完整的背景和细节,供您参考。 - i336_
显示剩余2条评论

1
已被接受的答案解释了原因,但我看到了一个可行的解决方案。它是通过使用附加的()来进行子shell处理,例如:(cat <(cat))
请在此处找到解决方案的详细信息: https://unix.stackexchange.com/a/244333/89706

感谢提供交叉链接。我肯定自己永远也找不到那个问题。 - i336_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接