“cat file | ./binary”和“./binary < file”有什么区别?

Bor*_*ris 106 shell pipe io-redirection

我有一个二进制文件(我无法修改),我可以:

./binary < file
Run Code Online (Sandbox Code Playgroud)

我也可以这样做:

./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF
Run Code Online (Sandbox Code Playgroud)

cat file | ./binary
Run Code Online (Sandbox Code Playgroud)

给我一个错误。我不知道为什么它不适用于管道。在所有 3 种情况下,文件的内容都提供给二进制的标准输入(以不同的方式):

  1. bash 读取文件并将其提供给二进制的标准输入
  2. bash 从 stdin 读取行(直到 EOF)并将其提供给二进制的stdin
  3. cat 读取文件行并将其放入标准输出,bash 将它们重定向到二进制文件的标准输入

据我所知,二进制文件不应该注意到这 3 个之间的区别。有人可以解释为什么第三种情况不起作用吗?

BTW:二进制文件给出的错误是:

20170116/125624.689 - U3000011 无法读取脚本文件“”,错误代码“14”。

但我的主要问题是,具有这 3 个选项的任何程序有何不同。

下面是一些进一步的细节:我试图再次strace的 和实际上有一些错误ESPIPE(非法谋取)lseek的 后面EFAULT(地址错误)读取错误信息之前。

试图用Ruby脚本(不使用临时文件)控制二进制我是金callapi为Automic(UC4)

Sté*_*las 158

./binary < file
Run Code Online (Sandbox Code Playgroud)

binary的 stdin 是以只读模式打开的文件。请注意,bash它根本不读取文件,它只是打开它以读取它binary在其中执行的进程的文件描述符 0 (stdin) 。

在:

./binary << EOF
test
EOF
Run Code Online (Sandbox Code Playgroud)

根据外壳,binary的 stdin 将是一个已删除的临时文件(AT&T ksh、zsh、bash...),其中包含test\n外壳放置的内容或管道的读取端(dash, yash; 并且外壳test\n并行写入在管道的另一端)。在您的情况下,如果您使用的是bash,它将是一个临时文件。

在:

cat file | ./binary
Run Code Online (Sandbox Code Playgroud)

根据外壳,binary的 stdin 将是管道的读取端,或者是写入方向已关闭 (ksh93) 并在另一端cat写入内容的套接字对file的一端。

当 stdin 是常规文件(临时或非临时)时,它是可查找的。binary可以转到开头或结尾,倒带等。它也可以ioctl()s映射它,做一些像 FIEMAP/FIBMAP(如果使用<>而不是<,它可能会截断/打孔等)。

另一方面,管道和套接字对是一种进程间通信方式,binary除了read数据之外没有太多可以做的(尽管也有一些操作,比如某些特定ioctl()于管道的s 可以对它们而不是常规文件执行) .

大多数情况下,这seek是导致应用程序在使用管道时失败/抱怨的缺失能力,但它可能是对常规文件有效但对不同类型文件(如mmap(), ftruncate(), fallocate())无效的任何其他系统调用. 在 Linux 上,当/dev/stdinfd 0 位于管道或常规文件时打开时,行为也有很大差异。

有许多命令只能处理可查找的文件,但在这种情况下,通常不适用于在其标准输入上打开的文件。

$ unzip -l file.zip
Archive:  file.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       11  2016-12-21 14:43   file
---------                     -------
       11                     1 file
$ unzip -l <(cat file.zip)
     # more or less the same as cat file.zip | unzip -l /dev/stdin
Archive:  /proc/self/fd/11
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of /proc/self/fd/11 or
        /proc/self/fd/11.zip, and cannot find /proc/self/fd/11.ZIP, period.
Run Code Online (Sandbox Code Playgroud)

unzip需要读取存储在文件末尾的索引,然后在文件内查找以读取归档成员。但是在这里,文件(在第一种情况下是常规的,在第二种情况下是管道)作为路径参数提供给unzip,并unzip自行打开它(通常在 0 以外的 fd 上),而不是继承调用者已经打开的 fd。它不从其标准输入读取 zip 文件。stdin 主要用于用户交互。

如果您binary在终端仿真器中运行的交互式 shell 的提示下不重定向地运行您的程序,那么binary的 stdin 将从它的调用者 shell 继承,它本身将从它的调用者终端模拟器继承它,并且将是一个pty 设备以读+写模式打开(类似于/dev/pts/n)。

这些设备也是不可搜索的。因此,如果binary从终端获取输入时工作正常,则问题可能与寻求无关。

如果 14 是 errno(由失败的系统调用设置的错误代码),那么在大多数系统上,这将是EFAULT( Bad address )。在read()如果要求读成不可写一个内存地址系统调用会失败与错误。这与 fd 是将数据从点读取到管道还是常规文件无关,并且通常会指示错误1

binary可能确定在其标准输入 (with fstat())上打开的文件类型,并在它既不是常规文件也不是 tty 设备时遇到错误。

如果不了解有关该应用程序的更多信息,就很难说。在strace(或truss/tusc在您的系统上等效)下运行它可以帮助我们查看系统调用是什么,如果这里有任何失败。


1 Matthew Ife在对您的问题的评论中所设想的场景在这里听起来很有道理。引用他的话:

我怀疑它正在寻找文件末尾以获得用于读取数据的缓冲区大小,严重处理了查找不起作用并尝试分配负大小(不处理错误的 malloc)的事实。传递缓冲区以读取给定缓冲区的哪些故障无效。

  • 非常有趣...这是我第一次听说`./binary &lt; file` 风格的重定向标准输入是可搜索的! (15认同)
  • @DavidZ 这是一个被“打开”的文件,它的行为与任何被“打开”的文件相同。它恰好是从父进程继承的,但这并不少见。 (3认同)
  • 如果系统包含 [strace](https://en.wikipedia.org/wiki/Strace) 或类似的工具,则可以使用它来检查二进制文件失败的系统调用。 (3认同)
  • “它还可以截断它、映射它、在其中打孔等。” - 嗯,没有。该文件以只读模式打开。程序必须以写入模式打开它才能做到这一点。但是它不能在写入模式下打开它,因为没有直接执行此操作的接口,也没有任何接口可以找到对应于打开文件的“该”目录条目(如果有两个这样的 dentry 或零呢?) . 它必须对文件进行统计,然后扫描文件系统以查找具有相同 inode 编号的对象。那会非常慢。 (2认同)

mur*_*uru 47

这是一个简单的示例程序,它说明了Stéphane Chazelaslseek(2)在其输入上使用的答案

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int c;
    off_t off;
    off = lseek(0, 10, SEEK_SET);
    if (off == -1)
    {
        perror("Error");
        return -1;
    }
    c = getchar();
    printf("%c\n", c);
}
Run Code Online (Sandbox Code Playgroud)

测试:

$ make seek
cc     seek.c   -o seek
$ cat foo
abcdefghijklmnopqrstuwxyz
$ ./seek < foo
k
$ ./seek <<EOF
> abcdefghijklmnopqrstuvwxyz
> EOF
k
$ cat foo | ./seek
Error: Illegal seek
Run Code Online (Sandbox Code Playgroud)

管道是不可搜索的,这是一个程序可能会抱怨管道的地方。


Ser*_*nyy 21

管道和重定向是不同的动物,可以这么说。当您使用here-doc重定向 ( <<) 或重定向标准输入时< ,文本不会凭空出现 - 它实际上进入文件描述符(或临时文件,如果您愿意),这就是二进制文件的标准输入所指向的地方。

具体来说,这里是bash's源代码的摘录,redir.c 文件(版本 4.3):

/* Create a temporary file holding the text of the here document pointed to
   by REDIRECTEE, and return a file descriptor open for reading to the temp
   file.  Return -1 on any error, and make sure errno is set appropriately. */
static int
here_document_to_fd (redirectee, ri)
Run Code Online (Sandbox Code Playgroud)

因此,由于重定向基本上可以被视为文件,二进制文件可以导航它们,或者seek()轻松地浏览文件,跳转到文件的任何字节。

Pipes ,因为它们是 64 KiB 的缓冲区(至少在 Linux 上),写入 4096 字节或更少,保证是原子的,因此不可查找,即您不能自由导航它们 - 只能按顺序读取。我曾经tail在 python 中实现过命令。如果重定向,可以在微秒内搜索 2900 万行文本,但是如果cat通过 pipe 进行搜索,那么,没有什么可以做的 - 所以必须按顺序阅读。

另一种可能性是二进制文件可能想要专门打开一个文件,并且不想从管道接收输入。它通常通过fstat()系统调用完成,并检查输入是否来自一种S_ISFIFO文件(表示管道/命名管道)。

您的特定二进制文件,因为我们不知道它是什么,可能会尝试寻找,但无法寻找管道。建议您查阅其文档以了解错误代码 14 的确切含义。

注意:某些 shell,例如 dash(Debian Almquist Shell,/bin/shUbuntu 上的默认设置)在内部here-doc使用管道实现重定向,因此可能无法查找。要点保持不变 - 管道是连续的,无法轻松导航,尝试这样做会导致错误。


Vou*_*uze 5

主要区别在于错误处理。

以下情况报错

$ /bin/cat < z.txt
-bash: z.txt: No such file or directory
$ echo $?
1
Run Code Online (Sandbox Code Playgroud)

在以下情况下不报告错误。

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)

使用 bash,您仍然可以使用 PIPESTATUS :

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo ${PIPESTATUS[0]}
1
Run Code Online (Sandbox Code Playgroud)

但它仅在命令执行后立即可用:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
$ echo ${PIPESTATUS[0]}
0
# oops !
Run Code Online (Sandbox Code Playgroud)

当我们使用 shell 函数而不是二进制文件时,还有另一个区别。在 中bash,作为管道一部分的函数在子 shell 中执行(如果lastpipe启用了该选项并且bash是非交互式的,则最后一个管道组件除外),因此变量的更改在父 shell 中没有影响:

$ a=a
$ b=b
$ x(){ a=x;}
$ y(){ b=y;}

$ echo $a $b
a b

$ x | y
$ echo $a $b
a b

$ cat t.txt | y
$ echo $a $b
a b

$ x | cat
$ echo $a $b
a b

$ x < t.txt
$ y < t.txt
$ echo $a $b
x y
Run Code Online (Sandbox Code Playgroud)

  • 所以,你展示了使用 `&gt;` 的错误处理是由 shell 完成的,但是使用管道它是由生成文本的命令完成的。好的。但是在这个特定问题中,OP 使用的是现有文件,所以这不是问题,并且显然产生的错误是二进制文件。 (5认同)