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


204

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

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

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

但是,为什么会有两个运算符来处理同一件事?

为什么不只是写写ls > grep来传递输出,这也不只是一种重定向吗?我缺少什么?

Answers:


223

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

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

示例:thing1 > thing2vsthing1 | thing2

thing1 > thing2

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

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

thing1 > temp_file && thing2 < temp_file

这将

  1. 运行名为 thing1
  2. 将输出保存到名为 temp_file
  3. 运行名为的程序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 < temp_file可以更轻松地使用管道。但是,为什么不重新使用>操作员来执行此操作,例如thing1 > thing2用于命令thing1thing2?为什么需要额外的运算符|
约翰·特里普伍德

1
“获取输出并将其写入文件”与“获取输出并将其传递到其他程序”是不同的操作。我会在回答中添加更多想法……
大卫·奥尼尔

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

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

2
@ Sridhar-Sarnobat不,该tee命令执行其他操作。 tee将输出写入屏幕(stdout文件。重定向执行文件。
David Oneill 2014年

22

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


仅当您在bar属于$PATHenv变量的目录中写入时,这才是问题。如果您在/ bin之类的目录中,则可能是个问题。但是即使这样,bar也必须设置可执行文件许可权,以便外壳程序不仅检查可执行文件,bar还可以执行它。而且,如果要考虑覆盖现有文件,则noclobershell选项应防止在重定向中覆盖现有文件。
Sergiy Kolodyazhnyy

13

从Unix和Linux系统管理手册中:

重新导向

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

管子

要连接一个的标准输出命令到STDIN的另一个使用| 符号,通常称为管道。

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


12

两家运营商之间有着至关重要的区别:

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

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

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

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

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


3

两者之间在语法上有很大的不同:

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

您可以想到这样的重定向:cat [<infile] [>outfile]。这表示顺序无关紧要:cat <infile >outfile与相同cat >outfile <infile。您甚至可以混合使用其他参数重定向起来:cat >outfile <infile -bcat <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。


1
Nitpick:如果将一个fd重定向到另一个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
muru

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

3

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

TL; DR

  • |管道不与磁盘上的条目相关联,因此没有磁盘文件系统的索引节点号(但在内核空间的pipefs虚拟文件系统中具有索引节点),但是重定向通常涉及文件,该文件确实具有磁盘条目并因此具有对应的索引节点。
  • 管道不可用lseek(),因此命令无法读取某些数据,然后回退,但是当您使用重定向><通常是带有lseek()对象的文件时,命令可以随心所欲地导航。
  • 重定向是对文件描述符的操作,可以很多。管道只有两个文件描述符-一个用于左命令,一个用于右命令
  • 标准流和管道上的重定向都被缓冲。
  • 管道几乎总是涉及分叉,因此涉及成对的过程;重定向-并非总是如此,尽管在这两种情况下,结果文件描述符都是由子进程继承的。
  • 管道始终连接文件描述符(一对),重定向-使用路径名或文件描述符。
  • 管道是进程间通信方法,而重定向只是对打开的文件或类似文件的对象的操作
  • 两者都dup2()在引擎盖下使用syscall来提供文件描述符的副本,在此发生实际的数据流。
  • 重定向可以使用exec内置命令“全局”应用(请参阅thisthis),因此,如果执行此操作,则此后exec > output.txt将写入所有命令output.txt|管道仅适用于当前命令(这意味着简单命令或子shell之类的seq 5 | (head -n1; head -n2)命令或复合命令。
  • 当重定向的文件来完成,像echo "TEST" > fileecho "TEST" >> file都使用open()了该文件系统调用(),并从它那里得到的文件描述符通过它来dup2()。管道|仅使用pipe()dup2()syscall。

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

介绍

为了了解这两种机制的不同之处,有必要了解它们的基本属性,两者的历史以及它们在C编程语言中的根源。实际上,了解文件描述符是什么,以及如何dup2()以及pipe()系统调用是必不可少的lseek()。Shell的意思是使这些机制对用户抽象的一种方式,但是比抽象更深入地挖掘有助于理解Shell行为的真实本质。

重定向和管道的起源

根据丹尼斯·里奇(Dennis Ritche)的文章《先知岩画》(Prophetic Petroglyphs),管道起源于1964年马尔科姆·道格拉斯·麦克罗伊Malcolm Douglas McIlroy)编写的内部备忘录,当时他们正在研究Multics操作系统。引用:

简单地说:

  1. 我们应该有一些连接程序的方法,例如花园水管-当有必要以另一种方式处理数据时,请拧入另一段。这也是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拥有两个相同的文件时,您可以独立地操作它们吗?这有点一样。通常,您可以看到在运行之前,它们bashdup(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上建立双向管道

也可以看看:


2

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

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`

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

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

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

没错 这是strace我在第一条评论中给出的命令所能看到的。通过重定向,tmp文件位于磁盘上,这使得它易于查找(因为它们使用lseek()syscall -命令可以根据需要从文件的首字节跳到最后一个字节。但是管道是顺序的而不是可查找的。因此,head执行此操作的唯一方法工作是第一次读到的一切,或者,如果文件是大-地图有些是通过到RAM mmap()的呼叫我曾经做过我自己。tail在Python,跑进完全相同的问题。
谢尔盖Kolodyazhnyy

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

1

我今天在C语言中遇到了一个问题。本质上,即使将Pipe发送到,其重定向的语义也不同stdin。确实,我认为给定这些差异,管道应该走到以外的地方stdin,以便以不同的方式来处理stdinstdpipe(让它成为任意的差异)。

考虑一下。当管道显示一个程序输出到另一个程序时fstatst_size尽管ls -lha /proc/{PID}/fd显示有文件,但似乎返回零。当重定向文件不是这种情况下(至少在Debian wheezystretchjessie香草和Ubuntu 14.0416.04香草。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.