学习日期2026 年 10 月 12 日 学习资料Missing Semester 2026 — Shell 入门️ 标签LinuxShellBashMissing Semester学习笔记 目录前言为什么要学习 Shell1 Shell 与 Bash 基础1.1 Shell、Terminal 和 Bash 的区别1.2 Shell 如何执行命令2 Linux 文件系统与路径2.1 绝对路径与相对路径2.2 特殊路径符号2.3 cd 与 pwd3 环境变量与命令查找3.1 PATH 环境变量3.2 Shell 内建命令与外部命令3.3 which 和 type4 Linux 文本处理工具4.1 grep 文本搜索4.2 正则表达式与 grep4.3 sed 文本替换4.4 find 文件查找4.5 find 中的 exec 与占位符4.6 awk 文本处理5 管道与输入输出重定向5.1 Linux 的三种标准流5.2 输出重定向5.3 管道符6 Shell 条件判断6.1 if 的基本语法6.2 为什么返回 0 才执行6.3 test 与方括号6.4 双方括号7 循环与算术运算7.1 for 和 while 循环7.2 两种算术语法的区别7.3 算术命令与退出状态的陷阱8 完整 Bash 脚本解析8.1 Shebang 和严格模式8.2 后台运行与进程 ID8.3 命令替换与日志8.4 自动化测试的执行逻辑9 实践编写一个 C 文件搜索脚本10 易错知识点速查表11 学习总结与下一步计划参考资料前言为什么要学习 Shell最近开始学习 MIT 的The Missing Semester of Your CS Education计算机教育中缺失的一课今天学习的是 2026 版课程中的 Shell 入门。最开始我对 Shell 的认识比较简单它就是一个可以输入 Linux 命令的地方。想切换目录就使用cd想查看文件就使用ls想搜索文件就使用find。但随着学习深入我逐渐发现仅仅记住这些命令远远不够。例如为什么cd ../..能够返回上两级目录为什么 Shell 可以直接识别date这样的命令为什么grep和find都能搜索但用途不一样为什么find中的{}可以自动替换成文件路径为什么 Shell 的if在返回0时反而表示条件成立为什么 Bash 进行算术运算需要使用双层括号这些问题让我意识到学习 Shell 不能停留在记住命令的阶段而应该理解它们的语法以及背后的执行原理。因此我决定将今天学习的知识系统整理下来既方便复习也希望以后能够把它们运用到 C 开发和日常自动化任务中。1 Shell 与 Bash 基础1.1 Shell、Terminal 和 Bash 的区别这三个概念经常同时出现但实际上并不相同。概念含义Terminal终端提供输入和显示命令的界面Shell命令解释环境负责解析并执行命令Bash一种常见的 Shell也是脚本语言解释器可以简单理解为用户通过 Terminal 输入命令由 Shell 负责解释和执行。例如echo Hello LinuxShell 解析这条命令调用相应功能并将结果输出到终端。结果Hello LinuxBash 是 Shell 的一种具体实现并不是唯一的 Shell。其他常见的 Shell 还有 Zsh、Fish 等。1.2 Shell 如何执行命令例如date这条命令可以显示当前系统时间。从逻辑上看命令的执行可以概括为用户输入命令。Shell 解析命令名称和参数。Shell 判断命令类型并在必要时寻找可执行文件。执行相应的命令。将执行结果返回给用户。Shell 不仅能够运行单个命令还能够通过管道、重定向和脚本将多个命令组织为完整的工作流程。这也是 Shell 真正强大的地方。2 Linux 文件系统与路径2.1 绝对路径与相对路径Linux 文件系统采用树形目录结构。例如/ └── home └── user ├── Documents └── Downloads其中/是整个文件系统的根目录。路径主要分为两类。绝对路径cd /home/user/Documents绝对路径以/开头表示从根目录出发。相对路径cd Documents相对路径不以/开头而是从当前工作目录出发。假设当前目录为/home/user那么cd Documents最终就会进入/home/user/Documents。所以判断路径时最直接的方法就是观察是否以/开头。2.2 特殊路径符号Linux 中有几个非常重要的特殊路径符号。符号含义.当前目录..上一级目录~当前用户的家目录/根目录或路径分隔符其中我对../..的理解最初存在疑问。例如cd ../..为什么表示返回两级目录实际上/在这里是路径分隔符。可以把../..拆成两个..第一个..表示父目录第二个..表示父目录的父目录。假设当前目录是/home/user/Documents那么cd ..进入/home/user而cd ../..进入/home这让我理解了一个非常重要的规则路径开头的/表示根目录作为起点路径中间的/则负责分隔目录层级。另外在根目录/下执行cd ..仍然位于根目录因为已经没有更高的父目录。2.3 cd 与 pwdcd是 Change Directory 的缩写用于切换工作目录。例如cd / # 进入根目录 cd ~ # 进入家目录 cd .. # 返回父目录 cd ../.. # 返回上两级目录而pwd用于显示当前工作目录。例如/home/user/Documents理解当前工作目录十分重要因为大量 Linux 命令都会以当前目录作为相对路径的起点。3 环境变量与命令查找3.1 PATH 环境变量在终端中输入dateShell 为什么知道应该执行哪个程序这涉及环境变量PATH。可以通过下面的命令查看echo $PATH可能得到/usr/local/bin:/usr/bin:/bin这里$PATH表示读取环境变量PATH的值。:用来分隔不同的目录。每个目录都是 Shell 搜索外部命令时可能使用的位置。例如输入date当 Shell 需要搜索对应的外部程序时会按照 PATH 中的目录顺序寻找。如果找到了可执行程序就可以运行它。因此PATH 的作用是告诉 Shell 到哪些目录寻找外部命令。需要注意PATH 只是一组搜索目录并不是所有 Shell 命令都必须通过 PATH 才能执行。3.2 Shell 内建命令与外部命令Shell 命令并不都是独立的可执行程序。可以粗略分为内建命令Builtin例如cd /homeBash 自己内部就实现了cd因此不需要额外启动一个外部程序。外部命令例如grep Linux hello.txt通常需要运行独立的可执行文件。一个比较有意思的问题是为什么cd被实现为内建命令因为cd要改变当前 Shell 自己的工作目录。如果让一个独立的子进程完成目录切换它改变的只是自己的工作目录无法直接改变父 Shell 的工作目录。3.3 which 和 type可以使用which grep查看grep对应的可执行文件路径。但是which并不能可靠地告诉我们 Bash 最终会采用哪一种命令实现。例如type echo通常会提示echo is a shell builtin说明 Bash 中存在内建的echo命令。使用type -a echo还能查看同名命令的不同实现。因此在 Bash 环境中想了解一个命令到底是内建命令、外部命令还是其他类型我更倾向于使用type。4 Linux 文本处理工具今天主要接触了四个重要工具工具作用grep查找文本内容sed对文本进行替换和编辑find根据条件查找文件或目录awk对结构化文本进行字段处理这几个工具可以配合使用完成许多实际任务。4.1 grep 文本搜索基本语法grep pattern filename例如grep Linux hello.txt假设文件内容为Hello Linux I like Python Linux is powerful MyLinuxSystem那么输出Hello Linux Linux is powerful MyLinuxSystem这里需要注意grep 默认查找包含匹配内容的行而不是要求整行与搜索字符串完全相等。另外默认情况下它会区分大小写。常用命令grep -i Linux hello.txt忽略大小写。grep -n Linux hello.txt显示匹配行的行号。grep -w Linux hello.txt匹配完整单词。grep -rn Linux .递归搜索当前目录及其子目录并显示行号。4.2 正则表达式与 grepgrep不仅能查找固定文本还能使用正则表达式描述匹配规则。例如grep ^Linux hello.txt匹配以Linux开头的行。grep Linux$ hello.txt匹配以Linux结尾的行。grep Linux.*error hello.txt匹配同一行中先出现Linux随后出现error的文本。这里^匹配行首。$匹配行尾。.匹配任意单个字符。*表示前面的表达式重复零次或多次。.*组合起来表示任意数量的字符。这里需要特别区分正则表达式负责描述匹配规则grep 的选项负责控制搜索行为、匹配方式和输出形式。例如grep -rin ^Linux.*error$ .其中-r递归搜索。-i忽略大小写。-n输出行号。^Linux.*error$正则表达式。这两类规则相互配合就可以构成复杂的文本搜索命令。4.3 sed 文本替换sed是 Stream Editor 的缩写。常见语法sed s/旧内容/新内容/g filename例如sed s/Linux/Ubuntu/g hello.txt其中s表示替换。第一个部分是匹配规则。第二个部分是替换内容。g表示替换每一行中的所有匹配项。假设一行文本为Linux and Linux and Linux不加gsed s/Linux/Ubuntu/ hello.txt结果为Ubuntu and Linux and Linux加上gsed s/Linux/Ubuntu/g hello.txt结果为Ubuntu and Ubuntu and Ubuntu还有一个重要参数sed -i s/Linux/Ubuntu/g hello.txt-i表示直接修改原文件。为了防止误操作在 GNU/Linux 中可以这样创建备份sed -i.bak s/Linux/Ubuntu/g hello.txt修改文件时最好先不加-i检查替换结果。4.4 find 文件查找基本语法find 搜索路径 匹配条件 执行操作例如find . -type f -name *.cpp含义.当前目录。-type f普通文件。-name *.cpp文件名以.cpp结尾。它会递归搜索当前目录及其子目录。常见条件包括参数作用-name按文件名查找-iname忽略大小写查找文件名-type f查找普通文件-type d查找目录-size按文件大小查找-mtime按修改时间查找例如find ~/Downloads -type f -name *.zip -mtime 30查找下载目录中符合修改时间条件的 ZIP 文件。需要注意GNU find 对-mtime使用以 24 小时为单位的向下取整计数因此30实际要求至少经过 31 个完整的 24 小时周期。再例如find ~ -type f -size 100M查找家目录中比较大的普通文件。4.5 find 中的 exec 与占位符这是今天我重点思考的语法之一。例如find . -name *.cpp -exec ls -l {} \;拆开来看部分含义find .从当前目录搜索-name *.cpp搜索对应名称-exec对匹配结果执行命令ls -l显示文件详细信息{}当前匹配项目的路径\;结束 exec 命令部分其中最关键的是{}。假设找到两个文件./main.cpp ./test.cpp那么find就相当于分别执行ls -l ./main.cpp ls -l ./test.cpp也就是说{}是 find 的 exec 操作识别的占位符用来代表当前找到的文件路径。它不是 Shell 中可以随意使用的普通变量。为什么;前面有\因为 Shell 本身也会解释分号所以需要通过转义让分号传递给find用来标记-exec的结束。将find与grep结合find . -type f -name *.cpp -exec grep -l TODO {} \;就可以查找当前目录下所有包含TODO的 C 源文件。这里还有一个重要区别find -name *.cpp使用的是通配符模式而grep默认使用基本正则表达式。虽然都是匹配规则但它们并不是同一种语法。4.6 awk 文本处理awk可以按字段处理文本。例如假设文件内容为Alice 20 Bob 21 Charlie 19执行awk {print $2} data.txt输出20 21 19这里$2表示每行的第二个字段并不是 Shell 的位置参数。如果是逗号分隔的数据awk -F, {print $2} data.csv其中-F,表示将逗号作为字段分隔符。相比grep和sedawk更适合处理具有字段结构的文本数据。目前我只是初步了解后续还需要结合具体数据处理任务深入学习。5 管道与输入输出重定向5.1 Linux 的三种标准流Linux 程序通常会使用三种标准流编号名称用途0stdin标准输入1stdout标准输出2stderr标准错误输出普通情况下标准输入通常来自终端。标准输出通常显示在终端。标准错误输出通常也显示在终端。但它们都可以通过重定向改变连接位置。5.2 输出重定向例如echo Hello output.txt将内容写入output.txt。其中表示重定向标准输出默认覆盖原文件。如果希望追加内容echo Linux output.txt使用2则可以重定向标准错误。例如command 2 error.txt而command output.txt 21表示将标准输出与标准错误都写入output.txt。这里的21表示让标准错误输出指向当前标准输出所指向的位置。需要注意重定向的顺序可能影响最终结果。5.3 管道符管道使用|它能够将前一个命令的标准输出连接到后一个命令的标准输入。例如cat hello.txt | grep Linux工作过程cat读取文件。将内容通过管道传递给grep。grep输出匹配的行。还可以继续组合grep -rn TODO . | head -n 10表示只查看前十条搜索结果。我认为管道体现了 Shell 的重要设计思想让不同工具分别完成自己擅长的工作再把它们连接起来。6 Shell 条件判断6.1 if 的基本语法Bash 的条件判断可以写成if command; then echo 成功 else echo 失败 fi其中if开始判断。then条件成功时执行。else条件不成立时执行。fi结束条件语句。这里最重要的是理解Shell 的if判断的是命令退出状态而不是简单地判断命令输出的数字。6.2 为什么返回 0 才执行这是今天非常容易与 C 混淆的地方。对于 Shell退出状态含义0命令成功非0命令未成功因此if [ 1 -eq 1 ]; then echo 条件成立 fi能够正常输出条件成立而在 C 中if (0) { cout Hello; }条件为假。这是因为二者判断的对象不同C 根据表达式的值进行布尔判断。Shell 的if根据命令的退出状态判断成功与否。可以通过$?观察上一条命令的退出状态[ 1 -eq 1 ] echo $?输出0而[ 1 -eq 2 ] echo $?输出1但不能简单认为所有非零状态都表示逻辑条件不成立有些命令会使用非零状态表示运行错误。6.3 test 与方括号例如test -f main.cpp用于判断main.cpp是否存在且为普通文件。它也可以写成[ -f main.cpp ]这里有一个十分重要的细节[在 Bash 中实际上是一个内建命令而不仅仅是普通的括号符号。因此必须写[ -f main.cpp ]而不是[-f main.cpp]因为[、-f、main.cpp和]需要成为独立的命令参数。常见判断条件条件含义-f普通文件存在-d目录存在-e路径存在-eq整数相等-gt整数大于-lt整数小于例如if [ -f main.cpp ]; then echo 文件存在 fi6.4 双方括号Bash 还提供了增强条件判断语法[[ -f main.cpp ]]它相比[ ]在变量展开、字符串和模式匹配等方面更加方便。例如nameLinux if [[ $name Linux ]]; then echo 匹配成功 fi需要注意[[ ]]是 Bash 等部分 Shell 支持的语法不属于所有/bin/sh都保证支持的标准语法。7 循环与算术运算7.1 for 和 while 循环Bash 中可以使用for遍历一组值for i in 1 2 3; do echo $i done输出1 2 3也可以使用while循环i1 while ((i 3)); do echo $i i$((i1)) done在这里while会根据算术条件是否成立决定是否继续执行。理解了 Shell 的if之后理解while就比较容易因为它同样依赖命令的退出状态。7.2 两种算术语法的区别Bash 中有两种容易混淆的语法(( ))以及$(( ))它们都能进行整数计算但用途不同。第一种算术命令a5 ((a)) echo $a输出6((a))执行一次算术运算但不会直接输出计算结果。第二种算术展开a5 echo $((a1))输出6这里$((a1))会将算术结果展开为一个值再传给echo。二者区别如下语法作用((表达式))执行算术命令$((表达式))把计算结果展开到当前位置值得注意的是算术展开也可以包含自增或赋值操作。例如a5 echo $((a)) echo $a输出5 6这是因为a是后置自增表达式产生的是自增前的值而变量本身已经加一。7.3 算术命令与退出状态的陷阱一个容易忽略的细节是((表达式))不仅进行计算它还会产生命令退出状态。规则是表达式计算结果非零退出状态为0。表达式计算结果为零退出状态为1。例如a0 ((a))这里虽然a最终变成了1但由于后置自增表达式的值是修改之前的0所以算术命令返回状态码1。如果脚本中启用了set -e并且该算术命令位于受-e影响的位置脚本就可能意外退出。因此在单纯需要增加计数器时我更倾向于采用a$((a1))因为这是一条赋值命令不会由于计算结果为零而触发同样的算术命令退出状态问题。8 完整 Bash 脚本解析课程还展示了一个更复杂的脚本在 CPU 高负载条件下反复执行测试程序直到发现失败并记录结果。这个示例将多个 Shell 知识点组合在了一起。8.1 Shebang 和严格模式脚本通常以#!/bin/bash开头。这叫 Shebang用来指定直接执行脚本时所使用的解释器。脚本还可能包含set -euo pipefail其中-e符合相应规则的命令失败时退出脚本。-u使用未定义变量时报错。pipefail让管道退出状态能够反映管道中失败的命令。它们可以减少脚本中的一些隐蔽错误但并不能代替完整的错误处理。8.2 后台运行与进程 ID例如stress --cpu 8 这里stress是 CPU 压力测试工具。--cpu 8表示创建八个 CPU 压力工作进程。表示让命令在后台运行。接下来STRESS_PID$!其中$!表示最近启动的后台作业对应的 PID。后面可以使用kill $STRESS_PID向相应进程发送终止信号。需要注意后台压力测试会持续占用计算资源实际使用时必须考虑进程清理不能直接把它当成普通的学习演示脚本随意运行。8.3 命令替换与日志例如LOGFILEtest_runs_$(date %s).log这里$(date %s)属于命令替换。Bash 会先执行括号里的命令再将输出结果放到对应位置。date %s输出 Unix 时间戳所以可以生成一个带有时间信息的日志文件名。这里还需要区分$RUN # 读取变量 $(date %s) # 命令替换 $((RUN1)) # 算术展开 $! # 最近后台作业的 PID $? # 上一条命令的退出状态虽然它们都以$开头但实际用途不同。8.4 自动化测试的执行逻辑课程中的核心循环类似RUN1 while cargo test my_test $LOGFILE 21; do echo Run $RUN passed ((RUN)) done这里的cargo test my_test是 Rust 项目中运行测试的命令。执行逻辑是运行一次测试。将标准输出和标准错误写入日志。如果测试成功执行循环体。输出成功信息更新测试次数。再次运行测试。测试失败后退出循环。这里有一个细节 $LOGFILE每轮都会覆盖原日志文件。因此脚本通常保留的是最后一次测试的输出而不是每一次测试的完整历史。对于实际项目还需要考虑脚本异常退出时如何清理后台进程。可以通过trap注册退出时的清理动作。从这个例子中我第一次比较直观地看到原来 Shell 可以将进程管理、循环控制、错误处理和日志系统整合成一个自动化工作流程。9 实践编写一个 C 文件搜索脚本学完这些知识后我打算尝试将 Shell 应用到 C 学习过程中。这次先实现一个简单功能自动搜索指定目录下的所有.cpp文件并将结果保存到日志中。创建文件search_cpp.sh写入以下代码#!/usr/bin/env bash set -euo pipefail # 获取搜索目录默认为当前目录 TARGET${1:-.} # 创建带时间戳的日志文件 LOGFILEcpp_files_$(date %Y%m%d_%H%M%S).txt # 搜索 C 源文件并保存结果 find $TARGET -type f -name *.cpp -print $LOGFILE echo 搜索完成 echo 结果保存在$LOGFILE # 显示最后五条搜索结果 tail -n 5 $LOGFILE可以通过bash search_cpp.sh .运行脚本。其中TARGET${1:-.}表示如果提供第一个位置参数就使用该参数。如果第一个参数未设置或为空就使用当前目录.。这个小脚本虽然简单却结合了今天的多个知识点变量定义和展开相对路径find文件查找命令替换输出重定向tail日志查看基础错误处理后续还可以增加更多功能比如搜索项目中包含TODO的代码文件。统计项目的代码文件数量。自动编译 C 程序。将编译错误保存到日志中。编译完成后自动运行测试。我认为这些都可以作为学习 Shell 后比较有价值的练习方向。10 易错知识点速查表这是我认为整篇笔记中最值得反复查看的部分。知识点容易混淆的地方正确理解/根目录还是分隔符位于路径开头时标识根目录路径中也用来分隔组成部分..为什么能返回上一级它是指向父目录的特殊路径组成部分../..为什么返回两级两个..由/分隔$PATH是一个命令吗它表示读取 PATH 环境变量which能确定实际执行的命令吗不一定Bash 中可使用type检查grep匹配整个文件吗默认逐行搜索并输出匹配行grep -w和普通 grep 一样吗要求满足单词边界条件sed中的g是替换整个文件吗替换每一行的所有匹配项sed -i会不会修改原文件会find -name使用正则表达式吗使用通配符模式find -exec有什么作用对匹配结果执行命令{}是 Shell 变量吗不是是 find exec 使用的占位符\;为什么有反斜杠避免分号被 Shell 提前解释if为什么 0 才表示真Shell 根据命令成功状态判断[ ]为什么必须有空格[是命令需要正确分隔参数[[ ]]与[ ]一样吗是 Bash 支持的增强条件语法(( ))为什么使用双括号Bash 的算术命令语法$(( ))与(( ))有什么区别它是算术展开会产生计算结果$?表示什么上一条命令的退出状态$!表示什么最近后台作业的 PID$(...)表示什么执行命令并获取其输出表示什么将命令放到后台执行表示什么重定向输出默认覆盖表示什么追加输出21表示什么将标准错误指向当前标准输出的位置表示什么将前一个命令的标准输出连接到后一个命令的标准输入这张表以后可以直接作为 Shell 复习时的快速检索资料。11 学习总结与下一步计划今天学习完 Missing Semester 的 Shell 课程之后我对命令行的理解有了明显变化。以前我认为 Shell 的学习主要是记忆各种命令和参数。但现在我发现真正重要的不是记住多少命令而是理解 Shell 如何解释这些命令以及如何把它们组合起来解决实际问题。比如find . -type f -name *.cpp -exec grep -l TODO {} \;如果只是记住这条命令那么过一段时间可能就会忘记。但如果理解find如何遍历目录。-type f如何筛选普通文件。-name如何按文件名匹配。-exec如何调用其他命令。{}如何替换成查找到的路径。grep -l如何输出匹配文件名。那么即使忘记了完整命令也可以根据需求重新组合出来。这也让我意识到学习计算机知识时理解底层执行逻辑通常比单纯记忆语法更加重要。尤其是对于正在学习 C 的我来说Shell 与 C 有一些相似的程序设计思想例如条件判断、循环、变量和算术运算。但两者在表达方式、命令执行模型和退出状态等方面也存在明显差别。这些差别恰恰是学习过程中值得深入思考的地方。下一步学习计划继续深入理解 Shell 的通配符、引用规则和正则表达式。进一步练习grep、sed、find和awk。掌握管道与重定向实现多命令组合处理。学习更完整的 Shell 脚本编写方法。尝试将 Shell 应用到 C 项目的编译、运行与测试流程中。我希望通过后续的学习逐渐将命令行从一个需要查阅语法才能使用的工具变成自己日常开发工作流程的一部分。今天最大的收获是学习 Shell不只是学习如何使用命令更是在学习如何通过程序组合实现自动化。参考资料Missing Semester 2026 — Shell 入门中文GNU Bash Reference ManualGNU Findutils ManualGNU Grep ManualShellCheck — Shell 脚本静态检查工具本文为个人学习笔记记录 Missing Semester 2026 Shell 课程中的核心知识和学习过程中遇到的疑问。代码示例主要用于理解基础语法具体行为可能随 Shell、操作系统和工具版本有所不同。