bash - stdin 在管道传输和重定向时表现不同

标签 bash 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

我的问题是,这两种操作有什么不同,为什么它们会产生不同的结果?作为奖励问题,使用“<”符号指定输入是否有适当的术语?

更新:

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

#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 替换参数并尝试通过管道传递给它。它可能有效,但也可能无效。

最佳答案

这两个命令在功能上应该没有区别。事实上,我无法重现您所看到的:

#! /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 的符号链接(symbolic link). /proc/self本身是指向/proc 下目录的特殊链接对于当前进程。所以/dev/stdin将始终指向当前进程的 fd 0。所以当你运行 MC-Annotate (或者,在我的示例中,test.pl ),文件 /dev/stdin将解析为 /proc/$pid/fd/0 ,对于 MC-Annotate 的任何进程 ID是。这只是 /dev/stdin 的符号链接(symbolic link)的结果。有效。

正如您在上面的示例中看到的那样,当您使用管道 ( | ) 时,/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

关于bash - stdin 在管道传输和重定向时表现不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16482447/

相关文章:

c - 断言标准输入是开放的

bash - 我的 awk 用户函数无法在 bash 脚本中运行

bash - 无法将 bash 输出到文件

java - 从 java 到 python 写入 stdin

ruby - 是否可以有多个 STDIN 流?

java - 使用 nextInt() 方法后使用 nextLine() 方法

linux - 如何为自定义 bash 命令添加前缀?

java - 编译依赖于jar的打包项目

bash - 如何创建 100 个文件,每个文件中有 1 个随机数,并根据数字授予它们权限

php - 如何使用 php 命令行在一行中获取多个标准输入