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 种情况下,文件的内容都提供给二进制的标准输入(以不同的方式):
据我所知,二进制文件不应该注意到这 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)的事实。传递缓冲区以读取给定缓冲区的哪些故障无效。
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使用管道实现重定向,因此可能无法查找。要点保持不变 - 管道是连续的,无法轻松导航,尝试这样做会导致错误。
主要区别在于错误处理。
以下情况报错
$ /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)