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

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

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

./binary < file

我也可以做:

./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF

cat file | ./binary

给我一个错误。我不知道为什么它不能与管道一起使用。在所有 3 种情况下,内容文件被赋予的标准输入二进制(以不同的方式):

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

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

顺便说一句:给出的错误二进制是:

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

但我的主要问题是,有什么区别任何程序有这 3 个选项。

以下是一些进一步的细节:我再次尝试了斯特雷斯 事实上有一些错误ESPIPE(非法寻求)查找 其次是EFAULT(地址错误)就在错误消息之前。

我尝试使用 ruby​​ 脚本(不使用临时文件)控制的二进制文件是卡拉皮自动 (UC4)

答案1

./binary < file

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

在:

./binary << EOF
test
EOF

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

在:

cat file | ./binary

根据 shell 的不同,binary的 stdin 要么是管道的读取端,要么是套接字对的一端,其中写入方向已关闭 (ksh93),并且正在写入另一端cat的内容。file

当 stdin 是常规文件(临时或非临时)时,它是可查找的。binary可能会转到开头或结尾、倒回等。它还可以映射它,执行一些ioctl()s类似 FIEMAP/FIBMAP 的操作(如果使用<>代替<,它可以在其中截断/打孔等)。

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

大多数时候,正是缺少seek这种能力导致应用程序在使用管道时失败/抱怨,但它可能是对常规文件有效但对不同类型的文件无效的任何其他系统调用(如mmap()ftruncate()fallocate()) 。在 Linux 上,/dev/stdin当 fd 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.

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

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

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

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

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

如果不了解更多有关应用程序的信息,很难说清楚。在strace(或您系统上的等效truss项)下运行它tusc可以帮助我们了解系统调用是什么(如果此处失败)。


1设想的场景马修·伊夫对你的问题的评论在这里听起来很合理。引用他的话:

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

答案2

这是一个简单的示例程序,说明了斯特凡·查泽拉斯的回答使用lseek(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);
}

测试:

$ make seek
cc     seek.c   -o seek
$ cat foo
abcdefghijklmnopqrstuwxyz
$ ./seek < foo
k
$ ./seek <<EOF
> abcdefghijklmnopqrstuvwxyz
> EOF
k
$ cat foo | ./seek
Error: Illegal seek

管道是不可查找的,这是程序可能抱怨管道的地方。

答案3

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

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)

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

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

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

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

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

答案4

主要区别在于错误处理。

以下情况报错

$ /bin/cat < z.txt
-bash: z.txt: No such file or directory
$ echo $?
1

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

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0

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

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo ${PIPESTATUS[0]}
1

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

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
$ echo ${PIPESTATUS[0]}
0
# oops !

当我们使用 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

相关内容