造轮子工程 · 第 2 章 造一个 Shell 章节摘要:Shell 是本书选作开篇的第一个实战项目,原因有三。第一,它是「你能天天用」的工具——造完一个能用的小 Shell,你能立刻拿它跑自己熟悉的命令,这种「我造的东西真在工作」的反馈极其宝贵。第二,它的难度恰好「踮脚能够到」:复杂到能教会你 fork/exec、管道、信号、作业控制等系统编程核心概念,又不至于像造操作系统那样需要一年。第三,它与姊妹篇《Linux 命令》第 9 章直接衔接——当你写 Shell 脚本写到「想让这个脚本语言更好用」时,造一个 Shell 就是顺理成章的下一步。
章节摘要:Shell 是本书选作开篇的第一个实战项目,原因有三。第一,它是「你能天天用」的工具——造完一个能用的小 Shell,你能立刻拿它跑自己熟悉的命令,这种「我造的东西真在工作」的反馈极其宝贵。第二,它的难度恰好「踮脚能够到」:复杂到能教会你 fork/exec、管道、信号、作业控制等系统编程核心概念,又不至于像造操作系统那样需要一年。第三,它与姊妹篇《Linux 命令》第 9 章直接衔接——当你写 Shell 脚本写到「想让这个脚本语言更好用」时,造一个 Shell 就是顺理成章的下一步。本章导读 Shell 的七个子系统:命令读取与解析、内建命令(cd/exit 等)、外部命令(fork+exec)、管道(连接多个进程的 stdin/stdout)、重定向(操作文件描述符)、作业控制(后台/前台/挂起)、脚本能力(变量/条件/循环)。我们给一个「最小 Shell 一百行」的路线图,每一步标注它依赖你从《Linux 命令》哪一章学的能力。读完本章,你能动手写一个能跑
ls | grep foo | wc -l的小 Shell。
阅读完本章,你应当能够:
cd 必须是内建的。A | B 是「A 的 stdout 接到 B 的 stdin」。>、<、>>),理解它本质是「把文件描述符 0/1/2 换成某个文件的句柄」。& 后台、Ctrl+Z 挂起、fg/bg),理解信号(SIGTSTP/SIGCONT)在其中扮演的角色。$?、&&/||),体会「Shell 也是一种编程语言」。Shell 的全部魔法,浓缩成一个循环:读取 → 解析 → 内建就自己做、外部就 fork+exec → 等待、收退出码 → 回到读取。管道和重定向是这个循环里 fork 阶段的「文件描述符接线」技巧。
Shell 的骨架是一个 REPL(Read-Eval-Print Loop):读取一行、执行、回到读取。讲清读取用什么(fgets/readline)、提示符怎么打印、为什么 Shell 没有「Print」这一段(命令自己负责输出)。呼应《Linux 命令》第 1 章的「提示符」概念。
把一行字符串切成「命令 + 参数」,并识别其中的特殊符号(|、>、<、&、;)。讲清这一步要把字符串拆成「管道段」和「重定向目标」,为后续 fork 与 dup2 做准备。呼应《Linux 命令》第 1 章的「Shell 展开」。
cd、exit、export、alias 必须是内建的——因为它们改变的是 Shell 自己的进程状态(当前目录、环境变量),fork 出子进程改了也没用。讲清「为什么 cd 不能是外部程序」这个经典问题,这是理解内建命令存在意义的关键。
Unix 进程创建的核心两步:fork(复制当前进程,得到子进程)、exec(在子进程里把程序映像替换为另一个可执行文件)。讲清为什么是两步分离(便于在 fork 之后、exec 之前做重定向接线),以及 wait 收尸。呼应《Linux 命令》第 6 章的「进程」概念。
管道 | 用 pipe 系统调用建一对文件描述符,dup2 把子进程的 stdin/stdout 接到这对 fd 上。重定向 >/< 则用 dup2 把 fd 1/0 接到某个打开的文件。这是 Shell 里最巧妙的部分——理解了 dup2,管道和重定向的神秘感就消失了。
&(后台运行)、Ctrl+Z(挂起,发 SIGTSTP)、fg/bg(恢复,发 SIGCONT)。讲清作业控制本质是「Shell 用信号管理一组子进程」,以及为什么需要进程组(process group)的概念。呼应《Linux 命令》第 6 章的「信号」。
让你的 Shell 支持变量(x=5、$x)、退出码($?)、条件(&&/||)。这一步让 Shell 从「交互式命令执行器」升级为「可编程脚本语言」。呼应《Linux 命令》第 9 章——那章是「用 Shell 脚本」,这章是「造 Shell 脚本能力」。
本章遵循「骨架 → 解析 → 执行 → 接线 → 控制 → 脚本」的递进路径,从最小可用一路扩到接近真实的 Shell:
REPL 主循环 (01) ── 先有"读-执行-循环"的骨架 │ ▼ 命令解析 (02) ── 把字符串拆成可执行的指令 │ ▼ 内建vs外部 (03) ── 区分"自己做"与"fork 出去做" │ ▼ fork+exec (04) ── 外部命令的执行机制 │ ▼ 管道重定向 (05) ── 文件描述符接线(最巧妙的部分) │ ▼ 作业控制 (06) ── 用信号管理后台与挂起 │ ▼ 脚本能力 (07) ── 从执行器升级为脚本语言 │ ▼ 第 3 章:造命令行工具(CLI),Shell 的近亲
这是一条「从最小可用到接近真实」的路线:01-02 是骨架与解析,03-04 是「能跑外部命令」的最小 Shell(约一百行即可达成),05 是最巧妙的管道/重定向,06-07 是进阶。建议你的实现分阶段:先做到 04(能跑单条命令),再做到 05(能跑管道),最后挑战 06-07。
前置知识:
本章为后续章节奠定的基础: