7

我正在尝试将信息传递到一个不接受来自标准输入的输入的程序中。为此,我使用 /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

我的问题是,这两种操作有什么区别,为什么它们会给出不同的结果?作为一个额外的问题,是否有使用“<”符号指定输入的适当术语?

更新:

我目前最好的猜测是正在运行的程序内部的某些东西利用了文件中的查找。下面的答案似乎表明它与文件指针有关,但运行以下小测试程序:

#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并试图用它来表达它。它可能有效,但也可能无效。

4

3 回答 3

2

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

#! /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(或者,在我的示例中为)时,无论进程 ID是什么test.pl,该文件/dev/stdin都将解析为 。这只是符号链接如何工作的结果。/proc/$pid/fd/0MC-Annotate/dev/stdin

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

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

请注意,这两种方法在 Bash 中都有点迂回。将进程的输出获取到只会打开文件名的程序中的公认方法是使用进程替换

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

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

(caneylan@faye.sn: tmp)$ echo <(true | grep example | cat)
/dev/fd/63
于 2013-05-10T16:14:47.037 回答
1

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

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

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

在重定向示例中,shell在运行之前fess/structures/168d.pdb直接连接到文件描述符 0 。启动时,它再次尝试打开,这一次指向而不是终端。 MC-AnnotateMC-Annotate/dev/stdinfess/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从 subshel​​l 继承其标准输入,而不是当前 shell,并且 subshel​​l 的输出cat作为其标准输入,而不是终端。

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

它认为一个简单的命令组也可以工作:

cat fess/structures/168d.pdb | { MC-Annotate /dev/stdin; }
于 2013-05-10T13:38:15.323 回答
0

通过查看有关 MC-Annotate的信息http://bioinfo.cipf.es/ddufour/doku.php?id=mc-annotate 管道不起作用的原因是因为 MC-Annotate 无法识别cat文件的输出作为类型之一.pbd

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

'<'('小于','左箭头','左尖括号')将文件输入到命令中。

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

于 2013-05-10T12:50:07.957 回答