Lecture 2_Shell Tolls and Scripting
Overview
本视频(Missing Semester 课程的第二讲)的核心论题是,Shell(特别是 Bash)远不止是一个简单的命令执行器,它本身就是一个功能完备且异常强大的编程环境。讲座的结论是,通过掌握 Shell 脚本的核心概念(如变量、控制流、函数)以及学会使用一系列高效的命令行工具(用于查找、搜索和导航),开发者可以将大量重复性的手动任务自动化,从而极大地提升工作效率和能力。
按照主题来梳理
第一节:Shell 脚本编程——释放 Bash 的真正力量
大多数开发者将 Shell 视为执行单个命令的地方,但它的真正潜力在于其“脚本”能力。本节深入探讨了将 Shell (Bash) 作为一种编程语言来使用的核心概念,这是实现自动化的基石。
-
变量(Variables)
-
字符串(Strings)与引号(Quoting)
-
函数与参数(Functions & Arguments)
-
Bash 允许你定义自己的函数,这对于重用代码块非常有用。一个贯穿讲座的例子是
mcd(Make and Change Directory) 函数 [03:26],它实现了一个常见的模式:创建一个新目录,然后立刻cd进去。 -
函数体可以访问传入的参数。在 Bash 中,这些参数是通过一系列特殊的“美元变量”来访问的:
-
$1,$2,$3…:分别代表第一个、第二个、第三个参数 [04:02]。在mcd示例中,mkdir $1和cd $1使用了第一个参数(即你希望创建的目录名)。 -
$0:代表脚本本身的名称 [05:27]。 -
$#:代表传递给脚本或函数的参数总数 [12:55]。 -
$@:代表所有参数的列表 [13:14],通常用于for循环中,以便迭代所有传入的参数。 -
$$:代表当前 Shell 脚本的进程 ID (Process ID) [13:01]。 -
$_:一个非常方便的变量,代表前一个命令的最后一个参数 [05:49]。例如,你可以运行mkdir my_new_dir,然后运行cd $_,它会自动扩展为cd my_new_dir。
-
-
-
退出码与控制流(Exit Codes & Control Flow)
-
这是 Shell 编程的核心心智模型。在 UNIX 哲学中,每个程序在退出时都会返回一个“退出码”(Exit Code)[07:28],这是一个整数。
-
0(零) 代表成功 [07:44]。 -
任何非零值(通常是
1)代表失败或错误 [08:07]。 -
你可以通过特殊变量
$?[05:40] 来获取前一个命令的退出码。例如,运行grep "fubar" mcd.sh,如果找到了字符串,$?会是0;如果没找到,$?会是1[08:07]。 -
这个机制是 Shell 中所有控制流的基础。Bash 使用两个主要的逻辑运算符来利用退出码:
&&(AND) 和||(OR)。 -
command1 && command2[09:32]:command2仅在command1成功(即退出码为0)时才会执行。 -
command1 || command2[08:46]:command2仅在command1失败(即退出码为非0)时才会执行。 -
这提供了一种极其强大和简洁的逻辑控制方式。例如,
cd some_directory || exit 1[26:05] 是一个非常健壮的模式,意思是“尝试进入那个目录,如果失败了,就立刻退出脚本”,这可以防止脚本在错误的目录中继续执行。 -
视频中的
example.sh脚本 [12:17] 演示了更复杂的if语句,它同样是基于退出码工作的:if [ $? -ne 0 ][15:24] 就是在检查“上一个命令是否失败了?”
-
-
输入/输出重定向 (I/O Redirection)
-
Shell 脚本经常需要处理命令的输出。有时你关心输出,有时你不关心。
-
视频中的
example.sh脚本使用grep fubar $file > /dev/null 2>&1[14:34] (或类似语法) 来运行一个命令,但完全不关心它的屏幕输出。 -
> /dev/null的意思是将标准输出(Standard Output)重定向到/dev/null(一个“黑洞”设备,会丢弃所有写入的数据)。 -
2>&1的意思是将标准错误(Standard Error,文件描述符为 2)也重定向到标准输出(文件描述符为 1)去的地方——也就是/dev/null。 -
这样做的唯一目的是为了获取
grep命令的退出码($?) [14:21],以便知道fubar是否存在于文件中,而不会让grep的任何输出污染屏幕。
-
-
命令替换与进程替换 (Substitution)
-
命令替换 (Command Substitution):
$(...)[10:16](或反引号``)。这允许你将一个命令的输出(一个字符串)“嵌入”到另一个命令中,或赋值给一个变量。 -
进程替换 (Process Substitution):
< (...)[11:13]。这是一个更高级但也极其有用的功能。它不会替换为字符串,而是替换为一个临时文件路径(技术上是一个文件描述符),该文件的内容是括号内命令的输出。 -
这对于那些期望接收文件路径作为参数、而不是从标准输入接收数据的命令(如
diff)非常有用。 -
示例:
diff <(ls foo) <(ls bar)[21:53]。这个命令会分别执行ls foo和ls bar,将它们的输出存入两个临时文件,然后diff会比较这两个文件的内容,告诉你foo目录和bar目录的文件列表有何不同。
-
-
Globbing (文件名扩展)
-
Shebang (
#!) 与脚本可移植性-
当你编写一个脚本(无论是 Bash、Python 还是其他语言)并希望它能像普通命令一样被执行时,你需要在文件的第一行添加一个 “Shebang”。
-
例如
#!/bin/python[23:23]。这行告诉操作系统:“不要用 Shell 来执行这个文件,请用/bin/python这个解释器来执行它”。 -
然而,不同系统上的 Python 路径可能不同(可能是
/usr/bin/python或/usr/local/bin/python)。 -
更健壮、可移植性更高的方法是使用
#!/usr/bin/env python[24:16]。env是一个标准程序,它会根据你的PATH环境变量 [24:40] 自动去查找python解释器所在的位置。这使得你的脚本在几乎所有 UNIX 类系统上都能正确运行。
-
-
脚本检查 (Linting)
-
Bash 语法非常古怪且容易出错(比如空格问题)。
-
讲座推荐了一个名为
shellcheck[25:29] 的工具。它是一个静态分析器(linter),可以检查你的 Bash 脚本,找出常见的语法错误、逻辑陷阱和不良实践,并给出修改建议。
-
第二节:Shell 工具箱——高效开发者的利器
掌握了脚本编程后,讲座的第二部分转向了日常工作中用于提高效率的“工具”。这些工具的核心思想是:你永远不应该手动去做计算机可以为你做的重复性工作。
-
查找帮助 (Getting Help)
-
查找文件(按名称、路径或元数据)
-
find[31:16]:这是最经典、功能最强大的文件查找工具。它会递归地遍历目录结构。 -
find最强大的地方在于它的-exec动作 [33:18]:find . -name "*.tmp" -exec rm {} \;。这个命令会找到所有.tmp文件,并对每一个找到的结果({})执行rm命令。这是将“查找”和“操作”结合起来的典范。 -
fd[34:25]:一个现代的find替代品。它通常更快,语法更简洁(例如fd "src"),并且默认会尊重.gitignore规则,这在代码库中查找时非常方便。 -
locate[35:23]:它不直接搜索磁盘,而是搜索一个预先建立的“索引”数据库。因此locate的速度极快,但它可能找不到你刚刚创建的文件,因为它依赖于updatedb[36:05] 进程(通常每晚运行)来更新索引。
-
-
查找内容(在文件中搜索文本)
-
grep(Global Regular Expression Print) [36:24]:UNIX 的标准文本搜索工具。 -
grep -R "fubar" .[36:43]:-R标志让grep递归地(Recursive)搜索当前目录(.)下的所有文件,查找包含 “fubar” 字符串的行。 -
ripgrep(rg) [37:52]:一个现代的grep替代品。它被认为是目前最快的文本搜索工具之一。 -
rg的优点包括:默认递归、默认尊重.gitignore[38:08]、彩色高亮输出、更快的速度。 -
rg有很多有用的标志,例如-C 5(--context=5)[38:26] 会显示匹配行以及它“上下的 5 行”上下文,这在阅读代码时非常有用。 -
它甚至可以做反向搜索,例如
rg -L "shebang"[39:28](-L对应--files-without-match)会列出所有不包含 “shebang” 字符串的文件。
-
-
查找历史(复用过去的命令)
-
上箭头 (
Up-Arrow) [41:21]:最基本的方式,效率低下。 -
history[41:37]:显示你所有的历史命令。一个常见的用法是history | grep "docker"[42:00],用grep来搜索你运行过的所有docker命令。 -
Ctrl+R[42:22]:(reverse-i-search) 这是一个内置的、交互式的“反向搜索”功能。按下Ctrl+R后,你可以开始输入命令的任何部分(例如convert),它会立刻从历史记录中找到最近的匹配项。 -
fzf(Fuzzy Finder) [42:52]:一个改变游戏规则的工具。它可以与Ctrl+R绑定 [43:20],将标准的Ctrl+R替换为一个极其强大的“模糊查找”界面。你只需要输入几个零散的字符(例如cvrt icon),它就能“模糊”地匹配到你想要的命令(如convert image.png icon.ico),即使你输入的字符是无序的 [43:47]。 -
历史子字符串搜索 (History Substring Search) [44:08]:许多 Shell(如 Zsh)支持的另一个功能。你只需输入命令的前几个字符(例如
git c),然后按“上箭头”,Shell 会自动只在你输入的前缀(git c)匹配的历史记录中循环,(例如git commit,git checkout)。
-
-
查找目录(高效导航)
框架 & 心智模型 (Framework & Mindset)
框架一:将 Shell 视为一个组合式的编程环境
视频中展示的第一个核心心智模型是停止将 Shell 视为孤立命令的执行者,而是将其视为一个用于“组合”的编程环境。UNIX 哲学的核心是“做一件事并把它做好”(Do One Thing and Do It Well)。grep 只负责搜索,find 只负责查找,sort 只负责排序。Shell 的魔力在于它提供了将这些简单工具“粘合”在一起的机制,以创建出极其复杂的自动化工作流。
这个框架的粘合剂包括:
-
管道 (
|):这是最主要的粘合剂。它将一个命令的“标准输出”连接到下一个命令的“标准输入”。history | grep "docker" | wc -l是一个完美示例:history的完整列表被“导入”grep,grep过滤后的结果被“导入”wc -l(字数统计)以计算行数。你动态地创建了一个“统计 docker 命令数量”的新程序。 -
退出码 (
$?) 与逻辑运算符 (&&,||):这提供了“逻辑”和“控制”。它们是 Shell 的if-then-else。你不必总是编写一个完整的.sh脚本文件。git pull && npm install[09:32](一个常见的例子)就是一个微型脚本,它在命令行上定义了逻辑:“只有在git pull成功后,才运行npm install”。cd my-dir || echo "Directory not found"[08:46] 也是如此:“尝试cd,如果失败了,就打印一条错误消息”。 -
变量与替换 (
$foo,$(...)):这提供了“状态”和“数据传递”。通过foo=$(pwd)[10:24],你将一个命令的动态输出(当前目录)捕获到了一个“内存”(变量foo)中,以便稍后在脚本的其他地方(例如echo "Building in $foo")重用它。
当你内化了这种思维,你就不会再害怕复杂的命令行。相反,你会开始主动地将你的任务分解为“我需要什么数据(find)?”、“我需要如何处理它(grep, sed)?”、“我需要如何组合它们(|)?”以及“我需要什么逻辑(&&)?”。
框架二:高效开发者的“查找与自动化”心智模型
视频的第二部分(关于工具)提供了一个隐含的框架,即如何解决“我正在手动做一件重复的事”。这个心智模型的目标是:将你的时间从“执行”任务转变为“自动化”任务。
这个框架可以被看作一个分层递进的查找策略:
-
查找“如何做”:
man与tldr -
查找“在哪里”:
find,fd,locate -
查找“它说了什么”:
grep与rg -
查找“我以前做过”:
Ctrl+R与fzf
最终的自动化(The Final Step):
这个框架的最后一步是,当你发现自己组合了上述工具来完成一个任务(例如,find 某些文件,然后 grep 它们的内容,最后用一个 sed 命令来修改它们)——并且你发现自己第二次在做这件事时——你就应该将这个命令流封装到一个可重用的 Shell 脚本(使用第一节中的技术)中,并将其放入你的 PATH。
这就是从一个“Shell 用户”转变为一个“Shell 程序员”的完整闭环。

