Lecture 1_The Shell
Overview
这堂课(视频)是“计算机教育中缺失的一课 (The Missing Semester of Your CS)”系列的第一讲。它的核心论题是:计算机专业的学生虽然擅长使用计算机来执行重复性任务和构建软件,但却常常忽视了那些能极大提升自己开发效率的工具 [00:34]。本课程旨在弥补这一差距,向学生展示如何充分利用已有的工具、学习新工具,并将它们组合起来,以更高效的方式在日常学习、研究和工作中使用计算机 [01:05]。本讲作为开篇,结论是为后续所有高级工具的学习打下基础,详细介绍最核心的交互界面——Shell(命令行外壳),包括它的工作原理、文件系统导航、权限管理,以及 Shell 最强大的特性:通过“管道”将简单程序组合成复杂的工作流。
按照主题来梳理
1. 课程介绍:我们为什么需要“缺失的一课”?
本课程的开设源于讲师们(Anish, Jose 和 John)在 MIT 担任助教时的一个观察:绝大多数计算机科学专业的学生,尽管深知计算机在自动化和处理重复任务上的威力,却很少将这种能力应用到自己身上 [00:26]。他们会编写复杂的软件,但自己的开发流程却可能效率低下。
这门课的目的就是为了解决这个问题,它尝试为你展示一系列能极大提升你日常效率的工具。课程的目标分为三个层面:
-
精通已知工具:教会你如何最大限度地利用你已经了解的工具。
-
学习未知工具:向你介绍你可能闻所未闻的强大新工具。
-
组合工具:展示如何将这些工具组合起来,实现远超你想象的强大功能 [01:19]。
课程结构与安排:
-
形式:课程由 11 节一小时的讲座组成,每一节都围绕一个特定主题 [01:31]。
-
独立性:大部分讲座主题是相互独立的,你可以选择你感兴趣的收听。但课程会默认你跟上了进度,例如,后续课程不会再重复教授本节的
bash基础知识 [01:50]。 -
资源:所有的课程讲义和视频录像都会发布在课程网站上 [02:03]。
-
答疑 (Office Hours):每节课后,讲师们都会在 Stata Center (32 号楼) 9 层的休息室提供答疑时间,你可以在那里提问或尝试课程的练习 [02:44]。
课程范围与期望:
由于时间有限(总共只有 11 个小时),课程无法深入到每个工具的所有细节 [03:10]。课程的重点是高亮展示 (highlight) 那些有趣的工具和它们有趣的使用方式,而不是详尽的深入研究。
本节课将从我们与计算机交互的最核心的界面——Shell(命令行外壳)开始。我们将涵盖基础知识,并为后续课程(如 Shell 脚本、编辑器、数据处理等)快速打好基础 [03:45]。
2. Shell 基础:进入文本驱动的世界
本讲的主题是 Shell(命令行外壳)。这是当你跳出图形用户界面 (GUI) 限制后,与计算机交互的主要方式 [04:10]。
为什么选择 Shell 而非图形界面 (GUI)?
-
图形界面(例如你用鼠标点击的按钮、拖动的滑块)在功能上是受限的。你只能执行那些被设计者预先“制作成按钮”的功能 [04:26]。
-
相比之下,文本工具被设计为可组合的 (composable) 和可自动化的 (automatable) [04:31]。你可以将多个简单的文本工具串联起来,完成复杂任务,或者编写脚本让它们自动执行。这是 Shell 强大力量的根源。
Shell 的形态:
-
Windows: 常见的 Shell 是
PowerShell。 -
Linux: 有各种各样的 Shell,最常见的是
bash(全称是 Born Again Shell)。本课程将主要使用bash[05:08]。 -
macOS: 默认的
Terminal(终端) 应用打开的也是bash(尽管可能是较老的版本)或zsh(功能与bash相近)。
当你打开一个终端时,你看到的那个闪烁的光标和它前面的文字,就是 Shell 提示符 (Shell Prompt) [06:03]。它通常显示你的用户名@机器名 当前路径 $。这个提示符是可以高度自定义的,它在“提示”你输入命令。
Shell 的核心工作:执行程序
Shell 的基本工作模式是:你输入一个命令 (program),后面可以跟上零个或多个参数 (arguments) [06:52]。
-
示例 1:无参数命令
1
date
输入
date并回车,Shell 会执行date程序,该程序打印当前的日期和时间 [07:05]。 -
示例 2:带参数命令
1
echo hello
echo是一个程序,它的功能是“回显”你给它的所有参数。这里hello是参数,所以它会在屏幕上打印hello[07:17]。 -
参数如何界定:空格与引号
Shell 默认使用空白字符 (whitespace)(如空格、Tab键)来分隔参数。
-
echo hello world会被识别为两个参数:hello和world。 -
问题:如果你想让
hello world成为一个单独的参数怎么办? -
方案 1:引号。你可以使用双引号 (
") 或单引号 (') 将参数括起来。1
echo "hello world"
这样,
echo程序只会收到一个参数,即字符串hello world[07:42]。 -
方案 2:转义字符。你可以在空格前使用反斜杠 (
\) 来“转义”这个空格,告诉 Shell 这个空格只是一个普通字符,而不是分隔符。1
echo hello\ world
-
重要性:理解这一点至关重要。一个常见的错误是创建带空格的目录:
mkdir my photos
这不会创建一个名为 “my photos” 的目录,而是会创建两个目录,分别名为 “my” 和 “photos” [08:29]。正确的做法是 mkdir “my photos”。
-
3. Shell 如何找到程序:PATH 环境变量与文件路径
你可能会想:当我输入 date 或 echo 时,Shell 是如何“知道”这些程序是什么、在哪里,以及如何运行它们的?
答案是,这些程序是存在于你计算机文件系统上的可执行文件。Shell 通过一个名为 PATH 的环境变量 (Environment Variable) 来查找它们 [09:20]。
Shell 作为编程语言:
在深入 PATH 之前,你需要知道:Shell 本身就是一种编程语言 [09:35]。你可以在 Shell 提示符下定义变量、使用循环 (for / while)、条件判断 (if),这些我们将在下一讲(Shell 脚本)中详细介绍。
环境变量 PATH:
环境变量是 Shell 启动时就自动设置好的一系列变量(例如你的用户名 USER、你的主目录 HOME)。PATH 是其中最关键的一个。
-
你可以使用
echo命令来查看PATH变量的值(变量名前需要加$):1
echo $PATH
-
你会看到一长串由冒号 (:) 分隔的目录列表,例如:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
-
查找机制:当你输入一个命令(如
echo)时,Shell 会:-
依次查看
PATH变量中的每一个目录。 -
检查该目录中是否存在一个与你输入的命令同名的可执行文件。
-
一旦找到第一个匹配的,Shell 就会停止搜索并执行该文件 [10:37]。
-
-
which命令:如果你想知道 Shell 具体会执行哪一个文件,可以使用which命令:1
which echo
它可能会返回
/usr/bin/echo,告诉你echo命令对应的程序文件路径 [10:59]。
理解路径 (Path):
-
路径:就是文件在文件系统中的位置。
-
Linux/macOS: 使用正斜杠 (
/) 作为分隔符。所有路径都始于一个唯一的“根”目录 (/) [11:21]。 -
Windows: 使用反斜杠 (
\) 作为分隔符。并且有多个“根”,例如C:\或D:\[11:44]。
绝对路径 (Absolute Path) vs. 相对路径 (Relative Path) [12:19]
-
绝对路径:从“根”目录开始的完整路径,它唯一确定了一个文件的位置。例如:
/usr/bin/echo。 -
相对路径:从“当前所在位置”开始的路径。
4. 文件系统导航与操作
为了使用相对路径,我们首先需要知道“当前所在位置”以及如何改变它。
核心导航命令:
-
pwd(Print Working Directory):打印“当前工作目录”,即你现在所处的位置 [12:49]。1
2pwd
# 输出: /home/john/dev/missing-semester -
cd(Change Directory):改变你的当前工作目录 [13:21]。
快捷导航技巧:
-
~(Tilde):波浪号~是一个快捷方式,始终代表你的主目录 (Home Directory),例如/home/john[17:14]。-
cd ~或只输入cd:快速返回你的主目录。 -
cd ~/dev/project:从任何位置快速切换到你主目录下的dev/project。
-
-
cd -(Dash):切换到你上一次所在的目录 [17:38]。这在两个目录间频繁切换时非常有用。
查看与管理文件:
-
ls(List):列出当前目录下的文件和目录 [16:23]。ls ..:你也可以给ls一个路径,让它列出其他目录的内容。
-
获取帮助:
-
文件操作命令:
-
mv [source] [destination]:移动 (Move) 或重命名 (Rename) 文件。-
mv old_name.txt new_name.txt(重命名) -
mv file.txt ../backup/(移动) [24:29]
-
-
cp [source] [destination]:复制 (Copy) 文件 [25:16]。 -
rm [file]:移除 (Remove) 文件。注意:在 Linux/macOS 上,rm默认是永久删除,没有回收站 [25:44]。 -
mkdir [directory_name]:创建 (Make) 目录 [26:28]。 -
rmdir [directory_name]:移除 (Remove) 目录,但它只允许你删除空目录(这是一种安全机制) [26:10]。 -
rm -r [directory_name]:如果你想删除一个非空目录及其中的所有内容,需要使用rm的-r(Recursive,递归) 参数。这是一个非常危险的命令,使用前请三思。 [26:06]
-
5. 深入理解文件权限:ls -l 的秘密
当你使用 ls 的 -l(long,长格式)参数时,你会看到更详细的信息:
1 | ls -l |
这串输出包含了丰富的信息,最重要的是开头的权限字符串,例如 drwxr-xr-x [20:16]。
-
第一个字符:文件类型。
-
d:代表这是一个目录 (Directory) [20:05]。 -
-:代表这是一个普通文件 (File)。
-
-
后续 9 个字符:权限信息,每 3 个一组。
-
第 1 组 (rwx):文件所有者 (Owner)(例子中是
john)的权限。 -
第 2 组 (r-x):文件所属组 (Group)(例子中是
john组)的权限。 -
第 3 组 (r-x):其他人 (Others)(既不是所有者,也不在所属组)的权限。
-
权限的含义 (r, w, x):
r、w、x 的含义在文件和目录上是不同的。
| 权限 | 对 文件 意味着… [21:34] | 对 目录 意味着… [22:15] |
|---|---|---|
r (Read) |
读取:可以读取文件的内容。 | 读取:可以列出 (ls) 目录中的内容(即看到有哪些文件)。 |
w (Write) |
写入:可以修改或覆盖文件的内容。 | 写入:可以在该目录中创建、删除、重命名文件。 |
x (Execute) |
执行:可以将该文件作为程序来运行。 | 执行:可以进入 (cd) 该目录。 |
两个关键的理解要点:
-
目录的
w权限:你对一个文件file.txt即使有w(写入) 权限,但如果你对它所在的目录 没有w(写入) 权限,你仍然不能删除file.txt[22:45]。因为删除文件是在修改目录的内容,这需要目录的w权限。 -
目录的
x权限:x权限非常容易被误解。它代表“进入”或“穿过”权限。如果你想访问/usr/bin/echo这个文件,你必须对/、/usr和/usr/bin这所有层级的目录都拥有x(执行) 权限 [22:58]。
6. Shell 的真正力量:流、重定向与管道
到目前为止,我们只在孤立地运行程序。Shell 的真正力量在于组合 (combining) 它们 [27:58]。
流 (Streams) [28:18]
简单来说,每个程序默认都有两个“流”:
-
输入流 (Input Stream):程序从哪里读取数据。默认是你的键盘。
-
输出流 (Output Stream):程序往哪里写入数据。默认是你的屏幕。
Shell 允许你“重定向 (rewire)”这些流。
I/O 重定向 (Redirection)
-
>(输出重定向):将程序的输出流重定向到一个文件(会覆盖文件原内容)。1
echo "hello" > hello.txt
这条命令不会在屏幕上打印 “hello”。相反,“hello” 被写入了
hello.txt文件中 [29:28]。 -
>>(追加重定向):将程序的输出流 追加 (Append) 到文件末尾(不会覆盖)。1
echo "world" >> hello.txt
现在
hello.txt的内容是 “hello\nworld” [31:15]。 -
<(输入重定向):将一个文件的内容作为程序的输入流。-
cat是一个会把它接收到的所有输入原样打印到输出的程序。 -
cat < hello.txt -
这条命令告诉 Shell:把
hello.txt的内容喂给cat程序作为输入。cat接收到 “hello\nworld”,于是把它们打印到屏幕(默认输出流)上 [30:13]。
-
管道 (Pipe) |
这是 Shell 中最强大、最核心的概念 [31:52]。
|(竖线):它做的是:将左边程序的输出流,直接“用管子”连接到右边程序的输入流。
关键点:ls 根本不知道 tail 的存在,tail 也不知道 ls [32:50]。它们各自都只是在从自己的标准输入读取、向自己的标准输出写入。是 Shell 在幕后用“管道”把它们连接了起来。这种“解耦”和“可组合性”就是 Shell 力量的源泉。
示例 1:简单管道
-
ls -l /:会输出根目录下所有文件的长列表(很多行)。 -
tail -n 1:tail程序会读取它的输入,并只打印最后 1 行 (-n 1)。
现在,我们用管道把它们连起来:
1 | ls -l / | tail -n 1 |
ls 的完整输出被“喂”给了 tail 作为输入,tail 处理后,只输出了最后一行 [32:09]。
示例 2:高级管道链
这是一个(有点刻意的)例子,演示如何通过组合工具链来获取 google.com 首页的 Content-Length(内容长度)[33:36]:
-
curl --head --silent google.com:curl是一个网络请求工具,这个命令会获取google.com的 HTTP 响应头。 -
| grep -i content-length:grep是一个文本搜索工具。-i表示忽略大小写。它会从curl的输出中,只保留含有 “content-length” 的那一行。 -
| cut -d ' ' -f 2:cut是一个文本切割工具。-d ' '表示使用空格作为分隔符,-f 2表示只保留第 2 个字段。
整个命令:
1 | curl --head --silent google.com | grep -i content-length | cut -d ' ' -f 2 |
通过这个“三级管道”,你从一堆原始的网络信息中,精确地提取出了你想要的那个数字。你用三个通用的程序,组合出了一个专用的工具。
这种能力不仅限于文本,你也可以用管道来处理图像、视频流(例如,将视频文件流式传输到 Chromecast)[34:41]。
7. 实战演练:sudo 权限与“重定向陷阱”
在 Linux/macOS 系统中,有一个特殊用户 root(也叫超级用户),它的用户 ID 是 0 [35:43]。root 用户不受权限限制,可以对系统做任何事情。
sudo (Super User Do)
-
你平时应该(也必须)作为普通用户(例如
john)操作,这更安全 [36:14]。 -
当你需要执行“管理员”才能做的操作时,你使用 sudo 命令:
sudo [command]
sudo 会让你输入你自己的密码,然后只以 root 权限运行你指定的那条 [command] [36:35]。
一个实战场景:修改屏幕亮度
-
在 Linux 系统中,很多硬件参数被抽象为
/sys目录下的虚拟文件 [37:03]。 -
例如,屏幕亮度可能由
/sys/class/backlight/intel_backlight/brightness这个文件控制。 -
cat brightness:可以读取当前亮度值 [38:08]。 -
尝试修改:
echo 500 > brightness
结果:Permission denied (权限被拒绝) [38:35]。
这是正常的,因为只有 root 才能修改这些内核参数。
-
尝试使用 sudo(The “Gotcha” 陷阱):
sudo echo 500 > brightness
结果:Permission denied (权限被拒绝) [38:48]。
-
为什么会失败? [39:01]
这是一个至关重要的概念:I/O 重定向 (>) 是由 Shell 自己处理的,而不是由你运行的程序 (sudo) 处理的。
-
Shell(正以
john用户的身份)在执行命令前,首先看到了>符号。 -
Shell(作为
john)尝试打开brightness文件以便写入。 -
系统内核拒绝了
john的写入请求。 -
命令甚至还没来得及执行(
sudo echo 500根本没运行),Shell 就报告了 “Permission denied” 错误。
-
-
解决方案 1:切换到 Root Shell (不推荐)
sudo su [40:20]
这会让你完全切换到一个 root 权限的 Shell(提示符会从 $ 变为 #)。
在这个新 Shell 里,echo 500 > brightness 就可以工作了,因为现在是 root Shell 在处理 > 重定向 [40:47]。但这很危险,因为你容易忘记你正处于“上帝模式”。
-
解决方案 2:使用管道和
tee(推荐)1
echo 500 | sudo tee brightness
工作原理 [41:17]:
-
echo 500:这个命令由你 (john) 来执行,它只是简单地产生 “500” 这个文本,并将其发送到输出流。 -
|(管道):接管了 “500” 这个输出,并将其作为输入流“喂”给下一个命令。 -
sudo tee brightness:-
sudo以root权限启动了tee命令。 -
tee是一个程序,它会从输入流(这里是管道传来的 “500”)中读取数据,并同时做两件事:a) 把它打印到输出流(屏幕);b) 把它写入到参数指定的文件中(这里是brightness)[41:39]。
-
-
因为是
tee程序(它正以root权限运行)去打开brightness文件进行写入,所以权限检查通过了。
-
这个技巧完美地解决了“重定向陷阱”,它只在真正需要权限的步骤(写入文件)才使用 sudo。
框架 & 心智模型 (Framework & Mindset)
1. 心智模型:I/O 流与可组合的“管道”
这堂课介绍的最核心的心智模型,是将程序视为处理“文本流”的过滤器 (Filter),而不是孤立的、大而全的应用。
在图形界面 (GUI) 中,一个程序(比如 Word)包揽了所有功能:打开文件、编辑、保存、打印。而在 Shell 的世界里,推崇的是“Unix 哲学”(视频中未明确提此词,但体现了该思想):
-
程序应该只做一件事,并把它做好。
-
ls只管列出文件。 -
grep只管搜索文本。 -
cut只管切割文本。 -
tail只管提取末尾几行。
-
-
程序应该能协同工作。
- 所有程序都默认从标准输入 (Standard Input) 读取数据,向标准输出 (Standard Output) 写入数据。
这个模型的“框架”就是 Shell 提供的“连接器”:
-
<和>(重定向):这是程序与文件之间的连接器。它们将程序的输入/输出流从默认的键盘/屏幕“重定向”到文件。 -
|(管道):这是程序与程序之间的连接器。它将前一个程序的标准输出“焊接”到后一个程序的标准输入上。
这个框架的威力在于“可组合性” (Composability)。
当你面对一个新问题时,你的思维方式不应该是“我需要一个能XXX的软件”,而应该是“我是否能用 a 程序获取原始数据,然后用 b 程序过滤,再用 c 程序提取,最后用 d 程序格式化?”
视频中的 curl | grep | cut [33:36] 链条就是这个心智模型的完美体现:
-
数据源 (Source):
curl,它从互联网获取原始数据流。 -
过滤器 1 (Filter):
grep,它从数据流中筛选出感兴趣的行。 -
过滤器 2 (Filter):
cut,它从上一步的行中提取出我们真正想要的字段。 -
汇入点 (Sink):你的屏幕(或者你可以用
> output.txt汇入到文件)。
这个模型是整个 Shell 乃至 Linux/Unix 系统设计的基石。它允许你使用几个非常简单的工具,像搭乐高积木一样,搭建出无限复杂的自定义工作流,而无需编写一行传统意义上的代码。
2. 框架:sudo 权限提升的“tee 技巧”
这是一个更具体、战术性的框架,用于解决一个在 Shell 中极其常见的权限问题。
- 问题 (The Problem):
你需要以普通用户身份,将数据写入一个只有 root 用户才能写入的文件(例如 /sys/ 或 /proc/ 下的系统文件)。
- 陷阱 (The Gotcha):
天真的尝试 sudo echo “data” > /path/to/root_file 会失败。
- 根本原因 (The Root Cause):
I/O 重定向 (>) 是由当前 Shell(以普通用户运行)在命令执行前解析的 39:13。你的普通用户 Shell 没有权限打开那个 root_file,因此操作在 sudo 生效前就失败了。
4. 解决方案框架 (The Framework):
1 | [data_generator] | sudo tee [target_file] |
框架组件解析:
-
[data_generator](数据生成器):-
这是任何能够产生你想要写入的数据的命令。
-
它作为普通用户运行。
-
例子:
echo 500(生成 “500” 字符串)[41:17]。
-
-
|(管道):-
这是框架的“传输带”。
-
它将
[data_generator]的标准输出(数据),传输给下一个命令的标准输入。
-
-
sudo tee [target_file](特权写入器):-
这是框架的核心。
-
sudo将tee命令的权限提升为root[42:05]。 -
tee是一个巧妙的工具,它从标准输入(即管道)读取数据,然后将其写入到[target_file]文件中。 -
关键点:因为
tee是在sudo下以root权限运行的,所以当它尝试打开并写入[target_file]时,它拥有足够的权限,操作得以成功。
-
这个框架的心智模型是“ 将数据生成与数据写入相分离 ” 。你作为普通用户负责“生成”数据(这是安全的),然后通过管道将数据“递交给”一个临时的、拥有 root 权限的“写入器”(sudo tee)来完成最后一步危险的写入操作。这远比 sudo su 切换到一个完整的 root Shell 要安全和优雅。

