这个问题听起来有点愚蠢,但是我真的看不到重定向和管道之间的区别。

重定向用于重定向stdout / stdin / stderr,例如ls > log.txt。管道用于将命令的输出作为另一个命令的输入,例如ls | grep file.txt

但是为什么有两个运算符用于同一件事?

为什么不只编写ls > grep来传递输出,这也不只是一种重定向?我想念的是什么?

#1 楼

管道用于将输出传递到另一个程序或实用程序。

重定向用于将输出传递到文件或流。

示例:thing1 > thing2thing1 | thing2

thing1 > thing2


您的shell将运行名为thing1的程序

所有thing1输出将放置在名为thing2的文件中。 (注意-如果存在thing2,它将被覆盖)

如果要将thing1程序的输出传递给名为thing2的程序,则可以执行以下操作:

thing1 > temp_file && thing2 < temp_file

将运行名为thing1的程序

将输出保存到名为temp_file的文件中

>运行名为thing2的程序,假装键盘上的人键入了temp_file的内容作为输入。

但是,这很笨拙,因此他们将管道制作为一种更简单的方法。 thing1 | thing2的功能与thing1 > temp_file && thing2 < temp_file的功能相同,

编辑以提供更多要评论的细节:

如果>试图同时成为“传递给程序”和“写入文件” ,这可能会导致双向问题。

第一个示例:您正在尝试写入文件。已经存在一个您想要覆盖的文件。但是,该文件是可执行的。据推测,它将尝试通过传递输入来执行该文件。您必须执行类似将输出写入新文件名,然后重命名文件的操作。

第二个示例:如Florian Diesch所指出的,如果系统中其他地方有另一个命令具有相同的命令,该怎么办?名称(在执行路径中)。如果要在当前文件夹中创建一个具有该名称的文件,则会被卡住。

第三:如果您键入错误的命令,它不会警告您该命令不存在。现在,如果您输入ls | gerp log.txt,它将告诉您bash: gerp: command not found。如果>意味着两者,它只会为您创建一个新文件(然后警告它不知道如何处理log.txt)。

评论


谢谢。您提到了Thing1> temp_file && Thing2 运算符来执行此操作,例如事物1>事物2用于命令事物1和事物2?为什么需要额外的运算符| ?

–约翰·特里普伍德
2012年8月7日13:57

“获取输出并将其写入文件”与“获取输出并将其传递到其他程序”是不同的操作。我会将更多想法编辑成答案...

–大卫·奥尼尔(David Oneill)
2012年8月7日14:01

@JohnThreepwood它们具有不同的含义。例如,如果我想将某些内容重定向到名为less的文件怎么办?东西少和物>少是完全不同的,因为它们做不同的事情。您提出的建议会造成歧义。

– Darkhogg
2014年5月25日上午9:55

准确地说“ thing1> temp_file”仅仅是“ thing1 | tee temp_file”的语法糖吗?自从发现tee以来,我几乎从不使用重定向。

– Sridhar Sarnobat
2014年6月5日下午5:09

@ Sridhar-Sarnobat不,tee命令执行的操作有所不同。 tee将输出写入屏幕(stdout)和文件。重定向仅执行文件。

–大卫·奥尼尔(David Oneill)
2014年6月5日9:16

#2 楼

如果foo > bar的含义取决于是否存在一个名为bar的命令,该命令会使使用重定向变得更加困难并且更容易出错:每次我想重定向到文件时,我首先都必须检查是否有一个名为目的地的命令文件。

评论


仅当您在$ PATH env变量一部分的目录中写入bar时,这才是问题。如果您在/ bin之类的目录中,则可能是个问题。但是即使那样,bar仍必须设置可执行权限,以便shell不仅检查是否找到可执行bar,而且实际上可以执行它。而且,如果要考虑覆盖现有文件,则noclober shell选项应防止在重定向中覆盖现有文件。

– Sergiy Kolodyazhnyy
18年9月12日在19:20

#3 楼

摘自《 Unix和Linux系统管理手册》:

重定向
Shell将符号<,>和>>解释为将命令的输入或输出重新路由到文件或从文件重新路由的指令。 br />管道
使用|将一个命令的STDOUT连接到另一个命令的STDIN。符号,通常称为管道。

所以我的解释是:如果命令是命令,请使用管道。如果要输出到文件或从文件输出,请使用重定向。

#4 楼

这两个运算符之间有一个重要的区别:


ls > log.txt->此命令将输出发送到log.txt文件。
ls | grep file.txt->此命令将输出发送通过使用管道(|)将ls转换为grep命令,然后grep命令在上一个命令提供的输入中搜索file.txt。

如果必须执行使用第一种情况执行相同的任务,则将是:

ls > log.txt; grep 'file.txt' log.txt


因此,使用管道(带有|)将输出发送到其他命令,而重定向(使用>)将输出重定向到某个文件。

#5 楼

注意:答案反映了我对这些机制的最新了解,是在本网站和unix.stackexchange.com上的同行研究和阅读答案后积累的,并将随着时间的推移而更新。不要犹豫,提问题或提出意见方面的改进。我还建议您尝试使用strace命令查看syscalls如何在shell中工作。另外,请不要被内部或系统调用的概念所吓倒-您不必了解或能够使用它们来了解shell的工作方式,但是它们绝对有助于理解。

TL; DR



|管道与磁盘上的条目不相关,因此没有磁盘文件系统的索引节点号(但在pipefs虚拟文件系统中确实具有索引节点)在内核空间中),但是重定向通常涉及文件,这些文件确实具有磁盘条目,因此具有相应的inode。
管道不是lseek()'可用,因此命令无法读取某些数据然后回退,但是当您重定向时使用><通常是一个支持lseek()的对象的文件,因此命令可以随心所欲地导航。
重定向是对文件描述符的操作,可以是很多;管道只有两个文件描述符-一个用于左命令,一个用于右命令
在标准流上重定向,管道都被缓冲。
管道几乎总是涉及派生,因此涉及成对的进程;重定向-并非总是如此,尽管在两种情况下,结果文件描述符都是由子进程继承的。
管道始终连接文件描述符(一对),重定向-使用路径名或文件描述符。
管道是进程间通信方法,而重定向只是对打开的文件或类似文件的对象的操作
都在后台使用dup2() syscall来提供文件描述符的副本,这些副本发生在实际的数据流中。
重定向可以使用exec内置命令“全局”应用(请参阅this和this),因此,如果执行exec > output.txt,则从此以后每个命令都将写入output.txt|管道仅适用于当前命令(这意味着简单命令或子外壳,例如seq 5 | (head -n1; head -n2)或复合命令(但也请注意,对于此类复合命令,read()消耗的字节数量将影响到发送端剩余的数据量)
在文件上完成重定向后,诸如echo "TEST" > fileecho "TEST" >> file之类的东西都在该文件上使用open() syscall(另请参见)并从文件获取文件描述符以将其传递到dup2()管道|仅使用pipe()dup2() syscall。重定向涉及文件和目录权限;匿名管道通常不涉及权限(即,是否可以创建管道),而不能涉及命名管道(使用mkfifo)确实包含典型的文件许可权和读写执行位。
就正在执行的命令而言,管道和重定向只不过是文件描述符-类似文件对象,可能会盲目地写这些对象,或者在内部对其进行操作(可能会产生意外的行为;例如,如果apt知道存在重定向,它甚至甚至不会写到stdout。)

简介

为了理解这两种机制有何不同,有必要了解它们的区别。本质属性,两者的背后历史以及它们在C编程语言中的根源。实际上,以及dup2()一样,了解什么是文件描述符以及pipe()lseek()系统调用的工作方式至关重要。 Shell是使这些机制对用户抽象的一种方式,但是比抽象更深入地了解有助于理解Shell行为的真实本质。

重定向和管道的起源

根据丹尼斯·里奇(Dennis Ritche)的文章《先知岩画》(Prophetic Petroglyphs),管道是在1964年由Malcolm Douglas McIlroy编写的内部备忘录中使用的,当时他们正在使用Multics操作系统。 Quote:


简而言之,我最担心的问题:


我们应该有一些连接程序的方法,例如花园软管-拧入当有必要以另一种方式处理数据时,另一个分段。这也是IO的方式。


很明显,当时程序能够写入磁盘,但是如果输出很大,效率很低。在Unix Pipeline视频中引用Brian Kernighan的解释:


首先,您不必编写一个大型的程序-您已经拥有了一些较小的程序,这些程序可能已经完成了部分工作。工作...另一个是,如果将数据存储在文件中,则可能处理的数据量可能不合适...因为请记住,如果您拥有这些东西上的磁盘,那我们就回到了过去很幸运,只有一两个兆字节的数据...因此管道不必实例化整个输出。


因此,概念上的区别显而易见:管道是使程序与之通信的一种机制另一个。重定向-是在基本级别上写入文件的方式。在这两种情况下,shell都使这两件事变得容易,但是在引擎盖之下,还有很多事情要做。

更深入: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.txtfile2.txt,当执行cp file1.txt file2.txt时,您将拥有两个相同的文件,但是您可以独立地操作它们?这有点一样。通常,您可以看到在运行之前,bash会执行dup(1,10)来制作一个副本文件描述符#1,即stdout(该副本将为fd#10),以便稍后进行恢复。重要的是要注意,当您考虑内置命令(它们本身是外壳程序的一部分,并且在/bin或其他位置没有文件)或非交互式外壳程序中的简单命令时,外壳程序不会创建子进程。

然后我们有了[n]>&[m][n]&<[m]之类的东西。这是在复制文件描述符,只是在外壳语法中才使用与dup2()相同的机制,方便用户使用。

关于重定向的重要注意事项之一是它们的顺序不是固定的,但对于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脚本中的实际使用可以是多种多样的:


将输出保存到变量仅写入stderr的程序
将stderr和stdout交换
将偶数输入线与奇数输入线分离

以及许多其他输入。

使用pipe()进行水暖和dup2()


那么如何创建管道?通过pipe()系统调用,它将使用名为pipefd的数组(又名列表)输入两个类型为int(整数)的项。这两个整数是文件描述符。 pipefd[0]将成为管道的读取端,而pipefd[1]将成为写入端。因此在df | grep 'foo'中,grep将获得pipefd[0]的副本,而df将获得pipefd[1]的副本。但是如何?当然,具有神奇的系统调用功能。对于我们示例中的dup2(),假设df具有#4,那么外壳程序将使它成为孩子,执行pipefd[1](还记得我的dup2(4,1)示例吗?),然后执行cp以实际运行execve()。自然地,df将继承文件描述符#1,但不会意识到它不再指向终端,而是指向fd#4,实际上是管道的写入端。自然,除了不同数量的文件描述符外,df也会发生相同的情况。

现在,一个有趣的问题:我们是否也可以制作重定向fd#2的管道,而不仅仅是fd#1?是的,事实上,这就是grep 'foo'在bash中所做的。 POSIX标准要求外壳命令语言为此目的支持|&语法,但df 2>&1 | grep 'foo'也支持bash

需要注意的重要一点是,管道始终处理文件描述符。存在一个|&或命名管道,它在磁盘上有一个文件名,让我们将其用作文件,但其行为类似于管道。但是FIFO类型的管道被称为匿名管道-它们没有文件名,因为它们实际上只是两个连接在一起的对象。我们不处理文件的事实也具有重要意义:管道不是|可用。内存中或磁盘上的文件是静态的-程序可以使用lseek() syscall跳转到字节120,然后返回字节10,然后一直转发到末尾。管道不是静态的-它们是顺序的,因此您无法使用lseek()倒回从管道中获取的数据。这使某些程序知道它们是从文件还是从管道读取的,因此它们可以进行必要的调整以提高性能;换句话说,一个lseek()可以检测到我是否执行progcat file.txt | prog。真正的工作示例是tail。

管道的另外两个非常有趣的属性是它们具有缓冲区,在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上创建双向管道。

另请参见:


如何生成任何shell命令的输出

如何在C语言中使用prog < input.txt syscall和pipe()创建管道的维基百科示例。
为什么使用管道代替输入重定向
&>和2>&1
之间的区别是什么,诸如dup2()<<的重定向在<<<bash中作为匿名(未链接的)临时文件实现,而ksh使用匿名管道; < <()/bin/dash使用管道。请参见bash中<<,<<<和<<之间有什么区别?



#6 楼

两者之间在语法上有很大区别:


重定向是程序的参数
管道将两个命令分开

您可以想到重定向如下:cat [<infile] [>outfile]。这意味着顺序无关紧要:cat <infile >outfilecat >outfile <infile相同。您甚至可以将重定向与其他参数混合使用:cat >outfile <infile -bcat <infile -b >outfile都很好。您也可以将多个输入或输出串在一起(输入将被顺序读取,所有输出将被写入每个输出文件):cat >outfile1 >outfile2 <infile1 <infile2。重定向的目标或源可以是文件名或流的名称(例如&1,至少在bash中是这样)。

但是管道完全将一个命令与另一个命令分开,您不能将它们与参数混合:

[command1] | [command2]


管道将从command1写入标准输出的所有内容发送到command2的标准输入。

您还可以结合管道和重定向。例如:

cat <infile >outfile | cat <infile2 >outfile2


前一个cat将从infile中读取行,然后将每行同时写入outfile并将其发送到第二个cat

在第二个cat中,标准输入首先从管道读取(infile的内容),然后从infile2读取,并将每一行写入outfile2。运行此文件后,outfile将是infile的副本,并且outfile2将包含infile后跟infile2。

最后,实际上您实际上使用“ here string”重定向(与bash系列相同)来执行与示例相似的操作)和反引号:

grep blah <<<`ls`


将得到与

ls | grep blah


相同的结果,但是我认为重定向版本将首先将ls的所有输出读入缓冲区(在内存中),然后一次将该缓冲区送入grep一行,而管道版本将在出现时从ls提取每一行,并将该行传递给grep 。

评论


Nitpick:如果将一个fd重定向到另一个,则顺序在重定向中很重要:echo yes 1>&2 2> / tmp / blah; wc -l / tmp / blah;回声是2> / tmp / blah 1>&2; wc -l / tmp / blah此外,重定向到文件将仅使用最后一个重定向。 echo yes> / tmp / blah> / tmp / blah2将只写入/ tmp / blah2。

–大师
2014年8月23日在22:49



重定向实际上不是程序的参数。该程序将不知道或不在乎其输出(或输入来自何处)。这只是在运行程序之前告诉bash如何安排事情的方式。

–阿洛瓦·马哈德(Alois Mahdal)
15年4月23日在18:33

#7 楼

除了其他答案外,语义上也有细微的差别-例如管道比重定向更容易关闭:

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避免关闭管道,则子进程中仍然可以使用它。

因此,“流”就是我们通过(stdin等)分流数据的东西,并且在这两种情况下都是相同的,但是管道连接来自两个流程的流,其中重定向将流程和文件之间的流连接在一起,因此您可以看到两者的异同。

PS如果您像我一样对这些示例感到好奇和/或惊讶,则可以进一步使用read深入了解进程如何解决,例如:

(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))


有时第一个过程在打印trap之前关闭,有时是在之后。

我还发现使用1来关闭重定向的流以逼近管道的行为很有趣(尽管有错误) ):

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`


评论


“当第一个对head的调用结束时,它会关闭管道”这实际上是不准确的,原因有两个。一个(head -n1; head -n1)是带有两个命令的子shell,每个命令都将管道的读取端作为描述符0继承,因此subshel​​l AND每个命令都打开了该文件描述符。第二个原因,您可以看到strace -f bash -c'seq 5 | (head -n1; head -n1)”。因此,第一个头部仅关闭其文件描述符的副本

– Sergiy Kolodyazhnyy
18-09-3在5:20



第三个示例也是不准确的,因为read仅占用第一行(即1和newline的一个字节)。 seq总共发送了10个字节(5个数字和5个换行符)。因此,管道缓冲区中还剩下8个字节,这就是第二个头起作用的原因-管道缓冲区中仍然有可用数据。顺便说一句,仅当读取了0个字节时head才退出,有点像head / dev / null

– Sergiy Kolodyazhnyy
18年3月3日在5:39

感谢您的澄清。我在序列5中正确理解吗? (head -n1; head -n1)第一次调用清空了管道,因此它仍然处于打开状态,但是第二次调用head没有数据?那么,管道和重定向之间的行为差​​异是因为head将所有数据从管道中拉出,但是仅从文件句柄中拉出了两行?

–朱利安·德·巴尔(Julian de Bhal)
18年9月12日在5:10

没错这是我在第一条评论中给出的strace命令可以看到的东西。通过重定向,tmp文件位于磁盘上,这使得它易于查找(因为它们使用lseek()syscall-命令可以根据需要从文件的第一个字节跳到最后一个字节。但是管道是顺序的而不是可查找的。做它的工作是首先读取所有内容,或者如果文件很大,则通过mmap()调用将其中一些映射到RAM。我曾经在Python中做自己的尾巴,然后遇到了完全相同的问题。

– Sergiy Kolodyazhnyy
18/09/12在5:21

同样重要的是要记住,首先将管道的读取端(文件描述符)提供给子外壳程序(...),然后子外壳程序会将其自己的stdin复制到(...)中的每个命令。因此,从技术上讲,它们是从同一对象读取的。首先,负责人认为这是从自己的标准输入中读取的。第二个负责人认为它有自己的标准输入。但实际上,它们的fd#1(stdin)只是同一fd的副本,该副本在管道的末尾读取。另外,我已经发布了答案,因此也许可以帮助您弄清楚事情。

– Sergiy Kolodyazhnyy
18年9月12日在9:56

#8 楼

我今天在C语言中遇到了一个问题。本质上,即使将Pipe发送到stdin,也具有不同的语义来进行重定向。确实,我认为考虑到这些差异,管道应该放在stdin之外的其他地方,以便可以用不同的方式来处理stdin并将其称为stdpipe(以进行任意微分)。

考虑一下。当通过管道将一个程序输出输出到另一个fstat时,尽管st_size显示存在文件,但似乎仍返回零作为ls -lha /proc/{PID}/fd。重定向文件时不是这种情况(至少在debian wheezystretchjessie vanilla和ubuntu 14.0416.04 vanilla上是这样的。

如果您使用重定向将cat /proc/{PID}/fd/0重新定位,则可以重复阅读任意多次,如果使用管道执行此操作,您会注意到第二次连续运行任务,将不会获得相同的输出。