当管道和重定向时,stdin的行为会有所不同

时间:2013-05-10 12:38:34

标签: bash stdin

我正在尝试将信息传递给不接受来自stdin的输入的程序。为此,我使用/ dev / stdin作为参数,然后尝试输入我的输入。我注意到如果我用管道字符做这个:

[pkerp@comp ernwin]$ cat fess/structures/168d.pdb | MC-Annotate /dev/stdin

我没有输出。但是,如果我使用左插入符字符做同样的事情,它可以正常工作:

[pkerp@plastilin ernwin]$ MC-Annotate /dev/stdin < fess/structures/168d.pdb
Residue conformations -------------------------------------------
A1 : G C3p_endo anti
A2 : C C3p_endo anti
A3 : G C3p_endo anti

我的问题是,这两项行动有何不同,为什么会有不同的结果呢?作为一个额外的问题,是否有一个适当的术语来指定输入使用'&lt;'符号

更新

我目前最好的猜测是,正在运行的程序内部使用的是文件中的搜索。下面的答案似乎表明它与文件指针有关,但运行以下小测试程序:

#include <stdio.h>

int main(int argc, char *argv[])
{   
    FILE *f = fopen(argv[1], "r");
    char line[128];

    printf("argv[1]: %s f: %d\n", argv[1], fileno(f));

    while (fgets(line, sizeof(line), f)) {
    printf("line: %s\n", line);
    }

    printf("rewinding\n");
    fseek(f, 0, SEEK_SET);

    while (fgets(line, sizeof(line), f)) {
    printf("line: %s\n", line);
    }
    fclose(f);
}

表示在fseek函数调用之前,所有内容都相同:

[pete@kat tmp]$ cat temp | ./a.out /dev/stdin
argv[1]: /dev/stdin f: 3
line: abcd

rewinding
===================
[pete@kat tmp]$ ./a.out /dev/stdin < temp
argv[1]: /dev/stdin f: 3
line: abcd

rewinding
line: abcd

使用过程替换正如Christopher Neylan建议的那样导致上面的程序挂起甚至没有读取输入,这看起来有点奇怪。

[pete@kat tmp]$ ./a.out /dev/stdin <( cat temp )
argv[1]: /dev/stdin f: 3

查看strace输出确认我怀疑尝试了一个在管道版本中失败的搜索操作:

_llseek(3, 0, 0xffffffffffd7c7c0, SEEK_CUR) = -1 ESPIPE (Illegal seek)

在重定向版本中取得成功。

_llseek(3, 0, [0], SEEK_CUR)            = 0 

故事的寓意:不要随便尝试用/dev/stdin替换一个论点并尝试去管它。它可能会起作用,但也可能不行。

3 个答案:

答案 0 :(得分:1)

问题在于打开文件进行阅读的顺序。

/dev/stdin不是真正的文件;它是当前进程用作标准输入的文件的符号链接。在典型的shell中,它链接到终端,并由shell启动的任何进程继承。请记住,MC-Annotate只会读取作为参数提供的文件。

在管道示例中,/dev/stdinMC-Annotate作为标准输入继承的文件的符号链接:终端。它可能在一个新的描述符上打开这个文件(比方说3,但它可以是任何大于2的值)。管道将cat的输出连接到MC-Annotate's标准输入(文件描述符0),MC-Annotate继续忽略该输出以支持它直接打开的文件。

在重定向示例中,shell在运行 fess/structures/168d.pdb之前将MC-Annotate直接连接到文件描述符0 。当MC-Annotate启动时,它会再次尝试打开/dev/stdin,这次指向fess/structures/168d.pdb而不是终端。

所以答案在于哪个文件/dev/stdin是执行MC-Annotate的进程中的链接;在进程启动之前设置 shell重定向; 流程启动后的管道

这有用吗?

cat fess/structures/168d.pdb | MC-Annotate <( cat /dev/stdin )

类似命令

echo foo | cat <( cat /dev/stdin )

似乎有效,但我不会声称情况是相同的。


[更新:不起作用。 /dev/stdin仍然是终端的链接,而不是管道。]

这可能提供一种解决方法。现在,MC-Annotate从子shell继承其标准输入,而不是当前shell,并且子shell的输出来自cat作为其标准输入,而不是终端。

cat fess/structures/168d.pdb | ( MC-Annotate /dev/stdin )

它认为一个简单的命令组也可以运行:

cat fess/structures/168d.pdb | { MC-Annotate /dev/stdin; }

答案 1 :(得分:1)

这两个命令之间应该没有功能差异。实际上,我无法重现您所看到的内容:

#! /usr/bin/perl
# test.pl
# this is a test Perl script that will read from a filename passed on the command line, and print what it reads.

use strict;
use warnings;

print $ARGV[0], " -> ", readlink( $ARGV[0] ), " -> ", readlink( readlink($ARGV[0]) ), "\n";
open( my $fh, "<", $ARGV[0] ) or die "$!";
while( defined(my $line = <$fh>) ){
        print "READ: $line";
}
close( $fh );

以三种方式运行:

(caneylan@faye.sn: tmp)$ cat input
a
b
c
d

(caneylan@faye.sn: tmp)$ ./test.pl /dev/stdin
/dev/stdin -> /proc/self/fd/0 -> /dev/pts/0
this is me typing into the terminal
READ: this is me typing into the terminal

(caneylan@faye.sn: tmp)$ cat input | ./test.pl /dev/stdin
/dev/stdin -> /proc/self/fd/0 -> pipe:[1708285]
READ: a
READ: b
READ: c
READ: d

(caneylan@faye.sn: tmp)$ ./test.pl /dev/stdin < input
/dev/stdin -> /proc/self/fd/0 -> /tmp/input
READ: a
READ: b
READ: c
READ: d

首先请注意/dev/stdin是什么:

(caneylan@faye.sn: tmp)$ ls -l /dev/stdin
lrwxrwxrwx 1 root root 15 Apr 21 15:39 /dev/stdin -> /proc/self/fd/0

(caneylan@faye.sn: tmp)$ ls -l /proc/self
lrwxrwxrwx 1 root root 0 May 10 09:44 /proc/self -> 27565

它始终是/proc/self/fd/0的符号链接。 /proc/self本身是指向当前进程/proc下目录的特殊链接。所以/dev/stdin将始终指向当前进程的fd 0。因此,当您运行MC-Annotate时(或者,在我的示例中,test.pl),文件/dev/stdin将解析为/proc/$pid/fd/0,无论进程ID为MC-Annotate是。这只是/dev/stdin的符号链接如何工作的结果。

正如您在我的示例中所见,当您使用管道(|)时,/proc/self/fd/0将指向由cat设置的管道的读取端。贝壳。当您使用重定向(<)时,/proc/self/fd/0将直接指向由shell设置的输入文件。

至于为什么你会看到这种奇怪的行为 - 我猜想MC-Annotate在打开它之前对文件类型进行了一些检查,并且它看到/ dev / stdin指向一个命名管道而不是一个普通文件,并正在拯救。您可以通过阅读MC-Annotate的源代码或使用strace命令来查看内部发生的情况来确认这一点。

请注意,这两种方法在Bash中都有点过时。将流程输出导入只打开文件名的程序的可接受方法是使用process substitution

$ MC-Annotate <(cat fess/structures/168d.pdb)

<(...)构造将文件描述符返回到来自...所有内容的管道的读取端:

(caneylan@faye.sn: tmp)$ echo <(true | grep example | cat)
/dev/fd/63

答案 2 :(得分:0)

查看有关MC-Annotate http://bioinfo.cipf.es/ddufour/doku.php?id=mc-annotate的信息管道无效的原因是因为MC-Annotate未将文件中的cat输出识别为{{1}类型之一}

管道链命令将第一个输出用作下一个输出。

'&lt;' ('小于','左箭头','左尖括号')将文件输入命令。

http://tldp.org/LDP/abs/html/io-redirection.html#IOREDIRECTIONREF2