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

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

我们在命令行中经常会用到类似 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 管道的全部内容,欢迎讨论。

责任编辑:庞桂玉 来源: TIAP
相关推荐

2020-12-09 11:10:12

shellLinux管道

2020-02-24 11:37:56

Linux重定向管道

2020-04-17 08:34:39

Linux管道

2009-06-17 17:13:40

UNIX

2009-06-17 09:11:24

Unix管道重定向

2018-09-10 08:45:04

Linux管道命令

2021-02-20 20:36:56

Linux无名管道

2021-07-02 16:30:01

CICDDevOps

2022-11-10 15:08:44

Linux输入输出

2023-01-30 15:55:08

2015-10-08 10:44:06

PB级数据管道处理Docker容器

2023-08-08 07:18:17

协程管道函数

2010-12-14 15:07:15

ICMP路由重定向

2010-03-09 16:11:59

Linux重定向

2017-01-19 19:14:20

Linux重定向命令

2023-05-05 08:18:38

Linuxsource命令

2021-07-13 11:12:49

Discover数据管道AI

2014-08-07 10:23:24

linux重定向

2022-05-19 09:00:00

安全CI/CD工具

2009-02-01 14:34:26

PythonUnix管道风格
点赞
收藏

51CTO技术栈公众号