Linux 中的管道是什么?管道重定向是如何工作的?

Posted CSDN云计算

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 中的管道是什么?管道重定向是如何工作的?相关的知识,希望对你有一定的参考价值。

作者 | 刘光录

来源 | TIAP

我们在命令行中经常会用到类似 cmd0 | cmd1 | cmd2 的写法。其实,这是管道重定向(pipe redirection),用于将一个命令的输出作为输入重定向到下一个命令。

那么,你知道它具体是怎么工作的吗?今天我们来详细了解一下。

注:本文中会有多个地方使用 Unix 这个术语(而不是Linux),因为管道的概念起源于 Unix。

Linux 中的管道:总体思路

以下是关于“什么是 Unix 管道?”的内容:

Unix 管道是一种 IPC(Inter Process Communication,进程间通信)机制,它将一个程序的输出转发到另一程序的输入。

现在,我们换一种更加专业且易懂的语言重新解释一下:

Unix 管道是一种 IPC(Inter Process Communication,进程间通信)机制,它接收程序的标准输出(stdout),并通过缓冲区将其转发给另一个程序的标准输入(stdin)。

这样的描述,大家应该能理解了。参考下图可以了解管道的工作原理:

管道命令的最简单示例之一是将一些命令输出传递给 grep 命令以搜索特定字符串。

比如,我们可以搜索名称包含txt的文件,如下所示:

管道将标准输出重定向到标准输入,但不是作为命令参数

有个非常重要的一点需要注意,管道命令将标准输出(stdout)传递到另一个命令的标准输入(stdin),但不是作为参数。下面我们举个例子来说明这一点。

如果我们不带任何参数使用 cat 命令,它默认会从 stdin 读取内容。看下面的例子:

$ cat
Hello, my friend.
^D
Hello, my friend.

在上面的例子中,没有带任何参数使用了 cat,因此它默认会读取 stdin。接下来,我写了一行文字,然后按键 Ctrl+d 告诉它我写完了(Ctrl+d 表示 EOF 或文件结束)。随后,cat 命令读取 stdin,然后把之前我写的那行文字输出到了终端中。

现在,看如下命令:

echo hey | cat

管道右边的命令并不等于 cat hey。这里,标准输出(stdout)"hey" 被放在了缓冲区(buffer),并被传输到了 cat 命令的标准输入(stdin)。由于没有命令行参数,所以 cat 默认读取 stdin,而 stdin 中恰好有了内容(即“hey”),因此 cat 读取了这个内容,并将其打印到 stdout。

为了演示这个区别,我们可以创建一个名为 hey 的文件,并在其中添加一些文本。参见下图:

Linux 中的管道类型

Linux 中有两种类型的管道:

1)匿名管道,也就是未命名管道;

2)命名管道。

匿名管道

顾名思义,匿名管道就是没有名称。当你使用 | 符号时,它们就会由 Unix shell 动态创建了。

我们通常所说的管道,就是指的匿名管道。它用起来很方便,作为最终用户,我们不需要跟踪它的运行,shell 自动会处理这一切。

命名管道

这个稍有不同,命名管道在文件系统中确实存在。它们像普通文件一样存在,可以使用下面的命令创建命名管道:

mkfifo pipe

这将创建一个名为 pipe 的文件,执行以下命令:

$ ls -l pipe
prw-r--r--. 1 gliu gliu 0 Aug 4 17:23 pipe

请注意开头的“p”,这意味着该文件是一个管道。现在我们来使用这个管道。

如前所述,管道将命令的输出转发给另一个命令的输入。这就像快递服务,你把包裹从一个地址送到另一个地址。因此,第一步是提供包裹。

echo hey > pipe

我们会看到 echo 信息没有打印出来,看起来像是被挂起了。新打开一个终端,尝试读取该文件:

cat pipe

我们看下两个终端的输出结果,如下图所示:

惊讶吗?这两个命令同时完成了执行。

这是普通文件和命名管道之间的基本区别之一。在其他进程读取管道之前,不会将任何内容写入管道。

那么,为什么要使用命名管道呢?我们来看一下。

命名管道不会占用磁盘上的任何内存。

如果我们执行命令 du -s pipe,就会发现它不会占用任何空间。这是因为命名管道就像从内存缓冲区读写的端点。写入命名管道的任何内容实际上都存储在临时内存缓冲区中,当从另一个进程执行读取操作时,该缓冲区将被刷新。

节省 IO

因为写入命名管道意味着将数据存储到内存中的缓冲区中,因此如果涉及大文件的操作的话,就会大幅减少磁盘 I/O。

两个不同进程之间的通信

通过使用命名管道,可以高效地从另一个进程实时获取事件的输出。因为读和写同时发生,所以没有等待时间。

较低层次的管道理解(针对高级用户和开发人员)

接下来我们更深入的讨论一下管道,以及具体的实现。这些需要对以下内容有基本的了解:

  • C 程序工作原理;

  • 什么是系统调用;

  • 什么是进程;

  • 什么是文件描述符。

我们不会很详细的介绍这些概念,只讨论与管道相关的内容。对于大多数Linux用户来说,下面的内容可以选择性的阅读。

为了进行编译,在文章最后提供了一个示例 makefile。当然,这只是用来说明的伪代码。

看以下程序:

// pipe.c
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>


extern int errno;


int main()
signed int fd[2];
pid_t pid;
static char input[50];
static char buf[50];


    pipe(fd);


if((pid=fork())==-1)
int err=errno;
        perror("fork failed");
exit(err);
    


if(pid)
        close(fd[1]);
        read(fd[0], buf, 50);
printf("The message read from child: %s\\n", buf);
     else 
        close(fd[0]);
printf("Enter a message from parent: ");
for(int i=0; (input[i]=getchar())!=EOF && input[i]!='\\n' && i<49; i++);
        write(fd[1], input, 50);
exit(0);
    
return 0;

在第16行,我使用 pipe() 函数创建了一个匿名管道,传递了一个长度为 2 的带符号整数数组。

这是因为管道只是一个包含两个无符号整数的数组,代表两个文件描述符。一个用于写,一个用于读。它们都指向内存上的缓冲区位置,通常为1mb。

这里我将变量命名为fd。fd[0] 是输入文件描述符,fd[1] 是输出文件描述符。在该程序中,一个进程将字符串写入 fd[1] 文件描述符,另一个进程从 fd[0] 文件描述符读取。

命名管道也一样,使用命名管道(而不是两个文件描述符),你可以从任何一个进程中打开一个文件,并像其他文件一样对其进行操作。同时应记住管道的特性。

下面是一个示例程序,它执行与前一个程序相同的操作,但它创建的不是匿名管道,而是命名管道:

// fifo.c
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>


extern int errno;


#define fifo "npipe"


int main(void)
pid_t pid;
static char input[50];
static char buf[50];
signed int fd;


    mknod(fifo, S_IFIFO|0700, 0);


if((pid=fork())<0)
int err=errno;
        perror("Fork failed");
exit(err);
    


if(pid)
        fd=open(fifo, O_RDONLY);
        read(fd, buf, 50);
        close(fd);
printf("The output is : %s", buf);
        remove(fifo);
exit(0);
     else 
        fd=open(fifo, O_WRONLY);
for(int i=0; (input[i]=getchar())!=EOF && input[i]!='\\n' && i<49; i++);
        write(fd, input, strlen(input));
        close(fd);
exit(0);
    
return 0;

在这里,我使用 mknod 系统调用来创建命名管道。如你所见,虽然在完成时删除了管道,但你可以不使用它,只需要打开并写入本例中的 npipe 文件,就可以轻松的实现在不同进程之间的通信。

其实现实中,我们也不必创建两个管道来实现双向通信,匿名管道就是这样的。

以下是一个简单的 Makefile 的源代码示例(只是示例),将其与前面的程序放在同一个目录中(分别为 pipe.c 和 fifo.c)。

CFLAGS?=-Wall -g -O2 -Werror
CC?=clang


build:
$(CC) $(CFLAGS) -o pipe pipe.c
$(CC) $(CFLAGS) -o fifo fifo.c


clean:
rm -rf pipe fifo

以上就是本次分享的关于 Unix 管道的全部内容,欢迎讨论。

往期推荐

一篇文章了解 Docker 的安装、启动以及工作原理!

剖析 kubernetes 集群内部 DNS 解析原理

Docker 镜像和容器的导入导出及常用命令

如何从 Docker 镜像里提取 dockerfile!

点分享

点收藏

点点赞

点在看

以上是关于Linux 中的管道是什么?管道重定向是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

Linux文件管理重定向和管道

linux中的管道符重定向于环境变量

4. Linux中的管道和重定向

linux中的管道和重定向

Linux管道(Pipes)

Linux - Linux中的重定向和管道符