“重定向”和“管道”有什么区别?

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

重定向用于重定向stdout / stdin / stderr,例如ls > log.txt

管道用于将命令的输出作为另一个命令的输入,例如ls | grep file.txt ls | grep file.txt

但为什么有两个运营商同样的事情?

为什么不写ls > grep来传递输出,这不仅仅是一种重定向吗? 我错过了什么?

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

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

示例: thing1 > thing2 vs thing1 | thing2 thing1 | thing2

thing1 > thing2

  1. 你的shell将运行名为thing1的程序
  2. thing1输出的所有内容都将放在名为thing2的文件中。 (注意 – 如果thing2存在,它将被覆盖)

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

thing1 > temp_file && thing2 < temp_file

这将

  1. 运行程序名为thing1
  2. 将输出保存到名为temp_file的文件中
  3. 运行名为thing2程序,假装键盘上的人输入了thing2的内容作为输入。

然而,这很笨重,因此他们将管道作为一种更简单的方式来做到这一点。 thing1 | thing2 thing1 | thing2thing1 > temp_file && thing2 < temp_file thing1 | thing2做同样的事情

编辑提供更多详细信息以在评论中提问:

如果>试图同时“传递给程序”和“写入文件”,它可能会导致两个方向的问题。

第一个例子:您正在尝试写入文件。 已存在您要覆盖的具有该名称的文件。 但是,该文件是可执行的。 据推测,它会尝试执行此文件,传递输入。 您必须执行诸如将输出写入新文件名,然后重命名该文件之类的操作。

第二个例子:正如Florian Diesch指出的那样,如果系统中其他地方的另一个命令具有相同的名称(即在执行路径中),该怎么办? 如果您打算在当前文件夹中创建具有该名称的文件,则会遇到困难。

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

如果foo > bar的含义取决于是否有一个名为bar的命令会使重定向变得更加困难且更容易出错:每次我想重定向到一个文件时我首先要检查是否有一个名为like的命令我的目标文件。

两个运营商之间存在重大差异:

  1. ls > log.txt – >此命令将输出发送到log.txt文件。

  2. ls | grep file.txt ls | grep file.txt – >此命令通过使用管道( | )将ls的输出发送到grep命令,grep命令在上一个命令提供给它的输入中搜索file.txt。

如果您必须使用第一个场景执行相同的任务,那么它将是:

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

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

来自Unix和Linux系统管理手册:

重定向

shell将符号<,>和>>解释为将命令的输入或输出重新路由到文件或从文件重新路由的指令

管道

要将一个命令的STDOUT连接到另一个命令的STDIN,请使用| 符号,俗称管道。

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

这两者之间存在很大的语法差异:

  1. 重定向是程序的参数
  2. 管道分隔两个命令

你可以想到像这样的重定向: cat [outfile] 。 这意味着顺序无关紧要: cat outfilecat >outfile 。 您甚至可以将重定向与其他参数混合使用: cat >outfile cat outfile都非常好。 您还可以将多个输入或输出串在一起(输入将按顺序读取,所有输出将写入每个输出文件): cat >outfile1 >outfile2 。 重定向的目标或来源可以是文件名或流的名称(如&1,至少在bash中)。

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

 [command1] | [command2] 

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

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

 cat outfile | cat 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出现时从ls获取每一行,并将该行传递给grep。

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

TL; DR

  • | 管道与磁盘上的条目没有关联, 因此没有磁盘文件系统的inode数量(但在内核空间中的pipefs虚拟文件系统中有inode),但重定向通常涉及文件,这些文件有磁盘条目,因此具有相应的i节点。
  • 管道不是lseek() ‘能够这样命令无法读取某些数据然后回退,但是当你用><通常它是一个lseek()能够对象的文件重定向时,所以命令可以随意导航。
  • 重定向是对文件描述符的操作,可以是很多; 管道只有两个文件描述符 - 一个用于左命令,一个用于右命令
  • 标准流和管道上的重定向都是缓冲的。
  • 管道几乎总是涉及分叉,重定向 - 并非总是如此
  • 管道总是处理文件描述符,重定向 - 要么使用带有文件名的实际文件,要么使用文件描述符。
  • 管道是进程间通信方法,而重定向只是对打开文件或类文件对象的操作
  • 两者都使用dup2()系统调用来提供文件描述符的副本,其中发生实际的数据流。
  • 重定向可以用exec内置命令“全局”应用(参见本章和本章 ),所以如果你执行exec > output.txt ,那么每个命令都会写入output.txt| 管道仅适用于当前命令(这意味着简单命令或子shell,如seq 5 | (head -n1; head -n2)或复合命令。
  • 当对文件进行重定向时,诸如echo "TEST" > fileecho "TEST" >> file东西都对该文件使用open() syscall( 另请参阅 )并从中获取文件描述符以将其传递给dup2() 。 管道| 只使用pipe()dup2()系统调用。

  • 对于正在执行的命令,管道和重定向只不过是文件描述符 - 类似文件的对象,它们可以盲目地写入,或者在内部操作它们(这可能会产生意外的行为; 例如,往往甚至不会写入stdout,如果它知道有重定向)。

介绍

为了理解这两种机制是如何不同的,有必要了解它们的基本属性,背后的历史以及它们在C编程语言中的根源。 事实上,知道什么是文件描述符,以及dup2()pipe()系统调用如何工作是必不可少的,以及lseek() 。 Shell意味着将这些机制抽象给用户,但是挖掘比抽象更深入有助于理解shell行为的真实本质。

重定向和管道的起源

根据Dennis Ritche的文章Prophetic Petroglyphs ,管道起源于Malcolm Douglas McIlroy 1964年的内部备忘录 ,当时他们正在研究Multics操作系统 。 引用:

简而言之,我最关注的是:

  1. 当有必要以另一种方式按摩数据时,我们应该有一些方法来连接像花园软管这样的程序 - 拧入另一个部分。 这也是IO的方式。

显而易见的是,当程序能够写入磁盘时,如果输出很大,则效率很低。 引用Brian Kernighan在Unix Pipelinevideo中的解释:

首先,您不必编写一个大型程序 - 您已经拥有可能已经完成部分工作的现有小型程序......另一个原因是,您正在处理的数据量可能不适合你将它存储在一个文件中...因为记住,我们回到了这些东西上的磁盘,如果幸运的话,还有一兆字节或两个数据...所以管道从来没有必要实例化整个输出。

因此,概念上的差异是显而易见的:管道是使程序彼此对话的机制。 重定向 - 是在基本级别写入文件的方式。 在这两种情况下,shell使这两件事变得简单,但在引擎盖下,有很多事情要进行。

更深入:shell的系统调用和内部工作

我们从文件描述符的概念开始。 文件描述符基本上描述了一个打开的文件(无论是磁盘上的文件,还是内存中的文件,还是匿名文件),它由整数表示。 两个标准数据流 (stdin,stdout,stderr)分别是文件描述符0,1和2。 他们来自哪里 ? 好吧,在shell命令中,文件描述符是从它们的父 - shellinheritance的。 并且它通常适用于所有进程 - 子进程inheritance父进程的文件描述符。 对于守护进程 ,通常会关闭所有inheritance的文件描述符和/或重定向到其他位置。

回到重定向。 真的是什么? 它是一种告诉shell为命令准备文件描述符的机制(因为重定向是在命令运行之前由shell完成的),并将它们指向用户建议的位置。 输出重定向的标准定义是

 [n]>word 

[n]有文件描述符号。 当您echo "Something" > /dev/null ,隐含数字1,并且echo 2> /dev/null

在引擎盖下,这是通过dup2()系统调用复制文件描述符来dup2() 。 我们来看df > /dev/null 。 shell将创建一个子进程,其中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)以便稍后恢复它。 重要的是要注意,当您考虑内置命令 (它们是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脚本中的实际用途可以是通用的:

  • 将输出保存到只写入stderr的程序的变量
  • 交换stderr和stdout
  • 甚至将输入线与奇数输入线分开

还有很多其他的。

管道pipe()dup2() pipe()

那么如何创建管道呢? 通过pipe()系统调用 ,它将输入一个名为pipefd的数组(也就是列表)作为输入,这两个类型为int (整数)。 这两个整数是文件描述符。 pipefd[0]将是管道的读取端, pipefd[1]将是写入端。 所以在df | grep 'foo' pipefd[0] df | grep 'foo'grep将获得pipefd[0]的副本, df将获得pipefd[1]的副本。 但是怎么样? 当然,借助dup2()系统调用的魔力。 对于我们示例中的df ,让我们说pipefd[1]有#4,所以shell会生一个孩子,做dup2(4,1) (还记得我的cp例子吗?),然后执行execve()来实际运行df 。 当然, df将inheritance文件描述符#1,但不会意识到它不再指向终端,而是实际上是fd#4,它实际上是管道的写入端。 当然,除了不同数量的文件描述符之外, grep 'foo'也会发生同样的事情。

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

需要注意的是,管道总是处理文件描述符。 存在FIFO或命名管道 ,它在磁盘上有一个文件名,让你将它用作文件,但行为就像一个管道。 但是| 管道的类型是所谓的匿名管道 - 它们没有文件名,因为它们实际上只是连接在一起的两个对象。 我们不处理文件这一事实也是一个重要的含义: 管道不是lseek()能够的。 内存或磁盘上的文件都是静态的 - 程序可以使用lseek()系统调用跳转到字节120,然后返回到字节10,然后一直转发到结尾。 管道不是静态的 - 它们是顺序的,因此您无法使用lseek()来回滚从它们获得的数据。 这使得一些程序在从文件或管道中读取时能够识别,因此他们可以对有效性能进行必要的调整; 换句话说, prog可以检测我是否执行cat file.txt | prog cat file.txt | progprog < input.txt 。 真正的工作示例是尾巴 。

管道的另外两个非常有趣的属性是它们有一个缓冲区, 在Linux上是4096字节 ,它们实际上有一个Linux源代码中定义的文件系统 ! 它们不仅仅是传递数据的对象,它们本身就是一个数据结构! 实际上,因为存在pipefs文件系统,它管理管道和FIFO, 管道在它们各自的文件系统上有一个inode编号:

 # 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脚本的神奇function,但您也可以在Linux上制作双向管道 。

也可以看看:

  • 如何使任何shell命令的输出无缓冲?
  • Wikipedia使用pipe() syscall和dup2()在C中创建管道的示例 。
  • 为什么使用管道而不是输入重定向
  • &>和2>&1之间有什么区别
  • 诸如<<<<<类的重定向在bashksh中实现为匿名(未链接)临时文件,而< <()使用匿名管道; /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` 

我今天在C中遇到了这个问题。 基本上Pipe也有不同的重定向语义,即使发送到stdin 。 真的,我认为鉴于差异,管道应该stdpipe stdin之外的其他地方,以便stdin和我们称之为stdpipe (以产生任意差异)可以以不同的方式处理。

考虑一下。 当一个程序输出到另一个fstat似乎返回零作为st_size尽管ls -lha /proc/{PID}/fd显示有一个文件。 当重定向文件时,情况并非如此(至少在debian wheezystretchjessie vanilla和ubuntu 16.04 vanilla上。

如果使用重定向cat /proc/{PID}/fd/0 ,您将能够重复读取任意次数。 如果使用管道执行此操作,您会注意到第二次连续运行任务时,您将得不到相同的输出。