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


102

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

./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)并将其提供给binary的 stdin
  3. cat读取文件行并将其放入stdout,bash将其重定向到二进制的 stdin

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

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

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

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

以下是一些详细信息:我使用strace再次尝试了该 方法,实际上在出现错误消息之前,有来自lseek的ESPIPE(非法查找)错误, 接着是来自读取EFAULT(错误地址)错误。

我试着用Ruby脚本(不使用临时文件)来控制二进制是部分callapi为Automic(UC4)


25
很酷,您的二进制文件中嵌入了一个UUOC检测器。我要它。
xhienne

4
它是什么操作系统(因此如果要表示errno,我们可以知道14是什么)吗?
斯特凡Chazelas

6
即使程序有可能以这种方式做出反应,但这样做确实是个小错误。当stdin是tty时,每个根本不需要stdin输入的非疯狂程序都需要工作,如果它可以同时使用tty和文件,则没有理由不支持管道。该程序的作者可能是暂时性出血,尽管任何isatty()返回错误的内容将是可搜索的或可映射的文件……
Henning Makholm

9
错误代码14代表EFAULT。如果您声明的缓冲区无效,则会发生读取。我会跟踪该程序,但我怀疑它正在寻找文件的末尾以获取用于读取数据的缓冲区大小,从而严重处理了查找不起作用的事实,并试图分配负大小(不处理错误的malloc) 。传递缓冲区以读取给定缓冲区的哪些故障无效。
马修·伊夫

3
@xhienne不,它有一个cat预防剂。看来您无法使用它来合并两个文件,这是预期的用法。
jpmc26

Answers:


150

./binary < file

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

在:

./binary << EOF
test
EOF

根据外壳的不同,binarystdin可能是一个已删除的临时文件(AT&T ksh,zsh,bash ...),其中包含test\n该外壳放置在其中的内容,也可能是管道的读取端(dashyash;并且外壳test\n并行写入)在管道的另一端)。就您而言,如果使用bash,它将是一个临时文件。

在:

cat file | ./binary

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

当stdin是常规文件(临时文件)时,它是可搜索的。binary可能会开始或结束,倒带等。它也可以ioctl()s映射它,做一些像FIEMAP / FIBMAP的操作(如果使用<>代替<,可能会在其中截断/打孔等等)。

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

大多数时候,它是在缺少能力seek与管工作时,导致应用程序故障/投诉,但它可以是任何其他系统调用是在常规文件上有效,但不能在不同类型的文件(如mmap()ftruncate()fallocate()) 。在Linux上,当/dev/stdinfd 0在管道或常规文件上打开时,行为上也存在很大差异。

有很多命令只能处理可搜索的文件,但是在这种情况下,通常不是针对在其stdin上打开的文件。

$ 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。它不会从其stdin中读取zip文件。stdin主要用于用户交互。

如果您binary在终端仿真器中运行的交互式shell的提示下运行了自己的代码而没有重定向,则binarystdin将从其父级shell继承而来,它本身将从其父级终端仿真器继承而来,它将是一个pty设备以读写模式打开(类似/dev/pts/n)。

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

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

binary可能确定在其stdin(带有fstat())上打开的文件的类型,并且在既不是常规文件又不是tty设备的情况下都会遇到错误。

在不了解应用程序的情况下很难说出来。下运行它strace(或truss/ tusc您的系统上等效)可以帮助我们来看看什么是系统调用,如果任何失败在这里。


1 马修·伊夫Matthew Ife)在对您的问题的评论中提出的设想在这里听起来很合理。引用他:

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


14
非常有趣...这是我所听说的第一个可以搜索到样式的标准输入的./binary < file方法!
David Z

2
@DavidZ这是一个已open编辑的文件,其行为与任何已open编辑的文件相同。它恰好是从父进程继承而来的,但这并不少见。
hobbs

3
如果系统包含strace或类似工具,则可以使用它检查二进制文件在哪个系统调用上失败。
pabouk

2
“它也可以截断,映射它,在它上打孔等等。” 好吧 该文件以只读模式打开。该程序必须以写入模式打开它才能执行此操作。但是它无法在写入模式下打开它,因为没有直接执行此操作的接口,也没有任何接口可以找到与打开的文件相对应的“ the”目录条目(如果有两个这样的dentries或为零,该怎么办?) 。它必须统计文件,然后在文件系统中扫描具有相同inode编号的对象。那太慢了。
凯文(Kevin)

1
@StéphaneChazelas:哦,对open("/proc/self/fd/0", O_RDWR),即使在删除的文件上也可以使用。傻我:P。 在a.out运行且其stdin从重定向之前取消echo foo>foo; (sleep 0.5; ll -L /proc/self/fd/0; strace ./a.out; ll -L /proc/self/fd/0) < foo & sleep 0.1 && rm foo链接。foofoo
彼得·科德斯

46

这是一个简单的示例程序,用于说明StéphaneChazelaslseek(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

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


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)

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

管道(因为它们是64 KiB的缓冲区)(至少在Linux上是这样),且写入的4096字节或更少的字节保证是原子的,因此无法查找,即,您无法自由浏览它们-只能顺序读取。我曾经tail在python中实现命令。如果重定向,则可以在2毫秒内搜索到2900万行文本,但是如果cat通过pipe编辑,那么什么也做不了-因此必须依次读取所有内容。

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

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

注意:一些shell,例如破折号(Debian Almquist Shell,/bin/shUbuntu上默认),在内部here-doc使用管道实现了重定向,因此可能无法找到。要点保持不变-管道是顺序的,无法轻松导航,因此尝试这样做将导致错误。


Stephane的回答说,here-docs可以用管道来实现,并且一些常见的shell可以这样dash做。这个答案解释了使用bash观察到的行为,但是显然不能保证在其他shell上的行为。
彼得·科德斯

@PeterCordes绝对是这样,我刚刚dash在系统上进行了验证。我以前没有意识到这一点。感谢您指出
Sergiy Kolodyazhnyy

另一条评论:您将fstat()在stdin上使用它来检查它是否是管道。 stat采用路径名。但是实际上,尝试尝试lseek是确定fd在打开后是否可搜索的最明智的方法。
彼得·科德斯

5

主要区别在于错误处理。

在以下情况下报告错误

$ /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 !

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

$ 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

4
因此,您正在显示with的错误处理>是由外壳完成的,而使用管道的错误处理是由生成文本的命令完成的。好。但是在这个特定的问题中,OP使用的是现有文件,所以这不是问题,很明显,二进制文件会产生错误。
Sergiy Kolodyazhnyy

1
尽管答案大多不对,但该答案在一般情况下确实与该问答有关,并且基本上是正确的,因此我认为不应该被那些否决票接受。
斯特凡Chazelas

@Serg:将shell用作命令行时,这并不重要。但是在脚本中,错误的处理可能非常重要。
Vouze
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.