我有一个二进制文件(无法修改),可以执行以下操作:

./binary < file


我也可以执行以下操作:

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


,但

cat file | ./binary


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


bash读取文件并将其提供给二进制的stdin

bash从stdin读取行(直到EOF)并将其提供给二进制的stdin

cat读取文件行并将其放到stdout,bash将它们重定向到二进制文件的stdin


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

BTW:二进制给出的错误为:


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


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

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

我试图用ruby脚本控制的二进制文件(不使用
临时文件)是Automic(UC4)中的callapi的一部分。

评论

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

它是什么操作系统(如果要成为errno,我们可以知道14是什么)吗?

即使程序有可能以这种方式做出反应,但这样做确实是个小虫子。当stdin是tty时,所有期望从stdin进行任何输入的非疯狂程序都需要工作,并且如果它可以同时在tty和文件中工作,则没有理由不支持管道。该程序的作者可能是暂时性出血,尽管isatty()返回为false的任何内容都将是可查找或可映射的文件...

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

@xhienne不,它里面有防猫剂。看来您无法使用它来合并两个文件,这是预期用途。

#1 楼


./binary < file

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

在外壳上,binary的stdin将是已删除的临时文件(AT&T ksh,zsh,bash ...),其中包含外壳放置在其中的test\n或管道的读取端(dashyash;以及外壳)在管道的另一端并行写入test\n)。在您的情况下,如果您使用的是bash,那将是一个临时文件。
在:
cat file | ./binary

根据外壳,binary的stdin可以是管道或套接字对的一端(已关闭写入方向(ksh93),并且cat在另一端写入file的内容)。
当stdin是常规文件(临时文件)时,是可寻求的。 binary可能会开始或结束,倒带等。它也可以映射它,执行一些Q4312079q,例如FIEMAP / FIBMAP(如果使用ioctl()s而不是<>,则可能会在其中截断/打孔等)。
另一方面,管道和套接字对是一种进程间通信方式,除<数据外,binary不能做很多(尽管也有一些操作,例如某些特定于管道的read,可以对它们进行操作而不是对
在大多数情况下,ioctl()缺少的功能会导致应用程序在使用管道时失败/抱怨,但这可能是对常规文件有效但对其他文件无效的其他任何系统调用不同类型的文件(例如seekmmap()ftruncate())。在Linux上,当fd 0在管道或常规文件上时打开fallocate()时,行为上也存在很大差异。
有很多命令只能处理可搜索的文件,但是在这种情况下,通常不是针对在其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.

/dev/stdin需要读取存储在末尾的索引文件,然后在文件中搜索以读取存档成员。但是在这里,文件(在第一种情况下为常规,在第二种情况下为管道)作为unzip的路径参数给出,并且unzip自己打开文件(通常在非0的fd上),而不继承调用方已经打开的fd。它不会从其stdin中读取zip文件。 stdin主要用于用户交互。
如果在终端仿真器中运行交互式shell的提示下运行unzip而不进行重定向,则binary的stdin将从其调用方继承,shell本身将从其调用者终端仿真器继承而来,它将是一个以读写模式打开的pty设备(类似binary)。
这些设备也不可搜索。因此,如果/dev/pts/n在从终端获取输入时工作正常,则可能不是问题所在。
如果14表示errno(系统调用失败而设置的错误代码),则在大多数系统上,那将是binary(地址错误)。如果要求EFAULT系统调用读入不可写的内存地址,它将失败并显示该错误。这将与fd从点读取数据到管道还是常规文件无关,并且通常会指示bug1。
read()可能确定在其stdin上打开的文件类型(使用binary)并遇到既不是常规文件也不是tty设备的错误。
不了解应用程序就很难告诉。在fstat()(或系统上等效的strace / truss)下运行它可以帮助我们查看什么是系统调用(如果此处失败)。

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

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


评论


非常有趣...这是我第一次听说可以搜索./binary
– David Z
17年1月16日在19:45

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

–霍布斯
17年1月17日,0:32

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

– pabouk
17年1月17日在8:37



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

–凯文
17年1月18日在3:48

@StéphaneChazelas:哦,对了,即使在删除的文件上,open(“ / proc / self / fd / 0”,O_RDWR)也可以。傻我:P。 echo foo> foo; (sleep 0.5; ll -L / proc / self / fd / 0; strace ./a.out; ll -L / proc / self / fd / 0)
– Peter Cordes
17年1月18日在23:37

#2 楼

这是一个简单的示例程序,用于说明StéphaneChazelas在其输入上使用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)


所以重定向可以二进制文件基本上被视为文件,它们可以轻松地在文件中导航,或跳转到文件的任何字节。

Pipes,因为它们是64 KiB的缓冲区(至少在Linux上是这样),因此保证不超过4096字节的写操作是原子的,因此无法查找它们,即,您无法自由导航它们-只能顺序读取。我曾经在python中实现seek()命令。如果重定向,则可以在2毫秒内搜索到2900万行文本,但是如果通过pipe进行tail,那么就无能为力了,因此必须按顺序读取。

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

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

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

评论


史蒂芬(Stephane)的回答说,这里文档可以用管道来实现,而一些常见的shell(例如dash)可以这样做。这个答案解释了使用bash观察到的行为,但是显然不能保证在其他shell上的行为。

– Peter Cordes
17年1月18日在22:55



@PeterCordes绝对是这样,我只是在系统上用破折号进行了验证。我以前没有意识到这一点。感谢您指出

– Sergiy Kolodyazhnyy
17年1月18日在23:12

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

– Peter Cordes
17年1月18日在23:26

#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中,属于管道的函数在子外壳程序中执行(如果启用了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


评论


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

– Sergiy Kolodyazhnyy
17年1月17日在1:21

尽管答案大多不对,但该答案在一般情况下确实与该问答有关,并且基本上是正确的,因此我认为不值得这些否决。

–StéphaneChazelas
17年1月17日在17:22

@Serg:将shell用作命令行时,这并不重要。但是在脚本中,错误的处理可能非常重要。

– Vouze
17年1月25日在13:36