Answers:
管道用于将输出传递到另一个程序或实用程序。
重定向用于将输出传递到文件或流。
示例:thing1 > thing2vsthing1 | thing2
thing1 > thing2
thing1thing1输出的所有内容都将放置在名为的文件中thing2。(注意-如果thing2存在,它将被覆盖)如果要将输出从程序传递thing1到名为的程序thing2,则可以执行以下操作:
thing1 > temp_file && thing2 < temp_file
这将
thing1temp_filething2,假装键盘上的人键入的内容temp_file作为输入。但是,这很笨拙,因此他们将管道制作为一种更简单的方法。thing1 | thing2做与thing1 > temp_file && thing2 < temp_file
编辑以提供更多详细信息以在评论中提问:
如果>试图同时“传递给程序”和“写入文件”,则可能会导致双向问题。
第一个示例:您正在尝试写入文件。已经存在一个您想要覆盖的文件名。但是,该文件是可执行的。据推测,它将尝试通过传递输入来执行该文件。您必须执行类似将输出写入新文件名,然后重命名文件的操作。
第二个示例:正如Florian Diesch所指出的,如果系统中其他地方有另一个同名命令(在执行路径中),该怎么办。如果您打算在当前文件夹中创建一个具有该名称的文件,那么您将陷入困境。
第三:如果您键入错误的命令,它不会警告您该命令不存在。现在,如果您键入ls | gerp log.txt,它将告诉您bash: gerp: command not found。如果>两者兼有,它只会为您创建一个新文件(然后警告它不知道如何处理log.txt)。
less怎么办?thing | less并且 thing > less完全不同,因为他们做的事情不同。您提出的建议会造成歧义。
tee命令执行其他操作。 tee将输出写入屏幕(stdout)和文件。重定向仅执行文件。
如果的含义foo > bar取决于是否有一个命名的命令bar,它将使使用重定向变得更加困难,并且更容易出错:每次我想重定向到文件时,我首先必须检查是否有一个名为目标文件的命令。
bar属于$PATHenv变量的目录中写入时,这才是问题。如果您在/ bin之类的目录中,则可能是个问题。但是即使这样,bar也必须设置可执行文件许可权,以便外壳程序不仅检查可执行文件,bar还可以执行它。而且,如果要考虑覆盖现有文件,则noclobershell选项应防止在重定向中覆盖现有文件。
两者之间在语法上有很大的不同:
您可以想到这样的重定向:cat [<infile] [>outfile]。这表示顺序无关紧要:cat <infile >outfile与相同cat >outfile <infile。您甚至可以混合使用其他参数重定向起来:cat >outfile <infile -b和cat <infile -b >outfile都是完全正常的。也可以串起来多于一个的输入或输出(输入将被顺序地读取并输出所有将被写入到每个输出文件): cat >outfile1 >outfile2 <infile1 <infile2。重定向的目标或源可以是文件名或流的名称(例如&1,至少在bash中)。
但是管道完全将一个命令与另一个命令分开,您不能将它们与参数混在一起:
[command1] | [command2]
管道将从命令1写入标准输出的所有内容发送到命令2的标准输入。
您还可以结合管道和重定向。例如:
cat <infile >outfile | cat <infile2 >outfile2
第一cat行将从infile中读取行,然后将每一行同时写入outfile并将其发送至第二行cat。
在第二个中cat,标准输入首先从管道(infile的内容)读取,然后从infile2读取,并将每一行写入outfile2。运行此命令后,outfile将是infile的副本,并且outfile2将包含infile和infile2。
最后,您实际上使用“ here字符串”重定向(仅bash系列)和反引号执行与示例非常相似的操作:
grep blah <<<`ls`
将产生与以下相同的结果
ls | grep blah
但是我认为重定向版本将首先将ls的所有输出读入一个缓冲区(在内存中),然后一次将该缓冲区送入grep一行,而管道版本会在出现时从ls提取每一行,并将该行传递给grep。
echo yes 1>&2 2>/tmp/blah; wc -l /tmp/blah; echo yes 2>/tmp/blah 1>&2; wc -l /tmp/blah,则顺序在重定向中很重要:此外,重定向到文件将仅使用最后一个重定向。echo yes >/tmp/blah >/tmp/blah2只会写入/tmp/blah2。
注意:答案反映了我对这些机制的最新了解,是在研究和阅读本站点和unix.stackexchange.com上的同行的答案时积累的,并将随着时间的推移而更新。不要犹豫,提出问题或提出改进意见。我还建议您尝试查看syscall在shell中如何通过strace命令工作。另外,请不要被内部或系统调用的概念所吓倒-您不必了解或能够使用它们来了解shell的工作方式,但是它们绝对有助于理解。
|管道不与磁盘上的条目相关联,因此没有磁盘文件系统的索引节点号(但在内核空间的pipefs虚拟文件系统中具有索引节点),但是重定向通常涉及文件,该文件确实具有磁盘条目并因此具有对应的索引节点。lseek(),因此命令无法读取某些数据,然后回退,但是当您使用重定向>或<通常是带有lseek()对象的文件时,命令可以随心所欲地导航。dup2()在引擎盖下使用syscall来提供文件描述符的副本,在此发生实际的数据流。exec内置命令“全局”应用(请参阅this和this),因此,如果执行此操作,则此后exec > output.txt将写入所有命令output.txt。|管道仅适用于当前命令(这意味着简单命令或子shell之类的seq 5 | (head -n1; head -n2)命令或复合命令。当重定向的文件来完成,像echo "TEST" > file并echo "TEST" >> file都使用open()了该文件系统调用(见),并从它那里得到的文件描述符通过它来dup2()。管道|仅使用pipe()和dup2()syscall。
就执行的命令而言,管道和重定向只不过是文件描述符-类文件对象,它们可能会盲目地写入其中,或在内部对其进行操作(这可能会产生意外的行为;apt例如,往往甚至不会写入stdout如果它知道有重定向)。
为了了解这两种机制的不同之处,有必要了解它们的基本属性,两者的历史以及它们在C编程语言中的根源。实际上,了解文件描述符是什么,以及如何dup2()以及pipe()系统调用是必不可少的lseek()。Shell的意思是使这些机制对用户抽象的一种方式,但是比抽象更深入地挖掘有助于理解Shell行为的真实本质。
根据丹尼斯·里奇(Dennis Ritche)的文章《先知岩画》(Prophetic Petroglyphs),管道起源于1964年马尔科姆·道格拉斯·麦克罗伊(Malcolm Douglas McIlroy)编写的内部备忘录,当时他们正在研究Multics操作系统。引用:
简单地说:
- 我们应该有一些连接程序的方法,例如花园水管-当有必要以另一种方式处理数据时,请拧入另一段。这也是IO的方式。
显而易见的是,当时程序可以写入磁盘,但是如果输出很大,效率很低。在Unix Pipeline视频中引用Brian Kernighan的解释:
首先,您不必编写一个大型的大型程序-您已有的小型程序可能已经完成了部分工作...另一个是,如果处理的数据量可能不适合,则可能您将其存储在文件中...因为请记住,我们回到了磁盘上拥有这些东西的日子,如果幸运的话,我们拥有一到两个兆字节的数据...因此管道永远不必实例化整个输出。
因此,概念上的区别是显而易见的:管道是使程序相互通信的一种机制。重定向-是在基本级别上写入文件的方式。在这两种情况下,shell都使这两件事变得容易,但是在引擎盖下却有很多事情要做。
我们从文件描述符的概念开始。文件描述符基本上描述了一个打开的文件(无论是磁盘上的文件,内存中的文件还是匿名文件),均由整数表示。两个标准数据流 (stdin,stdout,stderr)分别是文件描述符0、1、2。他们来自哪里 ?好了,在shell命令中,文件描述符是从其父shell继承的。通常对于所有进程都是如此-子进程继承了父进程的文件描述符。对于守护程序,通常会关闭所有继承的文件描述符和/或重定向到其他位置。
返回重定向。到底是什么 它是一种机制,告诉外壳程序为命令准备文件描述符(因为重定向是在命令运行之前由外壳程序完成的),然后将其指向用户建议的位置。输出重定向的标准定义是
[n]>word
这[n]有文件描述符数。当您执行echo "Something" > /dev/null此操作时,数字1就会隐含在其中,而echo 2> /dev/null。
在后台,这是通过dup2()系统调用复制文件描述符来完成的。让我们来df > /dev/null。外壳程序将在df运行时创建一个子进程,但在此之前它将/dev/null以文件描述符#3 打开并被dup2(3,1)发布,这将复制文件描述符3,而副本将为1。您知道如何拥有两个文件file1.txt,file2.txt,当您cp file1.txt file2.txt拥有两个相同的文件时,您可以独立地操作它们吗?这有点一样。通常,您可以看到在运行之前,它们bash会dup(1,10)制作一个副本文件描述符#1,该描述符为stdout(该副本将为fd#10),以便稍后进行恢复。重要的是要注意,当您考虑内置命令时(它们是Shell本身的一部分,并且在/bin其他地方没有文件)或非交互式shell中的简单命令,该Shell不会创建子进程。
然后我们有类似[n]>&[m]和的事情[n]&<[m]。这是在复制文件描述符,该dup2()语法与shell语法中的机制相同,现在用户可以方便地使用它。
关于重定向的重要注意事项之一是它们的顺序不是固定的,但是对于shell解释用户想要的内容很重要。比较以下内容:
# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/ 3>&2 2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd
# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/ 2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
这些在shell脚本中的实际使用可以是多种多样的:
还有很多其他
pipe()和dup2()那么如何创建管道?通过pipe()syscall,它将把称为pipefd两个int(整数)类型的项的数组(也称为列表)作为输入。这两个整数是文件描述符。在pipefd[0]将管道的读端,并pipefd[1]会写入结束。因此df | grep 'foo',grep将获得的副本,pipefd[0]并 df获得的副本pipefd[1]。但是如何?当然,有了dup2()syscall 的魔力。对于df在我们的例子中,假设pipefd[1]有#4,所以外壳会使孩子,做dup2(4,1)(记得我的 cp例子吗?),然后做execve()实际运行df。自然,df将继承文件描述符#1,但不会意识到它不再指向终端,而是实际上指向fd#4(实际上是管道的写入端)。自然地,grep 'foo'除了文件描述符数量不同之外,其他事情都会发生。
现在,一个有趣的问题:我们是否也可以制作重定向fd#2的管道,而不仅仅是fd#1?是的,事实上这就是|&bash的功能。POSIX标准要求shell命令语言支持df 2>&1 | grep 'foo'的语法为目的,但bash确实|&也是如此。
需要注意的重要一点是,管道始终处理文件描述符。存在FIFO或命名为pipe,它在磁盘上有一个文件名,让我们将其用作文件,但其行为类似于管道。但是|管道的类型是所谓的匿名管道-它们没有文件名,因为它们实际上只是连接在一起的两个对象。我们不处理文件这一事实也产生了重要的含义:管道不可用lseek()。内存中或磁盘上的文件都是静态的-程序可以使用lseek()syscall跳转到字节120,然后返回字节10,然后一直转发到末尾。管道不是静态的-它们是顺序的,因此您无法回退使用它们从管道中获得的数据lseek()。这就是使某些程序知道它们是从文件还是从管道读取的,因此它们可以进行必要的调整以提高性能。换句话说,a prog可以检测到我是cat file.txt | prog还是prog < input.txt。实际的例子是尾巴。
管道的另外两个非常有趣的属性是它们有一个缓冲区,在Linux上是4096字节,实际上它们有一个Linux源代码中定义的文件系统!它们不仅仅是传递数据的对象,它们本身就是数据结构!实际上,由于存在同时管理管道和FIFO的pipefs文件系统,因此管道在其各自的文件系统上都有一个索引 节点号:
# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/ | cat
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
在Linux上,管道是单向的,就像重定向一样。在某些类似Unix的实现中-有双向管道。尽管Shell脚本具有魔力,但您也可以在Linux上建立双向管道。
pipe()syscall和C在C中创建管道dup2()。<<,<<<被实现为在匿名(未链接)临时文件bash和ksh,而< <()使用匿名管道; /bin/dash使用管道<<。请参见bash中<<,<<和<<之间有什么区别?除了其他答案之外,语义上也存在细微差别-例如,管道比重定向更容易关闭:
seq 5 | (head -n1; head -n1) # just 1
seq 5 > tmp5; (head -n1; head -n1) < tmp5 # 1 and 2
seq 5 | (read LINE; echo $LINE; head -n1) # 1 and 2
在第一个示例中,当第一个调用head结束时,它将关闭管道并seq终止,因此第二个没有可用的输入head。
在第二个示例中,head占用了第一行,但是当它关闭自己的stdin 管道时,该文件将保持打开状态以供下一次调用使用。
第三个示例显示,如果我们read避免关闭管道,则它在子流程中仍然可用。
因此,“流”是我们通过其分流数据的东西(stdin等),并且在两种情况下都是相同的,但是管道连接来自两个流程的流,其中重定向将流程和文件之间的流连接起来,因此可以看到相同点和不同点的来源。
PS:如果您像我一样对这些示例感到好奇和/或惊讶,则可以深入研究trap这些过程如何解决,例如:
(trap 'echo seq EXITed >&2' EXIT; seq 5) | (trap 'echo all done' EXIT; (trap 'echo first head exited' EXIT; head -n1)
echo '.'
(trap 'echo second head exited' EXIT; head -n1))
有时第一个过程在1打印之前关闭,有时在打印之后关闭。
我还发现使用一种有趣的方法exec <&-来关闭重定向中的流以近似管道的行为(尽管有错误):
seq 5 > tmp5
(trap 'echo all done' EXIT
(trap 'echo first head exited' EXIT; head -n1)
echo '.'
exec <&-
(trap 'echo second head exited' EXIT; head -n1)) < tmp5`
read仅消耗第一行(1和newline 是一个字节)。seq共发送10个字节(5个数字和5个换行符)。因此,管道缓冲区中还剩下8个字节,这就是第二个head起作用的原因-管道缓冲区中仍然有可用数据。顺便说一句,只有在读到0字节时头部才退出,有点像head /dev/null
seq 5 | (head -n1; head -n1)第一个调用中清空了管道,因此它仍然处于打开状态,但是第二个调用中没有数据head?那么,管道和重定向之间的行为差异是因为head将所有数据从管道中拉出,但是仅从文件句柄中拉出了两行?
strace我在第一条评论中给出的命令所能看到的。通过重定向,tmp文件位于磁盘上,这使得它易于查找(因为它们使用lseek()syscall -命令可以根据需要从文件的首字节跳到最后一个字节。但是管道是顺序的而不是可查找的。因此,head执行此操作的唯一方法工作是第一次读到的一切,或者,如果文件是大-地图有些是通过到RAM mmap()的呼叫我曾经做过我自己。tail在Python,跑进完全相同的问题。
(...),并且subshell会将其自己的stdin复制到其中的每个命令(...)。因此,从技术上讲,它们是从同一对象读取的。首先head 认为它是从自己的标准输入读取的。第二head认为它有自己的标准输入。但实际上,它们的fd#1(stdin)只是同一fd的副本,该副本在管道的末尾读取。另外,我已经发布了答案,因此也许可以帮助您弄清楚事情。
我今天在C语言中遇到了一个问题。本质上,即使将Pipe发送到,其重定向的语义也不同stdin。确实,我认为给定这些差异,管道应该走到以外的地方stdin,以便以不同的方式来处理stdin它stdpipe(让它成为任意的差异)。
考虑一下。当管道显示一个程序输出到另一个程序时fstat,st_size尽管ls -lha /proc/{PID}/fd显示有文件,但似乎返回零。当重定向文件不是这种情况下(至少在Debian wheezy,stretch和jessie香草和Ubuntu 14.04,16.04香草。
如果您cat /proc/{PID}/fd/0有重定向,则可以重复阅读多次。如果使用管道执行此操作,则会注意到第二次连续运行任务时,不会获得相同的输出。
thing1 > temp_file && thing2 < temp_file可以更轻松地使用管道。但是,为什么不重新使用>操作员来执行此操作,例如thing1 > thing2用于命令thing1和thing2?为什么需要额外的运算符|?