shell如何实现管道符号‘|‘

在 Shell 中使用的管道符 | 属于 无名管道(Unnamed Pipes)。它用于将一个命令的输出直接传递给另一个命令作为输入,实现了进程间的数据流转。

工作原理

当你在 Shell 中使用 | 时,操作系统会创建一个无名管道,并将左边命令的标准输出(stdout)重定向到这个管道的写入端,同时将右边命令的标准输入(stdin)重定向到这个管道的读取端。

实现机制

假设我们在 Shell 中运行以下命令:

bash 复制代码
ls | grep "file"

这一操作会执行如下步骤:

  1. 创建无名管道 :操作系统通过 pipe 系统调用创建一个无名管道。pipe 系统调用会返回两个文件描述符,一个用于写入数据,一个用于读取数据。

  2. 执行第一个命令 :Shell 使用 fork 系统调用创建一个子进程来运行 ls 命令。ls 命令的标准输出会被重定向到管道的写入端。

  3. 执行第二个命令 :Shell 再次使用 fork 创建另一个子进程来运行 grep "file" 命令。grep 命令的标准输入会被重定向到管道的读取端。

  4. 数据传递 :ls 命令的输出(例如文件列表)通过管道写入端进入管道,然后 grep 命令从管道的读取端读取数据并进行过滤。

  5. 管道关闭:当所有数据被写入和读取后,管道会被关闭,两个子进程分别结束。

例子

以下是上述原理的简单实现示例,使用 C 语言模拟 Shell 中的 ls | grep "file" 操作:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    int pipefd[2];
    pid_t pid;

    // 创建无名管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 创建第一个子进程执行 `ls`
    if ((pid = fork()) == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程 1:执行 `ls`
        close(pipefd[0]);  // 关闭读取端
        dup2(pipefd[1], STDOUT_FILENO);  // 重定向 stdout 到管道写入端
        execlp("ls", "ls", NULL);  // 执行 `ls`
        perror("execlp ls");
        exit(EXIT_FAILURE);
    } else {
        // 创建第二个子进程执行 `grep`
        if ((pid = fork()) == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        }

        if (pid == 0) {
            // 子进程 2:执行 `grep`
            close(pipefd[1]);  // 关闭写入端
            dup2(pipefd[0], STDIN_FILENO);  // 重定向 stdin 到管道读取端
            execlp("grep", "grep", "file", NULL);  // 执行 `grep`
            perror("execlp grep");
            exit(EXIT_FAILURE);
        } else {
            // 父进程:关闭管道两端,并等待子进程结束
            close(pipefd[0]);
            close(pipefd[1]);
            wait(NULL);  // 等待第一个子进程结束
            wait(NULL);  // 等待第二个子进程结束
        }
    }

    return 0;
}

这个示例中,pipe 系统调用用于创建无名管道,fork 创建了两个子进程,分别执行 ls 和 grep,并通过无名管道连接两个进程。Shell 使用类似的方式来实现 | 管道操作符。

疑问:exec系统调用会清空子进程内存,为什么管道的文件描述符依然可用?

答:
exec replaces the instruction and data segments by those inferred from the indicated file and starts the process running. The system data segment is unaltered. So the PID, current working directory and file descriptors are unaltered. Open files remain open except if fcntl(2) has been used to set the close-on-exec flag.

也就是说,exec会用指定文件替换内存的指令段和数据段,而系统数据段不会改变,因此,例如pid,当前运行目录和文件描述符不会变;或者说,子进程内存是父进程内存的完全拷贝,因此也有文件描述符,这些文件描述符也指向父进程打开的文件。打开的文件会保持打开状态,除非利用fcntl系统调用设置了close-on-exec。

相关推荐
xbzb2 小时前
容器 IP 每次重建都变?docker network 五种模式与自定义网桥固定地址
网络·docker·容器·ip·network·固定
火云代理3 小时前
同一个网站,有的网络能打开、有的打不开?认识 IPv4 与 IPv6 的访问差异
服务器·网络·网络协议·tcp/ip·测试工具·php·状态模式
坤坤子吖3 小时前
Linux网络编程:TCP 粘包与半包问题,如何正确处理 TCP 流式数据?
linux·服务器·网络·笔记·网络协议·学习
AI_Gump3 小时前
Harness篇|DeepSeek Harness 上手机:五条安卓移植路线与 App 内 Linux 拆解
android·linux·人工智能·智能手机·deepseek
星恒随风5 小时前
MySQL 从零入门(六):复合查询详解——多表查询、自连接、子查询与 UNION
linux·服务器·数据库·mysql
少司府5 小时前
嵌入式入门篇(二):初识STM32F103ZET6
linux·python·stm32·单片机·嵌入式硬件·mcu·嵌入式软件
无足鸟ICT6 小时前
【RHCA+】IO调度算法
linux·服务器
火云代理6 小时前
Python 使用代理 IP 前,先弄清 Requests 的配置来源与验证方法
网络·python·网络协议·tcp/ip·ip
wacsii6 小时前
【Linux进程(四)】命令行参数与环境变量
linux·服务器
无奈笑天下6 小时前
银河麒麟上lsblk命令教程详解
linux·运维·服务器·数据库