你是否好奇,当你在终端输入一条命令并按下回车后,操作系统究竟做了什么?其实,这一切的背后是一个叫做 Shell 的程序在默默工作。它既是命令行解释器,也是用户与内核交互的桥梁。本文将带你从零实现一个简化版的自定义Shell,通过这个过程深入理解 Linux进程控制 的核心概念,包括进程创建、进程等待、程序替换以及进程间的协作(管道)。无论你是刚接触Linux的小白,还是想巩固进程知识的开发者,都能从中获得收获。
在动手写Shell之前,我们需要先了解几个关键的系统调用,它们是实现 命令行解释器实现 的基石:
这些系统调用共同构成了 进程协作 的基础。我们的自定义Shell将利用它们来执行外部命令并实现管道功能。
一个最简单的Shell是一个循环,每一步做三件事:
下面我们一步步实现这个流程。
我们使用C标准库函数 fgets() 从标准输入读取一行,然后去除末尾的换行符。为了让用户知道可以输入,先打印一个提示符,比如 "myshell$ "。
char input[1024];while (1) { printf("myshell$ "); fflush(stdout); if (fgets(input, sizeof(input), stdin) == NULL) break; // Ctrl+D 退出 input[strcspn(input, "")] = 0; // 去掉换行符 // 接下来解析 input} 最简单的解析是用空格分割字符串得到命令和参数,同时检测是否包含管道符。为了支持管道,我们可以先将输入按 "|" 分割成多个子命令,然后对每个子命令再按空格分割出参数列表。这里用一个简化版本:假设最多支持一个管道,命令格式为 "cmd1 | cmd2"。
char *commands[2];int pipe_pos = -1;for (int i = 0; input[i]; i++) { if (input[i] == "|") { input[i] = 0; commands[0] = input; commands[1] = &input[i+1]; pipe_pos = 1; break; }}if (pipe_pos == -1) { // 无管道 commands[0] = input; commands[1] = NULL;}// 然后对每个命令用 strtok 分割参数... 内建命令不需要创建新进程。例如 cd 需要改变当前工作目录,直接调用 chdir();exit 则直接退出Shell循环。
if (strcmp(args[0], "cd") == 0) { if (args[1] == NULL) chdir(getenv("HOME")); else chdir(args[1]); continue;}if (strcmp(args[0], "exit") == 0) { break;} 对于外部命令,我们创建子进程,在子进程中用 execvp 执行,父进程用 wait 等待子进程结束。
pid_t pid = fork();if (pid == 0) { // 子进程 execvp(args[0], args); perror("exec failed"); exit(1);} else if (pid > 0) { wait(NULL); // 等待子进程结束} else { perror("fork failed");} 当命令包含管道时,我们需要创建两个子进程,并让它们通过管道连接。例如执行 ls | grep .c:
ls,将其标准输出重定向到管道的写端(dup2(fd[1], STDOUT_FILENO)),然后关闭不必要的文件描述符,执行 ls。grep .c,将其标准输入重定向到管道的读端(dup2(fd[0], STDIN_FILENO)),然后执行 grep。int fd[2];pipe(fd);pid_t pid1 = fork();if (pid1 == 0) { dup2(fd[1], STDOUT_FILENO); // 标准输出指向管道写端 close(fd[0]); close(fd[1]); execvp(cmd1[0], cmd1); exit(1);}pid_t pid2 = fork();if (pid2 == 0) { dup2(fd[0], STDIN_FILENO); // 标准输入来自管道读端 close(fd[0]); close(fd[1]); execvp(cmd2[0], cmd2); exit(1);}close(fd[0]); close(fd[1]); // 父进程关闭管道waitpid(pid1, NULL, 0);waitpid(pid2, NULL, 0); 这样,两个进程通过管道协作完成了数据处理,这正是 进程协作 的典型例子。
将上述代码片段组合起来,你就有了一个具备基本功能的 自定义Shell。你可以编译运行它,尝试执行一些简单命令(如 ls、pwd)以及管道命令(如 ls -l | grep .txt)。当然,这个Shell还很简陋,不支持输入输出重定向、后台运行、多级管道等特性,但足以让你理解其核心原理。
通过本文,我们实践了 Linux进程控制 的几个关键系统调用:fork、exec、wait 和 pipe,并用它们实现了一个简易的 命令行解释器。我们不仅学习了如何启动新进程,还通过管道让多个进程协作完成任务。希望这篇教程能帮助你巩固进程管理知识,并为后续学习更复杂的Shell功能(如作业控制、重定向)打下基础。
本文涉及的核心关键词:Linux进程控制、自定义Shell、进程协作、命令行解释器实现。
本文由主机测评网于2026-02-18发表在主机测评网_免费VPS_免费云服务器_免费独立服务器,如有疑问,请联系我们。
本文链接:https://www.vpshk.cn/20260225792.html