Skip to content

元数据卡

  • 前置知识:第1章(计算机到底是什么)
  • 预计时间:40 分钟
  • 核心难度:
  • 阅读模式:🍃 轻松漫游
  • 完成标志:能够在终端中完成文件创建/移动/删除;理解 PATH 和环境变量;会用通配符和管道组合命令

你在哪

你还在出发前的工坊里。上一章的课桌摊着计算机的拆解图——你刚搞懂了电脑肚子里有什么,但现在你得学会怎么跟它说话。

但你看完全部作业的时候,发现工坊主人不见了。

桌上留了一张地图——一张泛黄的羊皮纸,画着一个奇怪的符号:$

地图底下压着一张字条:

"我在工坊地下的机房。下来找我。但你先要学会怎么跟电脑说话——我不觉得你每一次都想点鼠标。走左边的门,打开终端。我们在楼下见。"

你看了看左边。那里有一扇门,门上写着:TERMINAL

你的任务

这章之后,你不会再害怕那个黑底白字的窗口。你会知道如何用它——如何浏览文件、创建项目目录、杀死坏掉的程序、运行你的第一行代码。

终端不是"旧时代的遗物"——它是开发者最常用的工具。熟练用终端的人,操作效率是纯鼠标的三倍。不是因为你打字更快,是因为你可以把一串操作写成一个命令、组合起来、甚至写成脚本跑一整夜。

今天的目标:学会用键盘和电脑说话

本章分层

  • 必读pwdcdlsmkdirtouchcatrm,以及简单的输出重定向(>>>
  • 选读PATH 环境变量、通配符(*?)、管道(|)组合命令
  • 深水区:正则表达式、Shell 配置文件(.bashrc/.zshrc)、自定义环境变量

本章不会要求你掌握

  • 正则表达式的完整语法——知道 grep 可以按模式搜索就够了
  • Shell 脚本编程(for 循环、if 条件、函数)
  • 系统配置文件恢复技巧

遭遇战 → 获得技能

第一回合:第一次对话

"你会跟电脑说话吗?"

工坊主人站在左边那扇标着"TERMINAL"的门前,抱着胳膊。"不是用鼠标点,是像跟一个老工匠打招呼那样——说它的语言。"

他敲了敲门。门里传来一个低沉的声音:"谁?"

"你试试。"他用下巴指了指门把手。"推开它,说句话。"

你推开 Terminal 的门,里面是一片漆黑。只有一个小小的闪烁的光标——一条竖线,在一行文字末尾一明一灭。

屏幕上只有一行:

steven@workshop:~$

这个 $ 就是你的提示符(prompt)。它在跟你说话:

"我准备好了,請告诉我下一步做什么。"

你可以看到提示符的几个部分:

  • steven — 你现在是谁(当前登录的用户名)
  • workshop — 你在哪台机器上(主机名)
  • ~ — 你当前在哪个目录(~ 是家目录的缩写)
  • $ — 这是一个普通用户的 shell

试着敲几个字母。先输入 whoami,然后按回车。

bash
# 告诉你是谁
whoami

# 预期输出:
# steven

语言:Shell (Bash/Zsh) 如何运行:打开终端,输入命令后按回车 预期输出:你的用户名 你试试hostname 看机器名,date 看日期时间,uptime 看电脑开机多久了

你输入的 whoami命令,按的回车是执行。终端把你的命令交给 Shell——Shell 是一个程序,它解析你的输入,找到对应的可执行文件(还记得第一章的程序吗?),执行它,然后把输出显示在屏幕上。

你可能还没意识到,你已经在跟电脑对话了。

第二回合:识路——目录和路径

你成功对着终端说了一句话——"whoami",它回了你的名字。但你忽然意识到一件事:在这片黑暗中,你连自己站在哪都不知道。

就像刚进一座巨大的地下工坊,你听到回声,但看不到脚下的路。你摸着墙壁走了两步——"我这是在工坊的哪个角落?"

"你在哪?"——这是终端里永远要知道的第一个问题。

在文件系统里,你永远有一个"当前目录"。提示符的 ~ 告诉你,你现在在家目录里。

bash
# 打印当前工作目录
pwd

# 预期输出:
# /home/steven

语言:Shell (Bash/Zsh) 如何运行:输入 pwd,回车 预期输出:当前所在目录的完整路径 你试试:先 cd /tmp,再 pwd,看看路径怎么变

pwd = Print Working Directory(打印工作目录)

文件系统是一棵树——根是 /,然后分叉出 home, usr, etc, var……每个分叉又可以继续分。/home/steven 就是你在树上的位置。

你可以在这棵树上移动:

bash
# 切换到 /tmp 目录
cd /tmp

# 查看 /tmp 里有什么
ls

# 回到上一级目录
cd ..

# 回到刚才的目录(还记得 /tmp 吗?)
cd -

语言:Shell (Bash/Zsh) 如何运行cd <路径>ls 不加参数 预期输出cd 无输出,ls 显示当前目录下的文件和子目录 你试试:用 cd 在各个目录之间跳来跳去,每跳一次用 pwd 确认位置

路径有两种:

  • 绝对路径:从根 / 开始写,比如 /home/steven/projects
  • 相对路径:从当前目录开始写,比如 ../steven/projects.. 表示上一级,. 表示当前目录)

Tab 键可以自动补全路径——你打出前几个字符,按下 Tab,Shell 会帮你补全。如果有多个可能,再按一次 Tab 会列出所有选项。这个习惯一定要养成,它省的时间超过你想象。

第三回合:触摸这个世界——文件操作

"知道你站在哪,很好。"工坊主人的声音从某个角落传来。"但光站着有什么用?你总得动手吧。"

你看了看四周——空荡荡的黑底白字世界,什么都没有。"可是这里什么都没有啊……"

"那就创造点什么。"他的声音带着笑意。"工坊的地上不是有材料吗?捡一块符文石板,刻上你的名字。"

现在你知道自己在哪了。该动手了。

你在工坊的一角发现一个空白的符文石板——它的名字叫 first-try.txt

bash
# 创建一个空文件
touch first-try.txt

# 确认它已经存在
ls -l first-try.txt

语言:Shell (Bash/Zsh) 如何运行:先 cd ~ 到自己目录,然后逐条输入 预期输出ls -l 显示文件大小、权限、修改时间 变成什么了:当前目录下多了一个 first-try.txt 文件,大小 0 字节

touch 的字面意思是"触摸"。如果文件不存在——创建它。如果文件已存在——更新它的修改时间。

现在往里面写点东西:

bash
# 用 echo 把文字写入文件
echo "Hello, Workshop!" > first-try.txt

# 查看文件内容
cat first-try.txt

# 预期输出:
# Hello, Workshop!

语言:Shell (Bash/Zsh) 如何运行:先确保 first-try.txt 存在,然后运行这两条 预期输出:终端打印出 "Hello, Workshop!" 你试试:用 echo "另一行文字" >> first-try.txt(两个 >)追加内容,而不是覆盖

这里有一个微妙的符号——>

>重定向。你不是在"告诉 Shell 把输出写到文件"——你是说:"把 echo 命令的输出,从默认的屏幕显示,重定向到文件里去。"

bash
# 把日期写入文件
date > timestamp.txt

# 把进程列表写入文件
ps aux > processes.txt

# 看看写了什么
cat processes.txt | head -5

如果文件已存在,> 会覆盖它。>> 会追加到文件末尾,不会覆盖。

第四回合:目录操作——建营地

你在终端里戳了几个文件出来,就像在空地上放下了几块石板。但你很快发现一个问题——石板乱丢在地上,用不了多久就找不到了。

"看看你周围。"工坊主人的声音从头顶传来。"我的工坊有分区的:锻造区在左边,材料区在右边,图纸挂在墙上。你的终端里呢?"

你低头看着那堆文件——它们混在一起,像把螺丝刀和锅铲扔进同一个抽屉。

"你需要建几个架子。"

你决定为这次冒险建一个专门的目录——"我的项目"。

bash
# 创建目录
mkdir my-project

# 进入目录
cd my-project

# 在里面再建一个子目录
mkdir src docs

# 看看结构
ls -l

# 预期输出:
# drwxr-xr-x  2 steven steven  4096 Jun 23 16:00 docs
# drwxr-xr-x  2 steven steven  4096 Jun 23 16:00 src

语言:Shell (Bash/Zsh) 如何运行:逐条输入 预期输出ls -l 显示两个子目录 你试试mkdir -p project/{src,test,docs} 一次性创建嵌套目录结构

删除也简单,但要小心——删除不经过回收站

bash
# 删除文件
rm first-try.txt

# 删除空目录
rmdir docs

# 删除目录及其所有内容(小心使用)
rm -rf src

语言:Shell (Bash/Zsh) 如何运行:确认文件/目录存在后运行 注意rm -rf 很危险,它会递归删除所有内容且不确认。永远在运行前检查你要删除的路径。

bash
# 安全做法:先 ls 查看要删的路径
ls src/
# 确认后:
rm -rf src/

# 或者加 -i 交互模式,逐个确认
rm -ri src/

进阶: 以下三个回合(PATH、通配符、管道)不是终端生存必需。读完主线后可以回来探索,但第一次读可以先跳过,不影响后续章节。

第五回合:环境变量和 PATH——系统的查找路径

"你刚才敲了 whoami,它找到了。你敲了 ls,它也找到了。"工坊主人不知道什么时候坐到了你的旁边。"但你想过没有——电脑是怎么知道 ls 在哪的?你的终端里并没有一个叫 ls 的程序躺着。"

你愣了一下。"对哦……我从来没考虑过这个问题。"

"你身上有一张地图。"工坊主人指了指你的胸口。"你走到哪都带着它,但你自己看不见。"

你身上可能背着一些"隐形的东西"——环境变量。

环境变量是 Shell 维护的一组键值对,告诉程序一些关键信息:你的用户目录在哪、用哪个编辑器、去哪里找可执行文件。

bash
# 查看所有环境变量
env | head -10

# 查看单个变量
echo $HOME
echo $USER
echo $SHELL
echo $PATH

语言:Shell (Bash/Zsh) 如何运行:直接输入 预期输出:你的家目录路径、用户名、Shell 路径、命令搜索路径 你试试echo $LANG 看系统语言,echo $PWD 看当前目录

注意 $ 符号——在 Shell 里,$变量名 表示引用这个变量。之前提示符上写的是 ~ 而不是 $HOME,它们大部分时候是等价的。

重点来了——PATH

bash
# 看看 PATH 里有些什么
echo $PATH

# 预期输出示例:
# /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/home/steven/.local/bin

语言:Shell (Bash/Zsh) 如何运行:直接输入 预期输出:一串用冒号隔开的目录路径 你试试which ls 查看 ls 命令具体在哪个目录,which python3 看 Python 在哪

PATH 是 Shell 用来寻找可执行程序的"地图"。当你输入 ls,Shell 按顺序遍历 PATH 里的每个目录:

  1. 先查 /usr/local/bin 里有没有 ls
  2. 没有?查 /usr/bin → 找到了!执行它

如果在两个目录里找到同名文件,只执行第一个。 这就是为什么你安装新程序后,有时需要把它的目录加到 PATH 前面,才能覆盖系统默认版本。

设置自己的环境变量:

bash
# 设置一个变量(只在当前终端会话有效)
export MY_VAR="Hello from my shell"

echo $MY_VAR
# Hello from my shell

要让它永久生效,需要写入 Shell 的配置文件——Bash 是 ~/.bashrc,Zsh 是 ~/.zshrc

bash
# 用 echo 添加一行到配置文件
echo 'export MY_VAR="Hello from my shell"' >> ~/.bashrc

# 重新加载配置文件
source ~/.bashrc

语言:Shell (Bash/Zsh) 如何运行:先手动执行 export 测试,再把那行写入配置文件 你试试:把 ~/my-bin 加入 PATH:export PATH="$HOME/my-bin:$PATH"(先测试,再写入配置文件)

进阶继续: 通配符让你批量操作文件,很实用但不是第一步就得学会的内容。

第六回合:通配符——批量施法

你建好了目录,放好了文件。然后你发现一个问题:你有二十个日志文件需要改后缀名,或者三十个测试文件要移动到一个子目录里。

你一个一个敲文件名——敲到第五个的时候,手酸了。

"你听说过工坊里的那种施法道具吗?"工坊主人从墙上取下一根铁棒。"它能同时影响一排武器,而不是一次只碰一把。"

你有一堆文件要处理,一个一个来太慢了。Shell 的通配符就是为此而生。

bash
# 先创建一些测试文件
touch report-2024-01.txt report-2024-02.txt report-2024-03.txt
touch photo-vacation.jpg photo-party.jpg note.txt

# 查看所有 report 文件
ls report-*

# 预期输出:
# report-2024-01.txt  report-2024-02.txt  report-2024-03.txt

# 查看所有 .txt 文件
ls *.txt

# 查看所有以 photo- 开头的图片
ls photo-*.jpg

# 查看名字里带数字的
ls *[0-9]*

语言:Shell (Bash/Zsh) 如何运行:先创建文件,然后尝试各种通配符 预期输出:根据通配符匹配的文件列表 你试试ls report-2024-0[13].txt 只匹配 1 和 3,跳过 2

通配符匹配的规则:

模式匹配规则例子匹配
*任意字符(0个或多个)*.txt所有 txt 文件
?单个任意字符report-?.txtreport-1.txt, report-a.txt
[abc]集合中的一个report-[13].txtreport-1.txt, report-3.txt
[a-z]范围[a-z]*.txt以小写字母开头的 txt
bash
# 批量操作示例:把所有 .png 文件后缀改为 .jpg

# 先模拟一下——单纯重命名
for file in *.png; do
  mv "$file" "${file%.png}.jpg"
done

# 安全做法:先打印,不改动
for file in *.png; do
  echo "mv -- '$file' '${file%.png}.jpg'"
done
# 确认无误后去掉 echo

语言:Shell (Bash/Zsh) 如何运行:在包含 png 文件的目录中执行 注意:先用 echo 测试,确认无误后再去掉 echo 执行真的移动

进阶继续: 管道是 Unix 哲学的精华,但可以先只读概念部分,具体命令组合遇到时再查。

第七回合:管道——把命令串起来

"你有了目录、文件、通配符——但有一件事你还没试过:把两个命令的能量连在一起。"

工坊主人拿起两根铁管,一头接在一起。"如果锻造炉的火能直接通到淬火池——你就不用端着烧红的铁块跑过去了。"

他拍了拍接好的管道。"命令也是。一个命令的输出,可以是下一个命令的输入。中间不用停下来看。"

这是你学到的最强大的工具之一。

管道符号 | 做一件事:把左边命令的输出,当成右边命令的输入。

想象一条传送带——水从上游流下来,经过不同的过滤器,最后变成纯净水。

bash
# 找当前目录所有文件,只数个数
ls | wc -l

# 找一个进程
ps aux | grep chrome

# 看系统日志最后 5 行里包含 error 的
tail -100 /var/log/syslog | grep -i error

语言:Shell (Bash/Zsh) 如何运行:逐条输入 预期输出:第一条输出文件数量,第二条列出包含 chrome 的进程 你试试ls -la | sort -k5 -n | tail -3 找出当前目录下最大的三个文件

管道可以把无穷多的命令组合起来,每个命令只做一件事、做好它——这是 Unix 哲学的核心。

举个例子——你想知道:"当前目录里最大的 .log 文件是哪个?"

bash
# 一步一步拆解:
# 1. 所有 .log 文件
ls *.log

# 2. 带大小显示
ls -lh *.log

# 3. 按大小排序(人类可读排序要加 -h)
ls -lh *.log | sort -k5 -h

# 4. 只看最大的那个
ls -lh *.log | sort -k5 -h | tail -1

# 5. 只保留文件名(不要大小那些信息)
ls -lh *.log | sort -k5 -h | tail -1 | awk '{print $NF}'

语言:Shell (Bash/Zsh) 如何运行:在含有 .log 文件的目录中执行 你试试:先用 ls -lh *.log,一步一步加管道,理解每一步发生了什么

你并不需要记住所有的命令和选项——awksortgrep 你以后会逐渐熟悉。关键是理解管道的思维模式:把问题拆成小步骤,每一步输出结果,交给下一步处理。

常见陷阱

陷阱一:rm -rf / —— 不可逆的删除操作

你肯定听说过这个梗。rm -rf / 会递归删除根目录下的所有东西,意味着整个系统被清空。

但现代系统有保护——你跑不了的:

bash
# 安全!不会执行的
rm -rf /

# rm: it is dangerous to operate recursively on '/'

即使你要强行跑,也需要 --no-preserve-root。但即便如此,因为很多目录被系统锁定,你最多只能破坏自己的用户数据。

真正危险的是你自己的误操作:rm -rf ./build,但你在根目录 ~ 而不是项目目录里。养成先 pwdlsrm 的习惯。

陷阱二:文件名有空格

bash
# 问题:这个文件有名有姓
touch My Important File.txt

# 试图删除
rm My Important File.txt
# rm: cannot remove 'My': No such file or directory
# rm: cannot remove 'Important': No such file or directory
# rm: cannot remove 'File.txt': No such file or directory

Shell 把空格当作参数分隔符。上面的命令试图删除三个文件:MyImportantFile.txt

解决方案:永远对文件名加引号。

bash
rm "My Important File.txt"

# 或用反斜杠转义空格
rm My\ Important\ File.txt

陷阱三:~ 不是当前目录

在终端里,~ 永远是你的家目录,不是当前目录。如果你 cd /tmp,然后 rm -rf ~/build,你删掉的不是 /tmp/build,而是 /home/steven/build

如果需要在当前目录操作,用 . 明确表示:

bash
rm -rf ./build   # 当前目录下的 build
rm -rf build     # 也是当前目录下的 build(没有 /)
rm -rf ~/build   # 家目录下的 build——完全不是一个地方!

陷阱四:管道和重定向混合时的困惑

bash
# 这会把 error 输出也重定向到文件
command > output.txt 2>&1

# 这表示只重定向标准输出
command > output.txt

2>&1 的意思是:文件描述符 2(stderr,标准错误输出)重定向到文件描述符 1(stdout,标准输出)的当前位置。

这不是你现在就需要记住的。但当你看到 >| 搭配使用时,知道有"输出流"这个概念就好。

通关挑战

  • 🗡 热身(5 分钟,必做)
  1. 打开终端,用 pwd 确认当前位置,用 ls 看看里面有什么。
  2. 在家目录下创建一个叫 practice 的目录,进去,在里面创建一个叫 hello.txt 的文件,写入一行文字,查看它。
  3. 运行 echo $PATH,用 : 隔开的路径里,找出至少一个你知道的目录(比如 /bin/usr/bin)。
  4. ls / | sort 查看根目录有什么文件,用 | head -10 只看前 10 个。
  • 挑战(30 分钟,选做)
  1. 管道链练习:找到 /var/log 目录下最新的 3 个日志文件:

    bash
    ls -lt /var/log | head -4 | tail -3

    理解每一步发生了什么。

  2. 批量重命名:在 practice 目录下创建 10 个测试文件 test-{1..10}.txt,然后用通配符把 .txt 改名为 .bak。先用 echo 测试命令,确认无误再执行。

    bash
    # 创建文件
    touch test-{1..10}.txt
    
    # 测试重命名
    for f in test-*.txt; do echo mv "$f" "${f%.txt}.bak"; done
    
    # 真的执行
    for f in test-*.txt; do mv "$f" "${f%.txt}.bak"; done
  3. 探索 PATH:用 which 找到 python3grepls 分别在哪个目录。用 file 命令查看它们的文件类型。

  • 排障:下面每个命令都有问题——你能看出问题在哪吗?
bash
# 问题 1:
rm -rf ~/build
# (你本意想删当前目录下的 build)

# 问题 2:
cd ~
rm -r practice
# (一切都消失了——等等,你在哪?)

# 问题 3:
ls -l | grep .txt
# (咦,所有文件都出来了,不只是 .txt)
# 提示:grep 的参数是正则表达式,`.` 表示任何字符
# 正确做法:
ls -l * .txt    # 错,空格问题
ls -l *.txt     # 对,通配符

验收标准

  • 你能用 cdlspwd 自如地在文件系统中导航。
  • 你能用 touchmkdircpmvrm 创建和管理文件目录。
  • 你理解 PATH 是什么,知道 $ 引用变量的含义。
  • 你会用 *? 通配符做批量操作。
  • 你会用 | 把命令串联起来。
  • 你知道文件名中的空格需要用引号包裹。
  • 你知道 rm -rf 的危险性,养成了先 ls 后删的习惯。
  • 你知道 >>> 的区别(覆盖 vs 追加)。

常见卡点

"我按上下箭头没反应?" 上下箭头可以调出历史命令。试几次。如果不行,确认你用的确实是 Bash 或 Zsh。

"我输入 cd .. 没反应?" cd .. 不会显示任何输出——它只是改变了当前目录。用 pwd 确认你到哪了。

"我怎么知道一个命令在哪里?"which <命令名> 查看。如果返回 /usr/bin/ls,说明它就在那里。如果返回 "not found",你的 PATH 里没有它,或者你没装。

"配置文件我改乱了怎么办?" 大多数系统有备份。比如 ~/.bashrc 如果弄坏了,可以复制 /etc/skel/.bashrc 回来,或者新建一个终端会话看看有没有原始配置。如果完全坏了,创建一个干净的:

bash
echo "# Default bashrc" > ~/.bashrc
source ~/.bashrc

"为什么我改了 ~/.bashrc 没效果?" 修改后需要重新加载。运行 source ~/.bashrc 或在当前终端里新开一个子 shell。也可以直接关闭并重新打开终端。

现在不需要理解

  • Shell 脚本编程(for 循环、if 条件、函数)——这章只用了最简单的 for 循环做批量操作示例
  • awksedxargs 等复杂文本处理命令——以后自然会知道
  • /dev/null 到底是什么——现在知道它是"黑洞"就行
  • 进程管理 kill 信号类型(SIGTERM vs SIGKILL)——第 1 章提过了,但你不需要记住所有信号
  • 正则表达式——grep 支持正则,但这章只用到了普通文本搜索
  • 如何自定义 PS1 提示符——酷但还不需要

旅人笔记

终端是你的对话窗口。Shell 解析你的命令,$ 在等你下一条。
cd 让你在地图上移动,ls 环顾四周,pwd 确认位置。
文件操作铭刻于心:touch 创建,mkdir 建营,rm 销毁但要慎之又慎。
PATH 是 Shell 找程序的目录列表,$ 取出变量的值。
通配符是批量施法,管道是传送带。
空格是参数分隔符,用引号包住文件名。
忘不掉的是:先 ls,确认路径,再执行破坏性操作。

下一站预告

你已经能熟练地在终端里穿梭了——创建、删除、移动、查找,不在话下。

但很快你就会遇到一个让所有开发者都头疼的问题:改了代码,改错了,想回到一小时前——可是已经保存了。

下一章,你会拿到时光机:Git。你可以任意回溯到任何一个时刻,像从未犯错一样重新出发。还记得工坊主人说的那句话吗——"改坏了别怕,我们有存档。"

Built with VitePress | Software Systems Atlas