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

Joh*_*ood 270 pipe redirect

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

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

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

但是为什么同一件事有两个运营商呢?

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

Dav*_*ill 306

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

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

示例:thing1 > thing2vsthing1 | thing2

thing1 > thing2

  1. 你的 shell 将运行名为的程序 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 &gt; temp_file &amp;&amp; thing2 &lt; temp_file` 可以更轻松地使用管道。但为什么不重新使用“&gt;”运算符来执行此操作,例如命令“thing1”和“thing2”使用“thing1 &gt; thing2”?为什么需要额外的运算符“|”? (2认同)
  • @Sridhar-Sarnobat 不,`tee` 命令做了一些不同的事情。`tee` 将输出写入屏幕(`stdout`)*和*文件。重定向*仅*文件。 (2认同)

Flo*_*sch 25

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


小智 25

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

重定向

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

管道

为了一个的标准输出连接命令到的STDIN另一个用途| 符号,俗称管道。

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


Ank*_*kit 14

这两个操作符之间有一个重要的区别:

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

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

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

ls > log.txt; grep 'file.txt' log.txt
Run Code Online (Sandbox Code Playgroud)

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


Ser*_*nyy 10

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

TL; 博士

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

介绍

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

重定向和管道的起源

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

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

  1. 我们应该有一些连接程序的方法,比如花园软管——当需要以另一种方式处理数据时,在另一个部分拧紧。这也是IO的方式。

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

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

因此概念上的差异是显而易见的:管道是一种让程序相互通信的机制。重定向 - 是在基本级别写入文件的方式。在这两种情况下,shell 都使这两件事变得容易,但在幕后,还有很多事情要做。

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

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

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

[n]>word
Run Code Online (Sandbox Code Playgroud)

[n]就是文件描述符编号。当你这样做时echo "Something" > /dev/null,数字 1 隐含在那里,而echo 2> /dev/null.

在幕后,这是通过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
Run Code Online (Sandbox Code Playgroud)

这些在 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,所以 shell 将创建一个子项,执行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命名管道,它在磁盘上有一个文件名,让您将其用作文件,但其行为类似于管道。但是|管道的类型就是所谓的匿名管道——它们没有文件名,因为它们实际上只是连接在一起的两个对象。我们不处理文件的事实也产生了一个重要的含义:管道不能lseek()文件,无论是在内存中还是在磁盘上,都是静态的——程序可以使用lseek()系统调用跳转到第 120 字节,然后返回到第 10 字节,然后一直前进到最后。管道不是静态的——它们是连续的,因此你不能倒带从它们那里得到的数据lseek(). 这就是让一些程序知道它们是从文件还是从管道读取的原因,因此它们可以进行必要的调整以提高性能;换句话说, aprog可以检测到 I docat file.txt | progprog < input.txt。实际工作示例是tail

管道的另外两个非常有趣的特性是它们有一个缓冲区,在 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]'
Run Code Online (Sandbox Code Playgroud)

在 Linux 上,管道是单向的,就像重定向一样。在一些类 Unix 实现上 - 有双向管道。尽管借助 shell 脚本的魔力,您也可以在 Linux 上制作双向管道

也可以看看:


归档时间:

查看次数:

135607 次

最近记录:

5 年,3 月 前