SONUB 首页 ZH English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

如何开启通知

此浏览器或网址无法使用通知。请使用支持通知的浏览器,通过安全连接访问SONUB。

如果通知被屏蔽,请在浏览器设置中允许通知。

  1. 打开地址栏旁的站点设置或浏览器的站点设置,将此网站的通知设为允许。
  2. 在设备设置中允许浏览器发送通知,并检查勿扰模式或专注模式。
  3. 返回SONUB,刷新页面,然后通过铃铛开启通知。

在iPhone或iPad上,将SONUB添加到主屏幕,从主屏幕打开后允许通知。

基础中的基础,也是起点

AI 什么都能替你做,为什么还要学 Shell?

只要请 帮忙,它真的什么都能做。但如果什么都不懂就全部交给它,你就会被 AI 束缚,还会误以为 AI 做的事情是自己亲手完成的。

设想一位主管把重要工作交给员工,却完全不了解工作内容。即使员工做得很好,主管也难以判断结果;员工离开后,工作也很难接着做。把任务交给 AI 时,同样需要基础知识。

AI 也是一样。只有懂得基础的人,才能理解 AI 做了什么、确认它是否正确,并在卡住时自己接着做下去。想成为驾驭 AI 的人,就必须懂得基础。

正是基础中的基础,也是起点。学习 Shell 命令,你会掌握操作系统的使用方法和管理的基础,而下一步就是亲自运营服务器。

不要害怕。遇到不懂的命令,问 AI 就能轻松学会。亲手一行一行地输入,一步一步学下去就好。

学 Python 之前,先掌握 Shell 基础

学习 时,人们往往先想到 ,但安装和运行 AI 工具所需的实用基础,是与 。

Shell 虽然不是构建 AI 的主要语言,却比 Python 更早出现,长期用于文件管理和日常任务自动化。

无需深入钻研,从几个常用命令开始,就能轻松入门,掌握受用一生的基本功。

一切工具背后的基础语言

Shell 编程真正驱动 AI 的语言

在运行、管理文件和自动化开发方面非常有用。但 也可以通过、应用、笔记本和 使用,所以并不是每个 AI 用户都必须使用。

查看课程
  • 无需安装,立即练习
  • 兼容 macOS ·
  • 浏览器里安全的真实 Linux
我的学习旅程 今天迈出第一步
已完成 0 / 15 课 0%
15阶段路线图
47个练习任务
0次命令运行
循序渐进

用 15 课掌握 Shell

从基础到 与开发工具安装,再到运行 、、 等其他语言和,学完立即亲手运行。

入门 → 实战
第 1 部分 · 第 1 课–第 4 课

命令基础

打开、处理文件、查找并统计文本、把串起来。学完这四课,你就能自己使用终端了。

  1. 第 01 课 已完成 下一课 初识终端 命令语法与当前位置 3 个任务 开始
  2. 第 02 课 已完成 下一课 文件与文件夹 创建、移动、删除和查看内容 3 个任务 开始
  3. 第 03 课 已完成 下一课 文本处理工具箱 grep 查找,sort·uniq·wc 统计 2 个任务 开始
  4. 第 04 课 已完成 下一课 管道与重定向 连接命令并把结果写入文件 2 个任务 开始
第 2 部分 · 第 5 课–第 9 课

编写脚本

存放数值(),判断和重复(条件与),给一组起个名字(),安全地做出属于自己的自动化。

  1. 第 05 课 已完成 下一课 变量与展开 保存、复用和计算值 2 个任务 开始
  2. 第 06 课 已完成 下一课 条件与循环 用 if 判断,用 for 重复 2 个任务 开始
  3. 第 07 课 已完成 下一课 函数与脚本 创建自己的命令 1 个任务 开始
  4. 第 08 课 已完成 下一课 实用脚本 构建真正有用的自动化 1 个任务 开始
  5. 第 09 课 已完成 下一课 安全 Shell:引号与陷阱 养成防御式脚本习惯 2 个任务 开始
第 3 部分 · 第 10 课–第 13 课

实战工具

安装 和 开发工具,再在里用 、、 和完成实际工作。

  1. 第 10 课 已完成 下一课 Node.js 命令 用 node、npm、npx 运行 JavaScript 3 个任务 开始
  2. 第 11 课 已完成 下一课 安装开发工具 把 Grok、Codex、Claude Code、Firebase 装进终端 3 个任务 开始
  3. 第 12 课 已完成 下一课 用 Shell 运行其他语言 直接在终端里驱动 Python、awk 和 sed 5 个任务 开始
  4. 第 13 课 已完成 下一课 正则表达式 按“形状”查找、挑出和修改文本 5 个任务 开始
第 4 部分 · 第 14 课–第 14 课

一览总整理

把所有共同遵守的格式集中起来——空格位、、、连接符号。学完就能读懂从未见过的命令。这一课可以先读。

  1. 第 14 课 已完成 下一课 命令模式 所有命令背后的同一个格式 3 个任务 开始
第 5 部分 · 第 15 课–第 15 课

进阶——真实的 Linux

是一台真实的 电脑。你可以亲手准备一块空白磁盘,再从互联网下载并安装软件——和在真实上用的步骤完全一样。

  1. 第 15 课 已完成 下一课 磁盘、软件包与互联网 给磁盘分区、格式化并挂载,再像在真实服务器上一样下载和安装 10 个任务 开始
为什么学 SHELL?

一次掌握,终身受用的计算机通用语言

亲手运行 AI 给出的答案

当你让 整理文件或自动化任务时,它常常会用 来回答。学会阅读 Shell,才能在运行前亲自确认是否安全。

随处可用的基础能力

Shell 的几十年来几乎没变,是 macOS、 和上随处可见的通用计算机语言。学会一次,即可长期用于 macOS、Linux、服务器、平台和 AI 工具。

macOS 用户也可以直接跟学

macOS 默认使用 ,本课程的大部分基础命令和也能直接使用。但 zsh 对未加的如何拆分、如何编号,与 bash 不同。以 bash 为准:首行 表示直接运行脚本时使用 bash。macOS 自带的 bash 3.2 不支持 -A 等功能,这些示例请在课程中运行。

实战示例

用一个 Shell 脚本搭建 Web 服务器

用大约 20 行 ,就能启动一个真正向返回页面的 Web 。这个例子生动地说明: 归根结底是"约定好互相收发一段文本"。

server.sh
#!/bin/bash

PORT=8084
PIPE=simple-web-server-pipe

rm -f "$PIPE"
mkfifo "$PIPE"

response() {
    read -r method path _
    echo "[$(date)] $method $path" >&2
    index=$(cat index.html)
    printf 'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n%s' \
      "$index"
}

while true; do
    response < "$PIPE" | nc -l "$PORT" > "$PIPE"
done
  1. mkfifo "$PIPE" 创建一个。 收到的请求和我们生成的响应都通过这条通道来回传递。
  2. nc -l "$PORT" 用 nc(netcat)打开 8084 ,等待浏览器连接。一行就代替了服务器。
  3. response() 读取请求的第一行(方法与)并记录日志,把 index.html 作为 HTTP 200 响应返回。
  4. while true; do … done 响应后连接断开,重新开始以接待下一位访客,服务器因此持续运行。

把 index.html 放在同一目录,运行 bash server.sh,然后在浏览器中打开 :8084。并非用于生产,但非常适合理解 HTTP 的原理。

第 01 课 · 动手实验

初识终端

学习输入、理解并找到。

说得简单点 你和电脑用文字进行的第一次对话:你打一行,它回一行。

5 个学习目标 3 个练习任务 约 10 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 02 课 · 动手实验

文件与文件夹

练习创建、复制、移动、删除和读取文件与文件夹。

说得简单点 在文件管理器里用鼠标做的事(新建、复制、移动、删除),改成打字来完成。

4 个学习目标 3 个练习任务 约 10 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 03 课 · 动手实验

文本处理工具箱

查找匹配行、排序和去重文本、统计行数或单词数,并在文件夹中查找文件本身。

说得简单点 给超长文本用的荧光笔、排序器和计数器。

5 个学习目标 2 个练习任务 约 10 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 04 课 · 动手实验

管道与重定向

使用 | 连接输出与输入,使用 > 和 >> 把结果写入文件。

说得简单点 就像传送带:每条的结果传给下一条,最后装进箱子(文件)。

4 个学习目标 2 个练习任务 约 15 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 05 课 · 动手实验

变量与展开

将值保存在中,并把变量、结果和计算结果嵌入文字。

说得简单点 就像贴了标签的盒子:把值放进去,以后按名字取出来。

4 个学习目标 2 个练习任务 约 10 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 06 课 · 动手实验

条件与循环

使用 if 选择行为,使用 for 或 while 重复工作。

说得简单点 就像“下雨就带伞”和“爬十级台阶”:做判断,也做重复。

4 个学习目标 2 个练习任务 约 15 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 07 课 · 动手实验

函数与脚本

把常用组合成,传递,并理解与 。

说得简单点 就像一张常做事情的菜谱卡:叫一声名字,整件事就做完了。

4 个学习目标 1 个练习任务 约 15 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 08 课 · 动手实验

实用脚本

组合所学知识,编写能验证输入并安全自动化工作的。

说得简单点 就像多米诺骨牌:前一步成功了,下一步才会倒下。

4 个学习目标 1 个练习任务 约 15 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 09 课 · 动手实验

安全 Shell:引号与陷阱

避免和陷阱,编写遇错安全停止的。

说得简单点 就像系好安全带:和安全能在事故发生前把它挡住。

4 个学习目标 2 个练习任务 约 15 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 10 课 · 动手实验

Node.js 命令

学习在使用 :查看版本、运行、用 管理包,以及用 一次性运行工具。

说得简单点 就像的应用商店:npm 负责取来工具, 负责运行 。

4 个学习目标 3 个练习任务 约 10 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 11 课 · 动手实验

安装开发工具

简要说明如何在安装 编程助手与 。准备好 Node/ 后,多数工具只需一条即可开始。

说得简单点 就像把工具搬进新工作间,再逐一试试能不能用。

4 个学习目标 3 个练习任务 约 10 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 12 课 · 动手实验

用 Shell 运行其他语言

并不亲自做所有事,它是调度其他语言的指挥。学习用 -c 执行单行、用 传入多行,以及 最常用的两个工具: 与 。

说得简单点 就像翻译员:Shell 把活儿交给 、awk 或 sed,再把结果带回来。

6 个学习目标 5 个练习任务 约 30 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 13 课 · 动手实验

正则表达式

(regex)是一种用来写下“想找的文本长什么样”的小语言。日期、邮箱和 的值各不相同,但形状一样:用 查找,用 修改,再在 和 中检查。你还会学到 macOS、 和 Windows 自带的。

说得简单点 就像一枚刻着“三位数字-四位数字”形状的印章,能找出所有吻合的文字。

6 个学习目标 5 个练习任务 约 30 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 14 课 · 动手实验

命令模式

只遵守几种格式。学会命令、、对象这几个位置,就不必一个一个死记,连没见过的命令也能读会写。

说得简单点 就像一份每格都写着名称的表格——认得格子,什么表格都填得出来。

4 个学习目标 3 个练习任务 约 20 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
第 15 课 · 动手实验

磁盘、软件包与互联网

是一台真实的 ,带有一块空磁盘,并且能连上互联网。把磁盘分出一个,格式化它,挂到一个文件夹上,再在里面保存文件——然后用 获取网页,用 clone 复制项目,用 、 和 安装,和在真实上一模一样。

说得简单点 就像设置一台全新的电脑:先准备好空硬盘,再下载并安装你的应用。

5 个学习目标 10 个练习任务 约 40 分钟
立即练习 AI 老师 Lyen 会用文字和语音回答(需要登录)。
  1. 1 一次读一个概念
  2. 2 点一下它下面的示例,再按 Enter
  3. 3 在终端里完成任务
  4. 4 卡住了?问 AI
学习目标

本课你将做到

  1. 1

    使用“命令 [] ”结构

  2. 2

    查看位置并切换文件夹

  3. 3

    使用 输出文字

  4. 4

    使用 man 和 --help 查找帮助

  5. 5

    需要管理员权限的命令前加上

一个孩子坐在黑色终端窗口前敲键盘,光标在闪烁;柜台对面,一位友善的电脑办事员接过打好字的纸条,递回一张答案纸条;他身后的文件柜里,一枚图钉标出当前所在的文件夹。
就像一个柜台:你递过去一条命令,电脑完成后把结果递回来。 会告诉你现在站在哪个文件夹里。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 命令

    有些后面什么都不用加——名字本身就是整条命令。

  2. 命令 选项 对象

    几乎所有都遵守的格式——做什么、怎么做、对什么做。

  3. cd 到哪去

    只接收一个要去的地方。留空就回到主文件夹。

  4. echo 文字

    会把你给的文字原样打印出来,有空格就用括起来。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
选项
决定怎么做。以 - 或 -- 开头,可以不写,也可以写好几个。
对象
对什么做——文件或文件夹的名字,或者路径。
到哪去
结果放在哪里——新的位置,或者新的名字。
文字
要打印出来的文字。里面有空格就用引号括起来。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

终端与提示符

是输入文字的窗口。读取并执行命令的叫作 ,本课程使用 。$ 或 % 这样的表示可以输入命令了。在它后面输入命令,再按 Enter。这种用文字操作计算机的方式叫作 (命令行界面)。

深入了解 提示符显示的往往不只是 $。在很多电脑上,它看起来像 you@laptop:~$——依次是用户名、电脑名、当前所在的文件夹(~ 表示),最后是 $。命令运行期间提示符会消失,直到命令结束才重新出现;看不到提示符,就说明 Shell 还在忙。如果命令好像卡住了,按 Ctrl+C 停止它,提示符就回来了。

02

命令的三个部分

多数包含动作、。例如 -l notes.txt 表示详细列出 notes.txt 这个文件。遇到不认识的命令,可以在 man 后面写上它的名字,或者在命令后面加上 --help,就能看到它的说明书(里也会打开真正的手册,按 q 关闭)。

深入了解 选项通常以短横线开头。一个短横线后面跟单字母的短选项,而且可以合在一起写:ls -l -a 和 ls -la 是一样的。两个短横线后面跟好读的长名称,比如 ls --all。参数是命令要处理的对象——通常是文件名或文件夹名——很多命令一次可以接收好几个:ls notes.txt photos。如果名字里有空格,要用括起来,它才会保持为一个参数。

03

知道自己在哪里

查看, 切换目录, 查看内容。~ 表示,.. 表示。

深入了解 就是文件的地址。绝对路径从磁盘的最顶层 / 开始(例如 /home/you/notes.txt),所以在任何地方都有效。相对路径从你现在的位置开始(photos/cat.jpg),所以只有在正确的文件夹里才有效。单独输入 cd 会直接回到主目录,cd - 则跳回你之前所在的文件夹。输入名字时按 Tab, 会帮你补全剩下的部分()——打错字的情况会少很多。

04

在自己的电脑上打开终端

在这个页面上,不用安装任何东西,就能在里马上练习。在 Mac 上,打开“”应用(在 Spotlight 中搜索“终端”)。在 Windows 上,从开始菜单打开 ;要使用和本课程相同的 ,请安装 并打开 Ubuntu。在大多数 桌面上,按 Ctrl+Alt+T。

深入了解 Mac 的默认 是 zsh,它几乎能理解本课程的所有命令;如果想用和这里完全相同的 Shell,输入 bash 即可。在 PowerShell 里,少数名字(、、)能用,但行为不同,很多其他命令根本不存在,所以在 Windows 上推荐使用 WSL。不管在哪里,方向键都能调出之前的命令:按 ↑ 就能再次运行或修改上一条命令,不必重新输入。

05

需要 root 权限时 —— sudo

凡是改变整台电脑的操作 —— 安装,或往 /etc 这类系统目录里写文件 —— 都属于管理员(root),直接执行会得到 。在前加上 ,同一条命令就以管理员权限运行,例如 sudo add tree。不会要密码;在自己的电脑上则要输入登录密码。只在命令确实需要时才用它。

深入了解 sudo 通常让一条命令以管理员权限运行,可用于安装软件包(apk add、apt install)、修改 /etc 或 /usr、挂载磁盘。它不会防止操作失误,执行前要检查命令。中出现权限错误时,先检查文件所有者和权限。 install -g 是否需要 sudo,也取决于安装位置。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

ls -la ~/projects
  1. ls:列出文件夹里有什么。
  2. -l长格式:每行一项,并显示权限、所有者、大小和日期。
  3. -a全部:连名字以点开头的(例如 )也显示出来。
  4. ~/projects:(~)里的 projects 文件夹。省略它, 就列出你当前所在的文件夹。

屏幕上看到的

total 16
drwxr-xr-x   5 you  staff  160 Oct 17 09:12 .
drwxr-xr-x  20 you  staff  640 Oct 17 09:10 ..
-rw-r--r--   1 you  staff   18 Oct 17 09:11 .env
-rw-r--r--   1 you  staff  512 Oct 17 09:11 notes.txt
drwxr-xr-x   3 you  staff   96 Oct 17 09:12 site

每一行是一个项目。第一个字母是 d 表示文件夹,是 - 表示文件;后面的 r、w、x 字母说明谁可以读、写或运行它。接着是所有者、以为单位的大小、最后修改的日期和名字。. 是这个文件夹本身,.. 是文件夹;第一行(total)是磁盘占用,可以忽略。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

这就是 | 。左边打印出来的东西不去屏幕,而是直接进入右边的命令。第 4 课会正式学它,但从这里开始每一课都会见到 —— 把它读作「然后把这个递过去」就好。

  1. ls|wc -l

    把文件列出来, -l 数它打印了多少行,于是得到的就是「有几个文件」。

  2. cat notes.txt|head -3

    打印整个文件, -3 只留下其中前三行。

  3. echo "hello shell"|wc -c

    -c 统计 输出的数,包括末尾的换行符。结果不会保存到文件。

连接用的符号

|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 和之间必须有空格:应写 -la。
  • ,应使用 ls 和 。
  • 表示权限不足。先检查目标。如果操作确实需要管理员权限,例如向 /etc 写入的示例,再在命令前加 。
学习目标

本课你将做到

  1. 1

    创建文件夹和空文件

  2. 2

    复制、移动、重命名和删除

  3. 3

    使用 、、 查看内容

  4. 4

    安全使用删除

画成一棵树的文件夹:树干上的大文件夹是根目录,树枝通向更多文件夹,树叶是文件;有个文件夹上画着一座小房子,代表主目录;一位拿着地图的探险家沿着虚线,从树干走到一个发光的文件。
每个文件都长在同一棵大树的某个地方。像 /home/me/notes.txt 这样的,就是从根目录走到那片树叶的路; 带你从一个文件夹走到另一个文件夹。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. mkdir 选项 对象

    新建文件夹。加上 -p 时,路上缺的上层文件夹也一起建好。

  2. cp 选项 对象 到哪去

    复制有两个位置——复制什么,然后放到哪。整个文件夹要加 -r。

  3. mv 对象 到哪去

    移动和改名是同一个格式——写新文件夹就是移动,写新名字就是改名。

  4. rm 选项 对象

    删除你写出的东西。这里没有回收站,所以用 -i 先问一句。

每个位置填什么

选项
决定怎么做。以 - 或 -- 开头,可以不写,也可以写好几个。
对象
对什么做——文件或文件夹的名字,或者路径。
到哪去
结果放在哪里——新的位置,或者新的名字。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

创建 — mkdir 与 touch

创建文件夹, 创建空文件。mkdir -p 可以一次创建缺少的。和大多数一样,它们成功时什么也不输出,所以请用 查看结果。

深入了解 touch 还有第二个用途:如果文件已经存在,它不动内容,只更新“最后修改”时间,所以运行两次也安全。相反,如果文件夹已经存在,mkdir 会报错 'File exists'——除非加上 -p,它会默默接受已存在的文件夹。带空格的名字需要加(mkdir "my notes");很多人用 - 或 _ 代替空格,以免这种麻烦。

02

复制、移动、重命名

创建副本并保留原文件, 则移动文件或改名。两者都先写来源,再写目标。例如 mv memo.txt diary.txt 会在同一文件夹中将 memo.txt 改名为 diary.txt。

深入了解 当最后一个是一个已存在的文件夹时,cp 和 mv 会把文件以原来的名字放进去:cp a.txt b.txt backup/ 会把两个文件复制到 backup 里。要复制整个文件夹必须加 -r(),否则 cp 会拒绝;mv 不需要任何就能移动文件夹。用 mv 重命名即使是巨大的文件也是瞬间完成,因为只改了名字,什么都没有复制。

03

查看内容

显示全部内容, 和 显示开头或结尾。 不经过回收站,必须谨慎。

深入了解 如果不另外指定,head 和 tail 显示 10 行:head -n 3 显示三行。tail -f 会持续跟踪文件,新增的行一出现就打印出来,人们在运行期间就是这样查看日志的(按 Ctrl+C 停止)。长文件请用 less:它一次显示一屏,按 Space 翻页,/ 搜索,q 退出。因为 rm 不经过回收站,好习惯是先用相同的名字或模式运行 ,确认后再 rm。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

cp -r photos backup/photos-2026
  1. cp:复制。
  2. -r:复制文件夹及其中的所有内容。没有 -r, 会拒绝复制文件夹。
  3. photos源:要复制的文件夹。
  4. backup/photos-2026目标:副本会以 photos-2026 的名字创建在 backup 里。backup 文件夹必须已经存在。

屏幕上看到的

什么也没有——这类命令成功时不输出任何内容。

复制成功时,cp 什么也不输出。用 backup 检查——应该能看到 photos-2026。如果 backup 不存在,就会出现 'No such file or directory';先用 -p backup 创建它,再重新复制。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. ls -l|head -3

    详细列表长得会滚出屏幕,所以用 -3 只看前三行。

  2. mkdir -p backup&&cp notes.txt backup/&&ls backup

    前一步成了才走下一步 —— 就是「那件事成了才做」,最后一步把结果显示给你看。

  3. ls photos|wc -l

    不打开 photos 文件夹,就数出里面有多少项。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • -rf 会无确认删除整个文件夹,请先检查。
  • 和 可能静默目标,建议使用 -i。
学习目标

本课你将做到

  1. 1

    使用 查找行

  2. 2

    使用 grep -i 和 -n

  3. 3

    使用 排序

  4. 4

    使用 统计

  5. 5

    使用 find 按名称和类型查找文件和文件夹

一张写满文字行的纸穿过筛子:只有带高亮词的行掉下去,然后按顺序排好,再用计数器计数。
grep 只保留你想要的行,sort 把它们排好,wc 负责计数。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. grep 选项 要找的内容 在哪找

    要找的内容在前,到哪里找在后。这个顺序不会变。

  2. sort 选项 文件

    给文件的行排序。-r 倒过来排,-n 按数字排。

  3. wc 选项 文件

    统计文件内容:-l 数行,-w 数单词,-c 数。字节数可能不同于字符数。

  4. find 在哪找 条件 动作

    find 读起来像一句话——在哪找、符合什么、找到后做什么。

每个位置填什么

选项
决定怎么做。以 - 或 -- 开头,可以不写,也可以写好几个。
在哪找
去哪里看——命令要翻查的文件或文件夹。
文件
命令要读取或写入的文件。
要找的内容
要比对的文字或形状。用引号括起来,Shell 就不会动它。
条件
做判断的检查,结果只有真或假。
动作
轮到它时要运行的命令。
核心概念

掌握核心知识

试一试 点一下示例,它的就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

grep — 查找

word file 只显示匹配行。-i 忽略,-n 显示行号,-r 搜索。

深入了解 grep 逐行读取文件,并打印所有在任意位置包含该模式的行——搜索 也会找到 'concatenate'。加上 -w 就只匹配完整的单词。当模式里有空格或符号时,要用括起来(grep "error " app.log)。配合 -r 和一个文件夹使用时,grep 会在每个匹配行前面写上文件名,这样你就能看到每处结果在哪里。

02

排序与统计

将各行排序。 只合并相邻的重复行,所以要先用 sort 把分散的重复行放到一起。 -l 统计行数。下一单元会用连接这些,完成简单统计。

深入了解 sort 按文字比较,所以默认情况下 10 排在 9 前面;数字请用 sort -n,倒序用 sort -r。uniq 只合并相邻的相同行,所以它几乎总是跟在 sort 后面。不带的 wc 会依次打印三个数字——行数、单词数和数;-l、-w 和 -c 可以只选其中一个。

03

find — 查找文件和文件夹

查找文件里的行,find 则查找文件和文件夹本身。它从起始文件夹出发,走遍它下面的每一个文件夹,把每个匹配项的单独打印成一行。基本格式是 find 位置 条件 动作。

  • 位置 — 开始查找的文件夹。. 是,~ 是;省略时 find 默认使用 .
  • 条件 — -name "*.txt" 按名称匹配(模式要放在里),-iname 忽略,-type f 只选文件,-type d 只选文件夹。写了多个条件时,必须全部满足。
  • 动作 — 不写时就是 -print(显示路径)。-exec {} \; 对每个匹配项运行一次命令,{} 的位置会换成找到的路径。

不带条件只输入 find . 会列出当前文件夹下的所有内容。每个结果都是从你给的文件夹开始的路径,例如 ./photos/cat.jpg,所以可以直接交给别的命令。

深入了解 find 不只是看名字是否匹配——它会从上到下走遍整棵文件夹树。在 / 或整个主目录这样的大地方运行会花不少时间,而且每个没有读取权限的文件夹都会多出一行 ;在末尾加上 2> 就能把这些错误丢掉。-maxdepth 1 让 find 只看当前文件夹,不进入子文件夹。-name 区分大小写(README.md 和 readme.md 不同),而且只看名字、不看路径——要按路径匹配请用 -path "*/src/*"。和 grep 搭配,就能回答“哪些文件里有这个词?”:find . -name "*.js" -exec grep -l "TODO" {} +

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

grep -rin "todo" src/
  1. grep:查找包含某个模式的行。
  2. -r:搜索该文件夹及其所有子文件夹中的每个文件。
  3. -i忽略:TODO、Todo 和 todo 都会匹配。
  4. -n行号:显示每个匹配所在的行号。
  5. "todo"要查找的模式。只有一个词时可以不加,但加上是个好习惯。
  6. src/查找位置:src 文件夹。

屏幕上看到的

src/app.js:12:  // TODO: check the password length
src/utils/date.js:3:// todo: handle time zones

每个结果都写成“文件名 : 行号 : 该行内容”。打开那个文件的那一行去处理它。没有任何匹配时, 什么也不打印——这不是错误。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. grep "echo" todo.sh|wc -l

    找出匹配的行, -l 数这些行,于是你知道它出现了多少次。

  2. sort notes.txt|head -3

    先把行排好序,再留下前三行 —— 也就是字母序最前面的三条。

  3. find . -name "*.txt"|wc -l

    find 每找到一个就打印一行,所以数行数就是数文件数。

连接用的符号

|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 。 和 shell 是不同的词,所以 grep shell 找不到写着 Shell 的那一行。想忽略大小写,就像 grep -i shell 这样加上 -i。
  • grep 无匹配时不输出;只需判断时使用 grep -q。
  • 如果去掉,写成 find . -name *.txt,shell 会在 find 运行之前就把 *.txt 换成里的文件名(notes.txt)。这样 find 会漏掉子文件夹里的 .txt 文件;当前文件夹里有两个或更多 .txt 文件时,它还会报出 "paths must precede expression" 错误并停下。-name 后面的模式一定要加引号,例如 "*.txt"。
学习目标

本课你将做到

  1. 1

    使用 | 连接命令

  2. 2

    使用 > 保存输出

  3. 3

    使用 >>

  4. 4

    理解流

三台机器由透明的管子连在一起:第一台不断送出纸条;第二台是滚筒筛,把普通纸条漏进下面的托盘,只让带星号的纸条通过;第三台把它们叠起来数一数;最后一根管子把星号纸条经漏斗倒进一个箱子。
| 把一条命令的输出交给下一条命令,就像纸条从一台机器传到下一台机器。末尾的箭头 > 把最终结果倒进一个文件。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 命令 | 命令

    左边的输出成为右边命令的输入,中间不经过文件。

  2. 命令 > 文件

    把输出送进文件而不是屏幕。原来的内容会被。

  3. 命令 >> 文件

    做同样的事,但不,而是接在末尾。

  4. 命令 < 文件

    把文件当作的输入送进去——箭头指着文字流动的方向。

  5. 命令 2> 文件

    2 指的是,所以这样能把错误和结果分开。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
文件
命令要读取或写入的文件。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

管道 ( | )

A | 命令B 把 A 的输出交给 B 作为输入。 | txt 中,ls 列出名称,grep txt 只显示含有 txt 的行。先单独运行左边的命令查看结果,再加上 | 和下一条命令。

深入了解 每个命令都有一个(读取的地方)和一个标准输出(写入的地方——通常是屏幕)。把左边命令的输出连接到右边命令的输入,两者同时运行,所以数据一产生就流过去。管道要一步一步搭:先运行第一条命令,看看它的输出,再加上 | 和下一条命令。

02

重定向 ( >, >>, < )

> ,>> ,< 使用文件作为输入;2> 单独保存错误。

深入了解 在启动之前就设置好重定向。所以 data.txt > data.txt 会毁掉文件:> 先清空 data.txt,然后 sort 读到的是一个空文件。请写到一个新文件里(sort data.txt > sorted.txt)。数字是各个流的名字:0 是输入,1 是普通输出,2 是错误,所以 2> 只把错误送到文件,>(等同于 1>)只送普通输出。

03

既显示又保存 — tee

把输入保存到文件,同时也发送到屏幕或下一条。可以把它想成 T 形,将输出分成两路。 | tee list.txt 可以边看列表边保存。要到已有文件,用 tee -a。

深入了解 写入需要管理员权限的文件时,tee 也是常用的方法。 "text" | tee /etc/somefile 可以成功。但 sudo echo "text" > /etc/somefile 会失败。这是因为重定向(>)是由你自己的 完成的,而不是 sudo。如果只想要文件、不想在屏幕上再显示一份,就在末尾加上 > 。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

ls ~/Downloads | grep pdf | wc -l
  1. ls ~/Downloads列出 Downloads 文件夹。当输出进入时, 每行写一个名字。
  2. |管道:把这个列表交给下一条,而不是显示出来。
  3. grep pdf只保留包含 pdf 的行。
  4. |第二个管道:把剩下的行继续传下去。
  5. wc -l统计到达的行数。

屏幕上看到的

7

只有最后一条命令会写到屏幕上;其他命令只是把输出传下去。那个数字就是名字里包含 pdf 的文件个数。想看名字本身,就去掉最后一部分(| -l)再运行一次。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. ls|sort -r|head -3

    三条接连:列出来、把顺序倒过来、留下前三条。每条都只做自己那一小件事。

  2. cat notes.txt|wc -l>count.txt&&cat count.txt

    > 把数字存进文件, 再把那个文件打印出来,让你看到确实存进去了。

  3. ls|grep txt|wc -l

    先缩小范围再数 —— 回答「这样的有几个?」的常用办法。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
>
把输出写进文件。文件里原来的内容会被覆盖。
注意

初学者常见错误

  • > 会立即目标;需要保留内容时用 >>。
  • 不要把 | 和 > 弄混。 > 并不会搜索任何东西,只是把列表保存到一个名叫 grep 的新文件里。要把输出交给另一个,用 |;要保存到文件,用 >。
学习目标

本课你将做到

  1. 1

    创建并读取变量

  2. 2

    安全变量

  3. 3

    使用 $(( )) 计算

  4. 4

    使用 $( ) 获取命令结果

不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 名字=值

    把值放进自己起名的盒子里。= 两边不要留空格。

  2. "$名字"

    用 $ 把值取出来,保留,空格才不会散掉。

  3. $(命令)

    运行一个,把它打印出来的内容当作值来用。

  4. $((值))

    括号是两层就表示计算—— 会替你算出来。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
值
一个普通的值——数字、单词或一小段文字。
名字
自己起的名字,用在变量、函数和循环上。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

创建和读取变量

使用 name=value 创建,使用 $name 读取。= 两边不能有空格。

深入了解 名只能用英文字母、数字和 _(不能用汉字),而且不能以数字开头。按照习惯,自己的变量用小写,和其他共享的设置(、HOME)用大写。变量只存在于创建它的 里:打开一个新,它就不见了。要让每次会话都保留这个值,就把那一行写进 ~/.bashrc(Mac 上是 ~/.zshrc)。

02

用双引号保护值

使用 "$name" 可以保留值中的空格,这是安全 的核心习惯。

深入了解 里 $ 仍然起作用,所以 "$name" 会得到值,但双引号会保留空格,并阻止 * 变成文件名。如果后面直接跟着字母,就用大括号标出名字在哪里结束:"${name}s" 打印值再加上 s,而 "$names" 会去找一个叫 names 的变量。单引号会完全关闭变量('$name' 打印的就是 $name)。

03

算术展开与命令替换

$(( )) 放入计算结果,例如 $(( 3 + 4 )) 变成 7。$( ) 放入的输出。today=$(date) 运行 date,再把输出保存到 today。记住:这里两层括号做计算,一层括号运行命令。

深入了解 在 $(( )) 里,可以不写 $($(( count + 1 ))),还可以使用 +、-、*、/ 和 %(余数)以及括号。 只计算整数,所以 $(( 7 / 2 )) 是 3——小数部分被舍去。$( ) 则不同:它运行一条命令。两者结合,就能写出 files=$( | -l); "$(( files * 2 ))" 这样的行。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

today=$(date +%F); cp notes.txt "notes-$today.txt"
  1. today=创建一个叫 today 的。= 两边不能有空格。
  2. $(date +%F)运行 date,并把它的结果放在这里;+%F 要求日期以 2026-10-17 的形式输出。
  3. ;第一条结束——接着在同一行运行下一条。
  4. cp notes.txt复制 notes.txt ……
  5. "notes-$today.txt"…… 到这个新名字:$today 会被替换成日期,让它保持为一个名字。

屏幕上看到的

什么也没有——这类命令成功时不输出任何内容。

复制成功时什么也不打印。现在 会显示 notes-2026-10-17.txt;明天再运行同一行,就会得到一个带明天日期的新文件——一个简单的每日备份。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. count=$(ls|wc -l)&&echo "files: $count"

    $( ) 接住连起来的打印的东西,于是一整串连接变成了一个值。

  2. echo "$HOME"|wc -c

    里的值也能像别的输出一样原样递过去。

  3. first=$(head -1 notes.txt)&&echo "$first"

    把文件的第一行放进自己起的名字里,再打印出来。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • = 周围的空格会让名称被当成。
  • 未的会按空格分割,默认使用 "$name"。
学习目标

本课你将做到

  1. 1

    使用 if / elif / else

  2. 2

    使用 比较

  3. 3

    使用 for 处理列表

  4. 4

    区分数字和比较

传送带把文件箱一个个送到机械臂前;在传送带的岔口,每个箱子根据检测灯往左或往右走。
for 一次取一个项目,if 决定每个项目怎么处理。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. if [[ 条件 ]]; then 动作; fi

    if 开头,fi 收尾。条件决定动作到底要不要运行。

  2. for 名字 in 列表; do 动作; done

    列表里的项目一个一个进到名字的位置,每进一次动作就运行一次。

  3. while [[ 条件 ]]; do 动作; done

    条件为真就一直重复,所以里面必须有东西去改变这个条件。

  4. case 值 in 要找的内容) 动作;; esac

    拿一个值去比对好几种形状。每个用 ;; 结束,最后用 esac 收起全部。

每个位置填什么

要找的内容
要比对的文字或形状。用引号括起来,Shell 就不会动它。
值
一个普通的值——数字、单词或一小段文字。
名字
自己起的名字,用在变量、函数和循环上。
列表
要一个一个走过的项目。
条件
做判断的检查,结果只有真或假。
动作
轮到它时要运行的命令。
核心概念

掌握核心知识

试一试 点一下示例,它的就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

if 条件语句

if 检查条件,成立时执行 then 后面的。用 elif 检查其他条件,用 else 写所有条件都不成立时的操作,最后以 fi 结束。把 if 条件; then ... fi 读成“如果条件成立,就做这件事”。

深入了解 if 后面的条件其实是一条命令:if 运行它并检查它的,0 表示真。所以 if -q milk notes.txt; then … 不需要任何括号也能工作。把 then 和 fi 之间的行缩进,这样能看清每个块在哪里结束;如果把所有内容写在一行,别忘了 then 前面的 ;。

02

使用 [[ ]] 比较

用 ==、!=,数字用 -eq(等于)、-gt(大于)、-lt(小于)、-ge(大于或等于)、-le(小于或等于),文件和文件夹用 -f、-d。

深入了解 在 里可以用 (并且)和 ||(或者)连接测试,! 表示“不”:[[ -f notes.txt && ! -d backup ]]。拿不准时,比较中的就加上;在旧式的单方括号 [ ] 里则必须加,因为空变量会在里留下一个空洞。只涉及数字时,(( )) 读起来更自然:if (( score >= 80 )); then …

03

for 循环

for item in list; do ... done 会逐项处理列表,也可 {1..5} 或文件。

深入了解 in 后面的列表在循环开始前就了,所以 for f in *.txt 会对每个文本文件运行一次,文件名放在 $f 中。如果没有文件匹配,模式本身会被使用一次($f 变成 *.txt),所以要用 [[ -e $f ]] 检查,或者打开 (第9课)。不要对 $() 做循环;名字里有空格时,用模式更安全。

04

用 seq 重复固定次数

1 20 生成 1 到 20。for i in $(seq 1 20); do ... done 把每个数依次放进 i,重复 20 次。“for in 列表”中的 in 不能省略。固定范围也可写成 for i in {1..20}。

深入了解 seq 还能按步长计数和倒着数:seq 0 5 20 得到 0 5 10 15 20,seq 10 -1 1 则倒数。{1..20} 这样的范围不能使用变量({1..$n} 不行)。当终点来自变量时,请用 $(seq 1 "$n") 或 C 风格的 for ((i=1; i<=n; i++))。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

for f in *.txt; do echo "$f: $(wc -l < "$f") lines"; done
  1. for f in *.txt对这个文件夹里的每个 .txt 文件重复一次;每次 f 里存着一个文件名。
  2. do要重复的从这里开始。
  3. echo "$f: …"打印文件名,后面跟上它的行数。
  4. $(wc -l < "$f")统计那个文件的行数。用 < 把文件作为输入送进去, 就只打印数字。
  5. done结束——继续处理下一个文件。

屏幕上看到的

notes.txt: 3 lines
todo.txt: 12 lines

每个文件一行。循环运行的次数和 .txt 文件的个数一样多。如果一个都没有,它会以 *.txt 这段文字运行一次,wc 会报错——第9课的 可以避免这种情况。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. for f in *.txt; do wc -l "$f"; done|sort -n

    打印出来的东西汇成一股流,所以一个 -n 就把全部排好了。

  2. ls|grep -q txt&&echo "txt here"

    -q 什么都不打印,只报告成功还是失败,而 看的正是这个。

  3. if ls|grep -q todo; then echo "found"; fi

    连起来的也能当 if 的条件 —— 它递过去的是成功或失败,不是文字。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • [[ $x > 7 ]] 中的 > 比较文字,数字应使用 -gt。
  • [[ 和 ]] 内侧必须保留空格。
学习目标

本课你将做到

  1. 1

    定义并调用函数

  2. 2

    使用 $1 等参数

  3. 3

    说明 #!/bin/bash

  4. 4

    理解

一台挂着名牌的小机器:文件从入口槽进去,机器送出这个文件和一份贴好标签的备份副本。
函数就是一台有名字的机器:给它一个参数,每次都运行同样的步骤。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 名字() { 动作; }

    给一组起个名字。大括号包着主体,$1 是接到的第一个值。

  2. 名字 值

    自己写的和别的一样调用——名字在前,值在后。

  3. bash 文件

    把交给 。文件没有也能这样跑起来。

  4. chmod +x 文件

    +x 标记这个文件可以运行,只需要做一次。

  5. ./文件

    ./ 的意思是「就在这里的文件」,因为 不会翻。

每个位置填什么

文件
命令要读取或写入的文件。
值
一个普通的值——数字、单词或一小段文字。
名字
自己起的名字,用在变量、函数和循环上。
动作
轮到它时要运行的命令。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

定义与调用函数

name() { ; } 为一组命令命名,内通过 $1、$2 读取。

深入了解 先定义函数,再调用。函数中的 $1、$2 是传给该函数的参数。文字结果可用 输出,再用 result=$(myfunc) 接收。return 不返回文字:return 0 表示成功,return 1 等非零值表示失败,取值范围为 0~255。

02

脚本与 shebang

是把多条保存到 hello.sh 这样的文件中。首行 叫作 shebang,表示用 ./hello.sh 直接选择 。因此,即使当前 是 zsh,脚本也由 bash 执行。也可以用 bash hello.sh 直接指定执行。

深入了解 shebang 必须是第一行,并且必须正好以 #! 开头。很多脚本改用 #!/usr/bin/env bash:env 会找到 bash 安装在哪里,这在 bash 不在 /bin 里的系统上很有用。.sh 后缀只是给人看的提示;决定由哪个程序运行文件的是 shebang,而不是文件名。

03

权限与参数

先执行 +x hello.sh,再运行 ./hello.sh。使用 $1、$2、$@、$# 读取。

深入了解 ./ 的意思是“这个文件夹里的文件”。 只在 列出的文件夹里查找,而不在其中,所以单独输入 hello.sh 会得到 'command not found'。 -l 会显示权限:-rwxr-xr-x 里含有 x,所以这个文件可以运行。$0 是自己的名字,带的 "$@" 会把每个参数原样传下去,即使参数里有空格。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

backup() { cp "$1" "$1.bak" && echo "saved $1.bak"; }
backup notes.txt
  1. backup()定义一个叫 backup 的。
  2. { … ; }函数体。{ 后面的空格和 } 前面的 ; 都是必需的。
  3. cp "$1" "$1.bak"把第一个复制一份,名字后面加上 .bak。
  4. && echo …打印一条消息——只在复制成功时。
  5. backup notes.txt调用函数:在函数内部,$1 变成 notes.txt。

屏幕上看到的

saved notes.txt.bak

第一行只是定义函数,什么也不打印。第二行运行它并打印消息。换一个文件名调用,同样的步骤就会在那个文件上运行——这正是函数的意义所在。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. bash hello.sh|wc -c

    打印的东西,也能像任何的输出一样继续往下递。

  2. chmod +x hello.sh&&./hello.sh

    标记成可运行再运行,一行写完 —— 后半只有在前半成了之后才发生。

  3. bash todo.sh|head -1

    只看打印出来的第一行。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 未执行 +x 时可能出现 ,也可用 hello.sh。
  • 用 "$1" 以保留空格。
学习目标

本课你将做到

  1. 1

    通过 查看成功失败

  2. 2

    使用 与 || 连接处理

  3. 3

    使用 for 处理多个文件

  4. 4

    理解 定时执行

机器人工人沿着传送带传递箱子;前两个工位上方亮着绿灯,箱子继续往前走;第三个工位上方的红灯让整条线停下,一位主管探身过来查看。
每条结束时都会留下一个退出码:0 是绿灯,其他数字都是红灯。脚本可以检查它,在出错的一步弄坏下一步之前停下来。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 命令 && 命令

    只有前一个成功了,才运行后一个。

  2. 命令 || 命令

    只有前一个失败了,才运行后一个——这是你的安全出口。

  3. 命令 >> 文件 2>&1

    把结果和都收进同一个记录文件。

  4. 时刻 命令

    的格式——五个时间位置,后面跟。它在自己的电脑上运行,这个里不会跑。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
文件
命令要读取或写入的文件。
时刻
什么时候运行——分、时、日、月、星期五个位置。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

退出码与 $?

成功返回0,失败返回其他值。 读取上次结果,A B 只在 A 成功时运行 B。

深入了解 非零的退出码由每个自己决定——例如 在没有匹配时返回 1,出错时返回 2。A || B 和 && 正好相反:只有 A 失败时才运行 B,例如 backup || "could not create"。成功的以 exit 0 结束,失败的以 exit 1(或其他数字)结束,这样运行它的一方——人、 或另一个脚本——就能检查结果。

02

自动化重复工作

把和文件组合起来,即可对许多目标自动执行相同工作。

深入了解 改动文件前,在命令前加 预览要执行的内容,这叫作。备份名称中加入 $(date +%F) 可以按日期区分,但同一天再次运行仍会重名。要为每次执行保留独立备份,还需加入时间。每一步输出提示,更容易看清进度。

03

定时执行 — cron

用于定时运行。用 crontab -e 打开配置,依次填写“分 时 日 月 星期”五个,再写要运行的命令。0 3 * * * 表示每天凌晨 3 点。下方的计划行应写进 crontab,不要当作命令直接执行。

深入了解 五个字段依次是分钟(0–59)、小时(0–23)、日期、月份和星期几(0 = 星期日)。* 表示“每”;分钟字段里的 */15 表示每 15 分钟;星期字段里的 1-5 表示星期一到星期五。cron 的运行环境里 非常短,也没有终端,所以要使用完整,并把输出送到日志里,否则你永远看不到错误。crontab -l 会列出你当前的任务。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

*/15 9-18 * * 1-5 /home/you/check.sh >> /home/you/check.log 2>&1
  1. */15分钟:每 15 分钟(0、15、30、45)。
  2. 9-18小时:从 9 点到 18 点。
  3. * *每月的任何一天、任何月份。
  4. 1-5星期几:星期一(1)到星期五(5)。
  5. /home/you/check.sh要运行的,带完整—— 从你的主文件夹开始,而不是从脚本所在的文件夹。
  6. >> /home/you/check.log把它的输出到日志文件末尾。
  7. 2>&1把错误也送到同一个日志里,方便以后查看。

屏幕上看到的

什么也没有——这类命令成功时不输出任何内容。

这一行不是在下输入的:用 crontab -e 保存它,cron 就会自己运行——工作日从 9:00 到 18:45,每 15 分钟一次。里什么也不会出现;打开 check.log 查看发生了什么,用 crontab -l 确认这一行已经保存。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. mkdir -p logs&&ls>logs/list.txt&&cat logs/list.txt

    三步,每一步都等前一步成功 —— 就是这样不出事的。

  2. bash todo.sh>out.txt2>&1&&cat out.txt

    2>&1 把送进和结果相同的文件,于是一份记录里装着全部经过。

  3. cat missing.txt||echo "no such file"

    是 && 的另一面 —— 正因为前面失败了,这句话才出现。

连接用的符号

2>&1
把错误信息送到和结果相同的地方。
||
只有前面失败了,才运行后面。
&&
只有前面成功了,才运行后面。
>
把输出写进文件。文件里原来的内容会被覆盖。
注意

初学者常见错误

  • 关键失败时使用 command exit 1 停止。
  • 给加。 从主文件夹开始运行, 也非常短,所以和命令都要写绝对路径。
学习目标

本课你将做到

  1. 1

    说明三种引用方式

  2. 2

    理解单词分割与通配

  3. 3

    使用 set -euo pipefail

  4. 4

    始终引用

工作台前,一个人正要按下连着碎纸机的大红按钮,但他先看了看预览屏幕,上面有一个眼睛图标和一张打了勾的检查清单;旁边,一叠纸条被两个夹子夹在一起。
运行会删除或文件的之前,先看一看——先列出它会改动的文件。引号就像夹子,让带空格的名字不会散开。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 命令 "对象"

    带空格的名字只有在里才算一个对象。没有引号就看成两个。

  2. "$名字"

    里 $ 依然会把值取出来,但会把值保持成一整块。

  3. '文字'

    表示「就是这些字符」——连 $ 也失去作用。

  4. 命令 -- 对象

    -- 后面的东西不会当成来读,这能救下以 - 开头的名字。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
对象
对什么做——文件或文件夹的名字,或者路径。
名字
自己起的名字,用在变量、函数和循环上。
文字
要打印出来的文字。里面有空格就用引号括起来。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

三种引用方式

不同,的方式也不同。

  • "$var"(双引号) — 展开变量,但保留空格。
  • '$var'(单引号) — 里面的一切都保持字面值。
  • 未加引号的 $var — 会按空格分割,* 这样的字符还会展开成文件名。

拿不准时,用双引号包起来是基本原则。

深入了解 做个小测试就能看出区别:当 name="a b" 时, $name 打印 a b(空格被压缩了),而 echo "$name" 打印 a b。要在文本里放一个单引号,就用双引号把它括起来("it's")。在双引号里只有少数几个字符仍然特殊——$、`、" 和 \——在它们前面加反斜杠就能保护它们。

02

单词分割与通配

先按空白拆分未加的(分词),再把其中的 *、? 为匹配的文件名(展开)。如果 file 的值是 my notes.txt, $file backup/ 会把它当成两个文件。写成 cp "$file" backup/ 就能作为一个名称传入。

深入了解 分割使用的是 中的字符——默认是空格、制表符和换行符。接着,通配会把 *、? 和 [abc] 替换成匹配的文件名,所以一个保存着 * 的变量可能突然变成文件夹里所有的文件名。这两件事都只会发生在没有加引号的展开上;在双引号里永远不会发生。

03

防御式选项

set -euo pipefail 有助于及早发现中的错误。各作用如下。

  • -e:失败时停止,但 if 条件等检查执行结果的位置有例外。
  • -u:使用未设置的时报错。
  • pipefail:让中间命令的失败也影响整体退出状态。

这些选项无法防止所有错误。还要用 检查,并确认实际结果。

深入了解 把 set -euo pipefail 放在 紧接着的下一行,让它作用于整个脚本。shellcheck 用包管理器安装(brew install shellcheck、 apt install shellcheck)。它能在很多里工作,在你输入时就给错误加上下划线。它的警告带有像 SC2086(缺少)这样的编号,每个编号都有一个页面解释如何修正。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

cp -- "$src" "$dest/"
  1. cp:复制。
  2. --结束:-- 后面的所有内容都是名字,即使以 - 开头。
  3. "$src"带的源:带空格的名字也保持为一个。
  4. "$dest/"带引号的目标文件夹;结尾的 / 表明它必须是一个文件夹。

屏幕上看到的

什么也没有——这类命令成功时不输出任何内容。

复制成功时什么也不打印。没有引号的话,像 'my report.txt' 这样的名字会被拆成两个参数;没有 -- 的话,以 - 开头的名字(比如 -r)会被当成选项。两道保护都在,不管值里有什么,每个值都会正好作为一个名字传过去。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. ls *.txt|wc -l

    先把星号填好,所以 -l 数的是真实的文件名。

  2. wc -l "report 2024.txt"|head -1

    把带空格的名字守成一个文件;没有引号就看成两个了。

  3. grep 'e' "report 2024.txt"|wc -l

    要找的内容用,带空格的文件名用双引号,然后再数。

连接用的符号

|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 多数 事故源于遗漏,请养成始终引用的习惯。
  • 在 中使用 [[ $a = b $c = d ]]。
学习目标

本课你将做到

  1. 1

    用 node -v 查看 Node.js 版本

  2. 2

    用 node app.js 运行 JavaScript 文件

  3. 3

    用 npm 安装包并运行脚本

  4. 4

    用 npx 在不的情况下运行一次工具

笔记本电脑的终端里有一个绿色六边形引擎;一辆送货卡车把贴着标签的包裹运进项目文件夹,旁边放着一张清单。
Node.js 在终端里运行 JavaScript,npm 负责送来 里列出的包。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. node 文件

    把 文件交给 Node,它就照着运行。

  2. 命令 选项

    --version 几乎在所有工具上都通用,是确认是否装好的最快方法。

  3. npm 动作

    在第二个位置接子——比如 run、install、init。

  4. npm 动作 选项 对象

    在 install 后填写包名。连接互联网后即可运行。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
选项
决定怎么做。以 - 或 -- 开头,可以不写,也可以写好几个。
对象
对什么做——文件或文件夹的名字,或者路径。
文件
命令要读取或写入的文件。
动作
轮到它时要运行的命令。
核心概念

掌握核心知识

试一试 点一下示例,它的就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

什么是 Node.js?

是在之外(、)运行 的()。制作网页的工具、(CLI)工具、构建和许多 编程助手都依赖它。学完 后掌握 Node,安装开发工具会轻松很多。

深入了解 Node.js 使用和 Chrome 相同的 JavaScript 引擎(V8)。但它不是用来显示网页的,而是读写文件、建立网络连接、运行其他程序。偶数版本(22、24 等)会成为长期支持(LTS)版本,多年都能得到修复,所以学习时选它最稳妥。可以从 nodejs.org 安装,也可以用 安装(见进阶内容)。

02

node 命令

用 -v(或 --version)查看版本。node app.js 运行文件;node -e "console.log(1+1)" 运行一行。

深入了解 单独输入 node 会打开 ——一个交互式(>),每行 都会立即运行;用 .exit 或 Ctrl+D 退出。可以从 process.argv 读取:运行 node greet.js Mina 时,process.argv[2] 是 "Mina"。console.log 写到,所以 Node 也能像其他一样放进里(node report.js | total)。

03

npm 与 package.json

安装和管理可复用包。项目的 记录名称、版本、所需包(dependencies)和运行()。在该文件夹运行 npm install 安装包,用 npm run start 执行 start 脚本。 可以运行包提供的命令,无须。

深入了解 npm install 会创建两样东西。_modules 是下载的代码——通常非常大;不要修改它,也不要它。 列出安装的确切版本——需要提交它,这样每个人得到的都一样。npm ci 严格按照锁文件安装,自动构建用的就是它。像 ^4.17.21 这样的版本表示“这个版本或兼容的更新版本”。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

npm install --save-dev prettier
  1. npm 自带的包管理器。
  2. install下载一个包并把它加入项目。
  3. --save-dev记录在 下:开发时需要,应用真正运行的时候不需要。-D 是简写。
  4. prettier包名——这里是 Prettier,一个把整理得整整齐齐的工具。

屏幕上看到的

added 1 package, and audited 2 packages in 2s
found 0 vulnerabilities

会报告它添加了多少个包(一个包可能会带来其他包),并运行一次快速的安全检查(audit)。现在 package.json 的 devDependencies 下列出了 prettier, prettier --write . 就可以在项目上运行它。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. node app.js|wc -l

    Node 打印出来的也只是文字,所以 能像数别的一样数它。

  2. cat package.json|grep name

    读文件,只留下你真正关心的那一行。

  3. node --version&&npm --version

    两个检查写在一行;前一个工具真的在,后一个才会运行。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 若 或 提示 not found,说明尚未安装 。请从 nodejs.org 或用 安装后重新打开。
  • npm install -g 容易造成权限与安全问题。优先使用 nvm,或在不用 sudo 的情况下配置。
学习目标

本课你将做到

  1. 1

    说明 是许多工具安装的前提

  2. 2

    了解 Claude Code、Codex、Grok、 的常见安装命令

  3. 3

    区分(-g)与一次性运行()

  4. 4

    养成安装后检查版本的习惯

不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 命令 选项

    问工具的版本号,确认它真的装好了。

  2. command -v 命令

    存在就打印,不存在就什么都不打印。

  3. 命令 动作 选项 对象

    安装大多是这个格式——工具、子命令、、包名。请在自己的电脑上用。

  4. curl 选项 值 -o 文件

    从网址下载,并保存到 -o 后面的文件。联网后也能运行。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
选项
决定怎么做。以 - 或 -- 开头,可以不写,也可以写好几个。
对象
对什么做——文件或文件夹的名字,或者路径。
文件
命令要读取或写入的文件。
值
一个普通的值——数字、单词或一小段文字。
动作
轮到它时要运行的命令。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

为什么要在终端安装工具?

现代开发不只靠。下面这些工具都在里运行。

  • 编程助手 — Grok Build、OpenAI Codex、Claude Code
  • — Firebase tools
  • 包管理器 — pnpm 等

第10课学过的 Node 和 就是起点。

深入了解 这些工具大多需要一个账号:你在里登录一次(例如 login),工具就会记住你。它们都是 里的普通,所以本课程里的一切——、、——对它们同样适用。AI 编程助手可以替你运行,这让理解一条命令在做什么变得更重要,而不是更不重要。

02

常见安装(简要)

官方方式可能变化,请始终以官方文档为准。常见的起点如下。

  • Claude Code: -fsSL ://claude.ai/install.sh | (或 install -g @anthropic-ai/claude-)
  • OpenAI Codex:curl -fsSL https://chatgpt.com/codex/install.sh | sh(或 npm install -g @openai/codex)
  • Grok Build:官方安装,或 npm install -g @xai-official/grok
  • :npm install -g firebase-tools

安装后用 claude --version、codex --version、firebase --version 这样确认版本。

深入了解 安装后找不到时,先打开新的,让更新后的 生效。手动重新加载设置时,应使用当前 对应的文件,例如 zsh 的 ~/.zshrc、bash 的 ~/.bashrc。更新应沿用安装方式。npm 工具可用 npm install -g firebase-tools@latest 更新;删除则在 npm uninstall -g 后写包名。

03

安全安装习惯

| 只使用可信官方。 权限出错时,优先修复 而不是 。团队与 中用 固定版本更安全。

深入了解 先把安装下载下来,运行之前读一读。用 curl -fsSL -o install.sh 保存,用 less install.sh 阅读,然后运行 bash install.sh。在 -fsSL 中,-f 遇到错误就停止,-s 保持安静,-S 仍然显示错误,-L 跟随跳转。永远不要从随便哪个视频或论坛里粘贴 curl | bash 这样的;要从工具的官方网站复制。

深入了解

逐段读懂一行命令

把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。

curl -fsSL https://claude.ai/install.sh -o install.sh
  1. curl从一个网址下载。
  2. -fsSL四个短写在一起:遇到错误就失败、保持安静、但仍显示错误、跟随跳转。
  3. https://claude.ai/install.sh安装的官方地址。
  4. -o install.sh把它保存到文件里,而不是马上运行。

屏幕上看到的

什么也没有——这类命令成功时不输出任何内容。

下载成功时什么也不打印。用 less install.sh 阅读这个文件,然后用 install.sh 运行它。如果用 | bash 代替 -o install.sh,脚本会在你看都没看的情况下立即运行。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. cat tools.md|grep -i git|head -3

    读一遍,只留讲 的行(-i 不分),再只看其中三行。

  2. which bash&&bash --version|head -1

    先找到这个,然后才问它的版本。

  3. ls|sort|head -2

    最朴素的连接 —— 列出来、排序、截断。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 来源不明的 | 有风险。请先确认官方。
  • 名称相似但包名不同。请使用 @anthropic-ai/claude-、@openai/codex、 等准确名称。
学习目标

本课你将做到

  1. 1

    能用 -c 和 -e 执行其他语言的单行

  2. 2

    能把程序保存为文件并用 python3 文件.py 运行

  3. 3

    能用 heredoc(<<PY)把多行程序交给

  4. 4

    能用 sed 批量修改文件中的文本

  5. 5

    能用 awk 从表格数据中并汇总

  6. 6

    能在运行 AI 给出的代码前先安全确认

不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. awk '{动作}' 文件

    对每一行运行你写的动作。$1 是。

  2. awk '条件 {动作}' 文件

    在大括号前面放一个条件,只有符合的行才会执行动作。

  3. sed 's/要找的内容/值/' 文件

    s 表示替换——找这个,换成那个。斜杠就当作分隔的线来读。

  4. python3 文件

    把 .py 文件交给 ,它就照着运行。

  5. python3 -c '动作'

    -c 不用文件,就地运行一行 。

每个位置填什么

文件
命令要读取或写入的文件。
要找的内容
要比对的文字或形状。用引号括起来,Shell 就不会动它。
值
一个普通的值——数字、单词或一小段文字。
条件
做判断的检查,结果只有真或假。
动作
轮到它时要运行的命令。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

Shell 是指挥,不是独奏者

只用 完成所有计算很快就会碰壁。Shell 的角色是决定"何时叫谁":计算和数据交给 ,表格汇总交给 ,文本批量修改交给 ,再由 Shell 把结果串起来。懂 Shell,就能调用自己从没学过的语言。

02

只传一行 — -c 与 -e

多数语言都有"把当作直接运行"的: 是 -c "代码", 是 -e "代码",Perl 是 -e,Ruby 是 ruby -e。只要养成用 python3 -c "print(2**10)" 代替计算器的习惯,就会强大得多。

03

保存为文件再运行

如果一段不是用一次就丢,就把它存成文件。把写进 hello.py,输入 hello.py 就能直接运行。

用第 7 课学过的 会更简单。

  • 在文件第一行写上 #!/usr/bin/env python3。
  • 执行 +x hello.py 赋予。
  • 之后不用在前面加 python3,只写 ./hello.py 就能跑。

运行结束后输入 ,就能看出是成功(0)还是失败(1)。所以可以像第 8 课那样,用 接上下一条。

04

传多行 — heredoc

用 可以一次传入多行。输入 - <<'PY',从下一行开始写 ,最后单独写一行 PY。两个标记之间的内容会交给 Python。- 表示从而非文件读取代码。编程也会用这种形式执行多行代码。

05

<<'PY' 与 <<PY 并不相同

用包住(<<'PY'),正文会原封不动地传过去;不包(<<PY), 会先替换正文里的 $ 和 $()。 里很少出现 $,但或中一旦有 $ 就会悄悄出错,所以传代码时以加引号为默认做法。

06

sed — 查找替换的流编辑器

用 替换文字的基本形式是 s/查找/替换/。

  • sed 's/查找/替换/' — 只改每行遇到的第一处。
  • 末尾加 g(s/…/…/g)— 改这一行的全部。
  • 在前面加行号或 // — 挑选目标行(2s/…/…/、/error/s/…/…/)。
  • 把 -n 和 p 配合使用 — 只输出选中的行。
  • 不必是 /。替换时写成 s|/old/path|/new/path|,会好读得多。

要直接改文件本身,就写 sed -i.bak 's/查找/替换/' 文件。它会把原文件保留为 文件.bak,所以很安全(macOS 和 的差别请看下面的“注意”)。

07

awk — 处理表格的小型编程语言

既是一门小语言,也是处理表格文本的。它按空白拆分每一行,$1 是,$2 是第二列,NF 是列数,NR 是行号。awk '模式 { 操作 }' 用模式选择行,用 { } 指定操作。省略模式会处理所有行,省略操作会输出匹配行。BEGIN 在读取前执行一次,END 在读完后执行一次,可用于准备和汇总。

08

用管道把各种语言串起来

从读、向标准输出写的之间,可以用自由连接。例如 sales.txt | '{print $2}' | -n | -3 一行就完成这些事。

  • 读文件
  • 只取
  • 按数值排序
  • 只留最大的三个

awk 与 就这样用管道相连,而 则通过 -c 或 接收。这就是“ 是指挥”的真正含义。

09

安全地运行 AI 给的代码

通常正是用这种 形式作答。粘贴之前只需确认三件事:①用眼睛读一遍它要改哪些文件;②看看有没有 或 > 这类操作;③重要文件先用 原文件 原文件.bak 备份。会读 ,就意味着能验证 AI 的答案。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. awk '{print $1}' sales.txt|sort|uniq -c

    取出, 把相同的行聚到一起, -c 给每一组计数。

  2. python3 hello.py|wc -c

    打印, 计数 —— 彼此完全不必知道对方。

  3. sed 's/=/ /' config.txt|awk '{print $1}'

    把 = 换成空格, 才能把名字当作来读。

连接用的符号

|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 一定要用包起来。用双引号时 会把 $1 当成自己的替换成空:awk '{print $1}' 可以,awk "{print $1}" 则不行。
  • 的结束标记必须写在行首,前后不能有空格,且拼写完全一致。需要缩进时用 <<- 并以制表符缩进。
  • 传时以 <<'PY' 这样给标记加引号为默认做法。不加引号,代码里的 $ 会被替换成 Shell 变量而悄悄出错。
  • -i 会不可撤销地改写文件。先不加 -i 运行、在屏幕上确认结果,再加上它。
  • 在 macOS 上像 sed -i 's/…/…/' 文件 这样不写备份名,会报错(错误名随文件名而不同,如 invalid code)而文件原封不动 —— macOS 的 sed 要求 -i 后面跟名字。写成 sed -i.bak,同一条命令在 macOS 和 上都能通过。
  • sed 's/a/b/' file > file 会先清空文件再读取,内容因此丢失。请写到不同的文件名。
学习目标

本课你将做到

  1. 1

    能用 . * + ? [ ] ^ $ 等基本符号描述文本的形状

  2. 2

    能用 grep -E 找出匹配的行,并用 -o 只取出匹配的部分

  3. 3

    能用 sed -E 和 调换和修正文本

  4. 4

    能说明和的区别,以及为什么 \d 在 grep -E 中不是数字

  5. 5

    能在 Shell 脚本中用 [[ 值 =~ 模式 ]]、在 Python 中用 re 模块检查值

  6. 6

    能选用 macOS、Linux 和 Windows 自带的正则命令

一条摆满各种形状的长带子从一张模板卡片下面经过,卡片上镂空了星星和圆圈;对得上的紫色星星和圆圈亮起来,一块磁铁沿着弧线把它们吸起,放进篮子里,灰色的形状留在原地。
正则表达式是文字的模板:grep 把它放到每一行上比一比,只挑出符合这个模式的部分。
不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. grep -E '要找的内容' 文件

    -E 打开更完整的形状语法,在那里 | 表示「或者」。

  2. grep -oE '要找的内容' 文件

    -o 只打印匹配上的那一段,而不是整行。

  3. sed -E 's/要找的内容/值/g' 文件

    g 会把一行里匹配上的全部替换掉,而不只是第一个。

  4. [[ 值 =~ 要找的内容 ]]

    =~ 直接在 里把值和形状比对一下。

每个位置填什么

文件
命令要读取或写入的文件。
要找的内容
要比对的文字或形状。用引号括起来,Shell 就不会动它。
值
一个普通的值——数字、单词或一小段文字。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

正则表达式描述文本的“形状”

搜索 010-1234-5678 只能找到这个号码。要找同样 3-4-4 位数字的格式,就写 [0-9]{3}-[0-9]{4}-[0-9]{4}。[0-9] 表示一位数字,{3} 表示重复三次。这种搜索规则就是。、、、vim、、、PHP 的基础符号相似,但细节语法需要分别确认。

02

选一个字符的符号

. 表示任意一个字符(a.c 能匹配 abc 和 a1c)。[abc] 表示方括号里的某一个字符,[^abc] 表示不在其中的一个字符,[a-z] 或 [0-9] 表示某个范围内的一个字符。常用的字符集合有简写:\d 是数字,\w 是单词字符(字母、数字或 _),\s 是空白。大写(\D \W \S)表示相反的意思。 工具还接受 [[:digit:]]、[[:space:]] 这样的 名称。

03

表示次数的符号

* 表示前一个字符出现 0 次或更多次,+ 表示 1 次或更多次,? 表示 0 次或 1 次(colou?r 能匹配 color 和 colour),{n} 表示正好 n 次,{n,m} 表示 n 到 m 次。重复符号只作用于紧挨在它前面的一个字符(或一个方括号、一个分组)。而且重复会尽可能多地抓取():".*" 会从行里第一个一直取到最后一个引号,所以想停在一对引号之内,就写 "[^"]*"。

04

表示位置的符号 — ^ $ \b

^ 是行首,$ 是行尾。^error 匹配以 error 开头的行,done$ 匹配以 done 结尾的行,^$ 匹配空行。\b 是单词边界,所以 \bart\b 能找到 art,却不会匹配 party。这些符号不占用任何字符,只标出一个位置。别把它们和方括号开头的 ^([^0-9])搞混——那表示“不是”,意思完全不同。

05

分组与“或” — ( ) |

red|blue 表示 red 或 blue。( ) 把几个字符变成一个整体——(ab)+ 能匹配 ab、abab 和 ababab。分组还能把找到的内容取出来:在 中用 \1 \2,在 中用 ${BASH_REMATCH[1]},在 中用 m.group(1)。所以把 2026-10-15 变成 15.10.2026 只需要一行。

06

把符号当普通字符用 — 转义

. * + ? ( ) [ ] { } ^ $ | \ 都有特殊含义。想找这个字符本身,就在前面加 \:点写成 \.,星号写成 \*,左括号写成 \(。如果照原样写 192.168.0.1,每个 . 都表示“任意字符”,于是 192x168y0z1 也会匹配。另外,在 中一定要用把模式括起来,否则还没看到它们,Shell 就已经按自己的方式改动了 * ? $ { }。

07

基本语法与扩展语法 — 为什么要加 -E

在 和 中会遇到三种语法:

  • ——grep 和 sed 默认读取的语法。+ ? | ( ) { } 都是普通字符,要把它们当功能用,就得加反斜杠写成 \+ \| \(。
  • ——加上 -E(grep -E、sed -E),符号可以照原样写。拿不准时就一律加 -E。
  • (PCRE)——使用 \d 这样的符号。所以在 grep -E 中,\d 是字母 d,不是数字。

在 工具里写 [0-9];如果确实想用 \d,就用 grep -P()。、、PHP 和 (rg) 天生就认识 \d。

08

macOS、Linux 和 Windows 自带的命令

macOS 和 自带 、、、find 和 。不过各个操作系统略有不同。

  • macOS — grep 和 sed 是 。没有 grep -P,要用 [[:space:]] 代替 \s,用 sed -i 编辑文件时后面还要跟 ''。
  • Linux — 是 GNU 版本,-P、\s 和 \+ 都能用。
  • Windows — 有 , 有 、-match 和 -replace。PowerShell 能直接理解 \d。装好 或 ,也能使用 grep 和 sed。

下面的“各操作系统自带的”表汇总了示例。

09

在 Shell 脚本和 Python 中使用正则

在 中,用 [[ $value =~ 模式 ]] 检查一个值。

  • 匹配时结果为真,所以可以在后面接 或放进 if 里。
  • 用 ( ) 括起来的部分会存进 ${BASH_REMATCH[1]}。
  • 给模式加,bash 会把它当普通文本比较。所以模式要不加引号地写,或者先放进,如 re='…',再用 [[ $value =~ $re ]]。

在 中先 import re,再用下面的。模式写成前面带 r 的原始,如 r"\d+"。

  • re.search — 查找
  • re.findall — 全部查找
  • re.sub — 替换
  • re.fullmatch — 检查整段文本
前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. grep -E "ERROR|WARN" app.log|wc -l

    注意:里的 | 对 来说是「或者」,而外面那个 | 才是连接的符号。

  2. grep -o "user=[a-z]*" app.log|sort|uniq -c

    只取出匹配的部分,排好序,再数每一个出现了多少次。

  3. python3 regex.py|head -3

    只看 打印出来的前三行。

连接用的符号

|
把输出递给下一个命令,中间不经过文件。
一目了然

正则符号表

常用符号的含义、示例,以及该示例能匹配的文本。点击示例,它会放进下方的测试器,让你亲眼看到效果。

符号 含义 示例 匹配
. 任意一个字符 abc · a1c
* 前一个字符出现 0 次或更多次 ac · abbc
+ 前一个字符出现 1 次或更多次 abc · abbc
? 前一个字符出现 0 次或 1 次 color · colour
{n,m} 重复 n 到 m 次 12 · 2026
^ 行首 error: disk full
$ 行尾 all done
[abc] 方括号里的某一个字符 a · e
[^abc] 不在方括号里的一个字符 x · -
[a-z] 某个范围内的一个字符 Q · k
| 或 red · blue
( ) 把几个字符组成一个整体 ab · abab
\ 把符号当普通字符用(转义) .

简写符号

符号 等同于 含义 示例
\d [0-9] 一个数字 2026
\w [A-Za-z0-9_] 一个单词字符(字母、数字或 _) user_1
\s [ \t\n] 一个空白字符(空格、制表符、换行) a b
\D \W \S [^0-9] … 各自的反义——不是数字、单词字符或空白 abc
\b \< \> 单词边界——\bart\b 能找到 art,但会跳过 party art
[[:space:]] \s POSIX 名称——在包括 macOS 在内的任何地方的 grep 和 sed 中都能用的安全写法 42
BRE · ERE · PCRE

基本语法与扩展语法

和 默认读取,所以 + ? | ( ) { } 前面要加 \ 才能当功能用。加上 -E 就是,符号可以照原样写。

功能 基本——grep、sed 扩展——grep -E、sed -E
1 次或更多次 \+ +
0 次或 1 次 \? ?
或 a\|b a|b
分组 \( \) ( )
重复 n 到 m 次 \{n,m\} {n,m}

照原样书写符号的方法 grep -E sed -E awk [[ =~ ]] grep -P vim \v

\d \w \s 是 的符号。在 grep -E 中,\d 是字母 d 而不是数字,所以请写 [0-9]。需要 \d 时,用 grep -P()、 或 。使用 macOS 上的 grep 和 sed 时,[[:space:]] 比 \s 更稳妥。

在你的电脑上

各操作系统自带的命令

无需安装就能马上使用的。请在标签页中选择你的操作系统。

  • grep -E 找出匹配模式的行(,没有 -P) grep -E 'ERROR|WARN' app.log
  • sed -E 查找并替换——编辑文件时 -i 后面要跟 '' sed -E -i '' 's/[[:space:]]+$//' notes.txt
  • awk 从匹配的行中只打印 awk '/ERROR/ {print $1, $3}' app.log
  • find -E 用查找文件名(-E 为) find -E . -regex '.*\.(jpg|png)'
  • perl 使用 \d 的 替换——系统自带 perl -pe 's/(\d+)-(\d+)/$2-$1/g' a.txt
  • zsh [[ =~ ]] 在 macOS 默认的 中检查一个值 [[ $v =~ ^[0-9]+$ ]] && echo number
  • python3 的 re 模块——第一次可能会提示安装 python3 -c "import re; print(re.findall(r'\d+', 'a1b22'))"

macOS 默认的 Shell 是 zsh, 和 是 BSD 版本。想要和 完全一样,请用 brew install grep gnu-sed 安装 GNU 版本(ggrep、gsed)。

  • grep -E · -P · -o ——用 -P 支持 \d,用 -o 只取匹配部分 grep -Po '\d{4}-\d{2}-\d{2}' app.log
  • sed -E -i 就地编辑文件——GNU 也认识 \s 和 \+ sed -E -i 's/\s+$//' notes.txt
  • awk 给某一列()加上条件 awk -F: '$1 ~ /^www/' /etc/passwd
  • find -regex 用正则查找文件 find . -regextype posix-extended -regex '.*\.(php|js)'
  • bash [[ =~ ]] 检查一个值,并从 取出 [[ $s =~ ([0-9]+) ]] && echo "${BASH_REMATCH[1]}"
  • git grep 只在 内快速搜索 git grep -n -E 'TODO|FIXME'
  • python3 大多数发行版都自带的 python3 -c "import re; print(re.sub(r'\s+', ' ', 'a b'))"

上的 grep 和 sed 是 GNU 版本,所以表现和本课的完全一样。

  • findstr 在中搜索——只支持部分(没有 + ? |) findstr /R /N "^ERROR" app.log
  • Select-String 的 ——找出匹配模式的行 Select-String -Path app.log -Pattern 'ERROR|WARN'
  • -match 匹配时为真;分组存进 $Matches '2026-10-15' -match '^\d{4}-\d{2}-\d{2}$'
  • -replace 查找并替换——能直接理解 \d 和 \s (Get-Content a.txt) -replace '\s+$', '' | Set-Content a.txt
  • [regex]::Matches 找出所有匹配 [regex]::Matches('a1b22', '\d+').Value
  • Where-Object 用正则挑选文件名 Get-ChildItem | Where-Object Name -match '\.(jpg|png)$'
  • WSL · Git Bash 装上 或 ,就能原样使用 wsl grep -E 'ERROR|WARN' app.log
  • py 安装 后用 py 运行 py -c "import re; print(re.findall(r'\d+', 'a1b22'))"

PowerShell 使用 .NET 正则,所以 \d \w \s 和 都能直接用。命令提示符里的 能做的事少得多,所以推荐用 PowerShell。

一目了然

常用的实用模式

可以复制后马上使用的模式。点击一个,它会载入测试器,让你看到它在哪里匹配。

练习窗口

正则测试器

修改模式,下方文本中匹配的部分会立刻亮起来。文本也可以随意修改。

匹配与分组

测试器使用 ,语法与 工具并不完全一致。例如这里的 \d 表示数字,但 -E 中应使用 [0-9]。换成 grep -P ,也要检查中的实际结果。

注意

初学者常见错误

  • 一定要用把模式括起来。如果像 -E [0-9]{1,3} 这样直接写, 会把 {1,3} 成 1 和 3,还会把 * 和 ? 变成文件名。
  • grep 默认是,所以 grep 'a|b' 里的 | 只是一个字符。要把或(|)、+ 和 ? 当功能用,请加上 grep -E。
  • 在 grep -E 或 -E 中 \d 不是数字(而是字母 d)。在 Shell 工具中用 [0-9];如果确实需要 \d,就用 grep -P 或 。
  • . 表示“任意字符”。要找点号,请写 \.。照原样写 192.168.0.1,连 192x168y0z1 也会匹配。
  • 在 Shell 工具的方括号里,\ 只是普通字符。[^\s] 表示“除 \ 和 s 之外的任意字符”,所以非空白字符要写成 [^[:space:]] 或 [^ ]。
  • .* 会尽可能多地抓取。想只匹配一对引号里的文本,就用“不是那个字符”来缩小范围,如 "[^"]*"。
  • 像 [[ $v =~ "^[0-9]+$" ]] 这样给模式加引号, 会按普通文本比较,而不是按。请不加引号地写,或者放进(re='…')。
  • macOS 上的 sed 和 grep 是 ,\s、\+ 和 grep -P 可能用不了。用 -E 和 [[:space:]],同一条在 macOS 和 上都能用。
学习目标

本课你将做到

  1. 1

    能把任何命令按位置读出来——做什么、怎么做、对什么做

  2. 2

    能分清选项的四种形状——-l、--all、-la、-n 5

  3. 3

    会在对象位置上用,并知道是 Shell 先把它填好

  4. 4

    能用 --help 和 man 自己查出陌生命令的模式

不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. 命令 选项 对象

    如果只记一个,就记这个格式——做什么、怎么做、对什么做。

  2. 命令 --help

    几乎每个都会在这里告诉你它自己的模式。这就是学会陌生命令的路。

  3. 命令 选项 对象 到哪去

    先接来源,再接目标。并非所有双都如此,要确认每个位置的作用。

  4. 命令 要找的内容

    对象位置上的 * 由 先填好,所以看到的永远是真实的文件名。

  5. 命令 选项 | 命令 选项

    两边各自守着自己的格式,只是把结果递过去。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
选项
决定怎么做。以 - 或 -- 开头,可以不写,也可以写好几个。
对象
对什么做——文件或文件夹的名字,或者路径。
到哪去
结果放在哪里——新的位置,或者新的名字。
要找的内容
要比对的文字或形状。用引号括起来,Shell 就不会动它。
核心概念

掌握核心知识

试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

每个命令都有三个位置

我们敲下的几乎每一行都是、、对象这个顺序。 -la photos 的意思是「看列表(ls),详细到(-la),photos 文件夹」。命令总在最前面,被动手的东西总在最后面。一旦看出位置,长长的一行就不可怕了——一个位置一个位置地读就行。

02

选项的四种形状

决定如何工作。短选项以一个连字符开头(-l),长选项以两个开头(--all)。 的 -la 合并了 -l -a。有些选项还要接值,如 -n 5、--output report.txt。先按“命令 选项 目标”来读,再查帮助确认必需选项和能否合并。

03

对象位置和通配符

目标位置填写文件或文件夹。、 这类先写来源,最后写目标。文件名模式中,* 匹配零个或多个字符,? 匹配一个字符,[0-9] 匹配一位数字。 先匹配的文件名,再交给命令。{a,b} 是另一种展开,无论文件是否存在,都会生成 a 和 b 两个名称。

04

连接和包裹

用符号连起来——| 把结果递给下一个命令,> 写进文件,>> 接在文件末尾, 只在成功之后,|| 只在失败之后,; 就直接运行下一个。包裹的记号只有四种——"..." 把文字保持成一整块,$(...) 把命令的结果塞进来,$((...)) 做计算,[[...]] 检查条件。成块的写法总是一开一合成对——if 配 fi,for 配 done,{ 配 }。

05

查出陌生命令的模式

不用背。在名字后面加 --help,或者敲 man 加名字,然后读第一行,模式就写在那里。那一行里方括号 [ ] 表示「可以不写」,... 表示「可以写好几个」,直接写出来的词就是你要填的位置。另外每个命令都用 0 表示成功,用 $? 就能看到。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. ls *.txt|wc -l

    对象位置,然后,然后又是一个有自己位置的 —— 两边是同一个格式。

  2. ls --help|head -5

    会在头几行写明自己的模式,所以把那段帮助交给 。

  3. cat notes.txt report.txt|wc -l

    对象位置上的两个文件汇成一股流,行数把两个一起算。

连接用的符号

|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 要放在对象前面。 photos -la 在 上有时能用,但并非到处都行,所以请守住、选项、对象的顺序。
  • 不想让 动的形状要用括起来。 *.txt notes.txt 会被 Shell 先把 *.txt 换掉,而 grep "*.txt" notes.txt 则把星号原样递给 grep。
  • 分隔位置靠的是空格,所以带空格的文件名看起来像两个对象。请用引号括起来—— -l "report 2024.txt"。
学习目标

本课你将做到

  1. 1

    用 找到磁盘和分区,并说明什么是分区、和挂载点

  2. 2

    用 给空白磁盘分区,用 格式化,再用 挂载

  3. 3

    用 -h 查看剩余空间,在新磁盘上写入文件,然后安全地卸载

  4. 4

    用 curl 和 git clone 从互联网下载

  5. 5

    用包管理器安装程序——系统用 apk, 用 里的 pip,Node 项目用 npm

不要死记,按位置读

本课的命令模式

每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。

  1. lsblk 对象

    加上对象后, 只显示那块磁盘和它里面的。

  2. sudo mkfs.ext4 对象

    对象是,不是整块磁盘——而且上面的一切都会被清除。

  3. sudo mount 对象 到哪去

    先写要挂什么,再写挂到哪里:先设备,后文件夹。

  4. curl 选项 值

    -s 让 安静,-I 只要响应头;值是网址。

  5. 命令 动作 名字

    包管理器都是同一个形状:管理器、要做的事、包—— add、 install、 install。

每个位置填什么

命令
做什么——命令的名字。它总是排在最前面。
选项
决定怎么做。以 - 或 -- 开头,可以不写,也可以写好几个。
对象
对什么做——文件或文件夹的名字,或者路径。
到哪去
结果放在哪里——新的位置,或者新的名字。
值
一个普通的值——数字、单词或一小段文字。
名字
自己起的名字,用在变量、函数和循环上。
动作
轮到它时要运行的命令。
核心概念

掌握核心知识

试一试 点一下示例,它的就会放进练习终端的输入行,按 Enter 才会运行。运行之前也可以先改一改。

01

磁盘是一本空白笔记本——用 lsblk 看看

把每块磁盘都当作 /dev 下面的一个文件。(list block devices)会把它们画成一棵树:

  • sda 是第一块磁盘。这里它是一块 128 MB 的空练习磁盘。
  • sda1、sda2 …… 是它里面的。现在还一个都没有。
  • sr0 是光驱,TYPE 一栏显示 disk、part 或 rom。

lsblk 只看不改,所以它永远是第一步。每次打开这一课或按下“重置会话”,磁盘都会重新变空——想练多少次都可以。

02

分区——在笔记本上画线

是磁盘上的一块区域,用起来就像一个独立的驱动器。磁盘最开头的分区表记录着每块区域从哪里开始、到哪里结束。 负责写分区表:mklabel msdos 新建一个空的分区表,mkpart primary ext4 1MiB 100% 添加一个从 1 MiB 到磁盘末尾的分区。-s 的意思是“不要提问”。改动磁盘是管理员的工作,所以以 开头。之后用 看,sda 下面会出现 sda1。

03

格式化——给分区一个文件系统

新还只是一片空地,还放不了文件。格式化就是在上面写入一个——它像一本目录,记着每个文件的名字、数据放在哪里、属于谁。 创建 ext4,这是 常用的文件系统;mkfs.vfat 创建 FAT,Windows 和 macOS 也能读(适合 U 盘)。格式化会清除这个分区上原有的一切。

04

挂载——把磁盘接到文件夹上

把磁盘连接到文件夹,而不是使用 C:、D:。这叫挂载,连接的文件夹叫挂载点。执行 /dev/sda1 /mnt/data 后,写入 /mnt/data 的文件就保存在 sda1。用 -h 查看容量。新属于 root,可用 sudo 写入,或用 sudo chown you /mnt/data 更改所有者。用完后以 sudo umount /mnt/data 卸载。

05

curl——在终端里打开网址

( )是一个没有画面的网页。你在浏览器里输入一个地址时,浏览器会向发送请求( 请求),服务器则送回响应:(200 表示 OK,404 表示找不到,301 和 302 表示已经搬到了另一个地址,500 表示服务器出错)、响应头(内容的类型、大小等信息),以及正文(、 或文件)。浏览器会把正文画成网页;curl 则把收到的内容原样显示在里,或者保存成文件。所以 curl 常用来检查服务器是否还活着、下载文件和调用 。 最常用的:

  • curl 地址——把正文打印到屏幕上
  • -o 文件——用你自己定的名字保存;-O——用地址里的名字保存
  • -L——跟着跳到搬去的新地址(301 和 302)
  • -I——只要响应头(不要正文)
  • -s——隐藏进度条;-S——但仍然显示错误;-f——服务器用 404、500 这类错误回答时,以失败结束( 22)
  • -w "%{http_}"——最后打印状态码

安装里常见的 -fsSL,就是这四个选项合在一起。curl 收到的内容可以用交给别的命令:| 只看开头几行,| 只取 JSON 里的一个。这个 经过 SONUB 的网络中继与真正的互联网通信;因为它是模拟器,下载可能要花几秒钟。

06

git clone——复制整个项目

clone 从 这样的复制一个项目——所有文件,连同完整的历史记录。 获取一个文件;git clone 获取整个文件夹。--depth 1 只取最新版本,快得多。用 进入新文件夹,再用 四处看看。这个 是模拟器,所以下载需要几秒到几十秒。

07

包管理器——用 apk 安装程序

包管理器就是用打字来操作的应用商店。它会找到,把程序和它需要的所有东西一起下载下来并安装好。每个系统都有自己的包管理器:

  • Alpine (这个):
  • Ubuntu 和 Debian:apt
  • macOS:brew(Homebrew)

apk add figlet 安装程序,apk search 查找程序,sudo apk del 删除程序。

08

Python 包——在 venv 里用 pip

从 PyPI 安装 包。先创建 venv,与系统 Python 分开使用。运行 -m venv ~/venv,再用 source ~/venv/bin/activate 启用。出现 (venv) 后,用 pip install 安装,结束时输入 deactivate。此处创建约需一分钟。练习环境外没有 pip,会提示 not found;受保护的系统环境可能提示 externally-managed-environment。

09

Node 包——在项目文件夹里 npm install

按项目安装包。在一个空文件夹里,npm init -y 会创建 ,也就是项目所需内容的清单。npm install ms 把包下载到 _modules,并写进 package.json,这样以后任何人只要执行 npm install,就能重建一样的环境。每个项目都有自己的 node_modules,所以两个项目永远不会为版本打架。

前一个交给后一个

把命令连起来

很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。

  1. lsblk|grep sda

    列出所有磁盘和, 只留下关于练习磁盘 sda 的那几行。

  2. sudo mkdir -p /mnt/data&&sudo mount /dev/sda1 /mnt/data

    文件夹真的建好了才挂载磁盘 —— 就是「那件事成了才做」。

  3. curl -s https://sonub.com/robots.txt|head -3

    从互联网获取一个文件, -3 只显示它的前三行。

连接用的符号

&&
只有前面成功了,才运行后面。
|
把输出递给下一个命令,中间不经过文件。
注意

初学者常见错误

  • 会清除你指定的上的一切。这里是一块空白练习磁盘,但在你自己的电脑上,/dev/sda 可能就是系统正在运行的磁盘——一定要先用 确认名字。
  • 不加 的话,、mkfs 和 会因 或 must be superuser 而停下。改动磁盘是管理员的工作。
  • 你还在那个文件夹里时,umount 会回答 is busy。先 ~,再卸载。
  • 在 外面, install 会失败——这里显示 pip: not found,在 Ubuntu 上显示 externally-managed-environment。请创建并启用 venv,不要用 --break-system- 强行安装。
  • 在这里,ping 的回应来自模拟器本身,所以它证明不了互联网是通的。请改用 检查。
  • 你在这里做的一切——分区、文件、安装的——在重新打开这一课或按下“重置会话”后都会消失。真正的工作请放在自己的电脑上。
🚀 进阶学习 — 深入了解

读取用户输入

向用户询问一个值并存入。变量不必事先声明。

echo "你叫什么名字?"# 在屏幕上输出问题
read name# 把回答读入变量 name
echo "你好,$name!"# 使用读到的值

read 会等用户按下 Enter,然后保存输入的内容。加上 -p 可以在同一行显示问题(read -p "Name? " name),-s 会像输入密码时那样隐藏输入的字符,-r 则让反斜杠保持原样。如果不给变量名,答案会存进 $REPLY。

当前目录与子 Shell

除了 , $PWD 也能告诉你。用括号 ( ) 包起来的命令在单独的 中运行,所以在里面 不会改变外面的位置。

echo "我现在在 $PWD"# 通过使用当前
(cd /tmp; pwd)# 只在子 Shell 里移动 → 输出 /tmp
pwd# 外面仍在原来的位置

子 Shell 是当前 的一个副本。在里面做的改变——cd、新变量——在它结束时都会消失,所以 (cd build make) 能在另一个文件夹里构建,而不会把你移走。的各个部分也在子 Shell 中运行,所以在管道后面的 while 里设置的变量,循环结束后是空的(第4课的能解决这个问题)。

清屏与帮助

clear(或 Ctrl-L)用来清理屏幕。遇到不认识的,有三种帮助:内置命令用 help,手册用 man,详细文档用 info。

clear# 清空屏幕(与 Ctrl-L 相同)
help for# for 等内置命令的帮助
man ls# 的手册页(按 q 退出)

help 只认识 的内置命令(、for、read);ls 这样的要用 man 查看。在 man 里,用方向键或 Space 移动,输入 / 加一个词来搜索,按 q 退出。想快速回顾一下的话,很多命令用 --help 会打印简短说明;type 会告诉你一个名字是内置命令、程序还是别名(type cd、type ls)。

挑战

在自己电脑的里,用三条查出三件事:你当前在哪个文件夹、里有什么(包括),以及 是不是 的内置命令。

提示 先用 ,然后用带隐藏文件、以 ~ 为的 ,最后用 type。

查看参考答案
pwd
ls -A ~
type cd

-A 会显示隐藏文件,但不显示 . 和 ..,所以列表里只有真正的项目。type cd 的回答是 'cd is a shell builtin':cd 是 Shell 自身的一部分,因为一个独立的无法改变启动它的那个 Shell 的文件夹。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

花括号展开

会把 { } 中的列表或范围为多个名称。例如 file{1..5}.txt 会变成 file1.txt 到 file5.txt 共五个名称。再交给 或 ,就能一次创建多个文件夹或文件。

mkdir -p project/{src,tests,docs}# 一次创建三个文件夹
touch file{1..5}.txt# 创建 file1.txt 到 file5.txt 共五个文件
cp config.yaml{,.bak}# 备份技巧:config.yaml → config.yaml.bak

Shell 会在运行之前展开,所以命令本身根本看不到花括号—— file{1..3}.txt 会打印 file1.txt file2.txt file3.txt,这是预览模式的一种安全方法。范围也可以用字母({a..e})。花括号里不要加空格:{a, b} 不会被展开。

安全删除

删除后无法撤销。-i(逐个确认)和 -v(显示删除了什么)可以减少失误,trash- 则会把文件送进回收站。

rm -i old.txt# 删除前先询问是否真的要删
rm -v *.tmp# 显示删除了哪些文件
rm -r tempDir/# 删除文件夹及其中的全部内容(小心!)

让 rm 变得危险:rm *.tmp 没问题,但多一个空格——rm * .tmp——就会删除文件夹里的所有文件(因为没有 -r,只有子文件夹会留下)。先用同样的模式运行 (echo *.tmp),准确看到哪些会被删除。rmdir 只删除空文件夹,所以当你认为某个文件夹应该是空的时,它比 rm -r 更安全。

更多 ls 选项

可以改变 的排序方式和列表格式。这三个值得学会。

ls -l# 每行一个,显示详细信息
ls -t# 最近修改的排在最前
ls -R# 连所有子文件夹也一起列出()

选项可以组合:ls -lt 按最新优先详细显示,ls -lS 按大小排序,-h 会把大小显示为 K、M、G 而不是(ls -lhS)。在大项目里 ls -R 可能打印几千行;如果只想看文件夹,find . -type d 更容易阅读。

挑战

创建一个名为 project 的文件夹,里面包含 src、tests 和 docs,再在 project 里放一个空的 README.md。把 README.md 复制到 docs,把这个副本重命名为 guide.md,最后显示整个目录树。

提示 用一行就能建好三个文件夹。每一步都用 检查。

查看参考答案
mkdir -p project/{src,tests,docs}
touch project/README.md
cp project/README.md project/docs/
mv project/docs/README.md project/docs/guide.md
ls -R project

-p 会同时创建 project 和三个文件夹。复制到文件夹里会保留名字,所以在 把它改名为 guide.md 之前,副本是 docs/README.md。ls -R 会列出每个文件夹及其内容,一次就能看到结果。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

更多 grep 选项

加上, 会强大得多。可以组合搜索、行号、排除和计数。

grep -r "TODO" ./src# -r:递归搜索整个文件夹
grep -n "error" app.log# -n:带上行号
grep -v "debug" app.log# -v:只显示不含该词的行(反向)
grep -c "error" app.log# -c:只显示匹配的行数

选项可以组合:grep -rnw "total" . 会在下的每个文件里搜索完整单词,并显示行号。-l 只打印匹配的文件名,在打开文件之前很方便。-A 2 和 -B 2 会显示每个匹配之后或之前的两行,让你看到日志中错误前后发生了什么。

使用 sed 编辑流

逐行读取文件,替换文字或选择要输出的行。默认不会改动原文件。加上 -i 会直接修改文件,因此可以像下方示例一样用 -i.bak 保留备份。

sed 's/okay/great/g' file.txt# 把所有 okay 换成 great
sed '/^#/d' file.txt# 删除以 # 开头的行
sed -i.bak 's/old/new/g' file.txt# 直接修改文件 + 留下 .bak 备份
sed -n '/error/p' log.txt# 只输出含有 error 的行

sed 一次读取一行,执行它的,然后打印结果;只有加 -i 时文件本身才会改变。在 s/old/new/g 中,g 表示这一行里的每个匹配——没有它就只替换第一个。在 Mac 上,sed -i 需要一个备份后缀的值(不要备份就写 sed -i '' …),这和 不同;第13课会介绍更多这类差异。

使用 awk 处理列

按空格(或 -F 指定的字符)把每行切开,把各段 $1、$2 … 来处理。它擅长求和、筛选和统计。

echo 'a b c' | awk '{print $2}'# 第二列 → b
awk -F',' '{print $3}' data.csv# 按逗号切分,取第三列
awk -F',' '$2 > 25' people.csv# 第二列大于 25 的行
awk '{s+=$1} END{print s}' nums.txt# 第一列的合计

awk 对每一行运行它的:前面的模式($2 > 25)选择行,大括号里的部分说明要对这些行做什么。NR 是行号,NF 是字段数,所以 awk 'NR > 1' 会跳过表头行。END 块在最后一行之后运行一次——非常适合求总和与平均值:awk '{s+=$1} END {print s/NR}' 打印第一列的平均值。

cut、sort、uniq 与 xargs

用把小工具连起来,就能做强大的数据处理。 把一条的输出变成下一条命令的。

cut -d ',' -f 1 file.csv# 只取按逗号分隔的
sort file.txt | uniq -c# 先排序,再统计重复次数
find . -name "*.tmp" | xargs rm -f# 把找到的文件交给 删除
find . -name "*.txt" -print0 | xargs -0 rm# 文件名带空格也安全(-0)

一个经典的管道组合用来统计某样东西出现的次数:cut -d' ' -f1 access.log | | -c | sort -rn | 会显示第一列中出现最多的十个值。xargs 根据输入构造命令;加 -n 1 就每项运行一次,配合 find -print0 使用 -0,带空格的名字也能安全传递。

更多 find — 按大小和日期查找,并对每个结果运行命令

除了名称,find 还能按大小、最后修改时间和深度来选择,并对每个匹配项运行。写了多个条件时,必须全部满足。

find . -type f -size +10M# 大于 10 MB 的文件
find . -name "*.log" -mtime +30# 最后修改在 30 天以前的日志文件
find . -maxdepth 1 -type f# 只看里的文件,不进入子文件夹
find . -name "*.tmp" -exec rm {} \;# 对找到的每个文件运行 (填进 {})

在 -size 中,+ 表示“大于”,- 表示“小于”,所以 -size +10M 就是大于 10 MB 的文件(c 是,k、M、G 分别是千字节、兆字节、吉字节)。-mtime -7 表示最近 7 天内修改过,-mtime +30 表示 30 天以前修改的,-mmin 则以分钟为单位做同样的事。条件之间有一个看不见的“并且”(-a);-o 表示“或者”,! 表示“非”——像 \( -name "*.jpg" -o -name "*.png" \) 这样用括号分组时,要在括号前加 \,免得被 抢先处理。-exec 以 \; 结尾时,每个匹配项运行一次命令,而 + 会把许多匹配项收集起来一次运行,速度更快。-delete 和 -exec rm 无法撤销,所以先用同样的条件、不加动作运行一遍,确认列表无误后再加上动作。

挑战

names.txt 每行一个名字,有些名字出现了好几次。把每个名字只打印一次,并附上它出现的次数,出现最多的排在最前面。

提示 只合并相邻的行——先排序。然后再按数字排序一次。

查看参考答案
sort names.txt | uniq -c | sort -rn

把相同的名字排在一起,uniq -c 把它们合并并在前面写上次数,sort -rn 按这个数字排序,最大的在前(-n 按数字,-r 倒序)。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

Here document 与 here string

<< EOF()把多行文字整体交给作为输入。<<<()则是送入一行短文字的轻便方法。

cat << EOF# 到 EOF 为止的几行成为输入
多行 文字 EOF# 两个 EOF 之间的内容就是正文
grep "香蕉" <<< "苹果 香蕉 葡萄"# 用 <<< 输入一行

<< 后面的词只是一个标记;EOF 是习惯,不是规定。写成 <<'EOF'(标记加)时,文本会按字面处理——$HOME 仍然是 $HOME——这正是把写进文件时需要的。 > config.txt << EOF … EOF 是快速创建几行小文件的方法。

标准错误与 /dev/null

(stdout)和标准错误(stderr)走的是不同的通道。2> 只接收错误,2>&1 把错误送到和输出相同的地方,/dev/null 是专门丢弃东西的地方。

command 2> error.log# 只把写入文件
command > out.log 2>&1# 输出和错误一起写入 out.log
command > /dev/null 2>&1# 输出和错误都丢掉(静默)

顺序很重要: > out.log 2>&1 会把两个流都送进文件,但 command 2>&1 > out.log 不会,因为 2>&1 复制的是普通输出在那一刻指向的地方——那时还是屏幕。在 中,&> out.log 是 > out.log 2>&1 的简写。用 /dev/null 隐藏你明白的无用输出没问题,但把所有错误都藏起来,问题就很难找到了。

进程替换

<() 把一条命令的输出当作文件一样传出去。不用临时文件就能直接比较两条命令的结果,非常方便。

diff <(ls dir1) <(ls dir2)# 直接比较两个文件夹的列表
while read l; do echo "$l"; done < <(ls)# 里设置的在循环结束后仍然保留

<( ) 会运行命令,并给它的输出一个临时文件名,比如 /dev/fd/63,这样只接受文件名的(diff、comm、paste)也能读取它。这是 和 zsh 的功能,普通的 sh 没有,所以必须以 开头。第二个例子之所以重要,是因为后面的 while 循环在 中运行;用 < <(…),循环就在你的 里运行,它的变量也能保留下来。

命令替换

$() 把命令的结果像值一样嵌进句子里。比起反(`),更推荐 $( )——它可以嵌套。

echo "这里有 $(ls | wc -l) 个文件"# 把文件个数嵌进句子
today=$(date +%Y-%m-%d)# 把今天的日期存入

$( ) 先运行命令,再把它的输出放在原来的位置,并去掉末尾的换行。结果可能含有空格时,要加引号——"$()"。它可以嵌套而无需反斜杠,比如 $( "$()"),这正是它取代反引号的主要原因。

挑战

把的文件列表保存到 files.txt,同时也在屏幕上看到它。然后在 files.txt 末尾加一行 'checked',最后只打印 files.txt 现在的行数。

提示 同时显示和保存,>> 到末尾,< 把文件作为输入交给 。

查看参考答案
ls | tee files.txt
echo "checked" >> files.txt
wc -l < files.txt

tee 把列表同时写到屏幕和 files.txt。>> 是追加而不是。wc -l < files.txt 把文件当作读取,所以 wc 不知道文件名,只打印数字。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

参数展开

在 ${ } 里取出值的同时,还能截取或替换其中的一部分。这是加工值最强大的工具。

echo "${var/Some/A}"# 把第一个 Some 替换成 A
echo "${var//Some/A}"# // 会把所有 Some 都替换成 A
echo "${var:0:7}"# 从第 0 个字符起只取 7 个(子)
echo "${#var}"# 字符串的长度

${var/old/new} 只在这一次使用时改变值;除非把结果重新赋给变量(var="${var//-/_}"),否则变量本身保持不变。这里的模式是 模式,不是:* 表示任意文本。这些在 内部完成,不需要额外的,所以比在里调用 快得多。

默认值与大小写

为空时可以给一个默认值,也可以一次性转换。

echo "${foo:-默认值}"# foo 为空时改用“默认值”
: "${NAME:=访客}"# NAME 不存在时设为“访客”并保留
echo "${var^^}"# 全部大写( 4+)
echo "${var,,}"# 全部小写

:- 只在当下补上一个值,:= 还会把它存进变量,:? 会在值缺失时用你的消息停止(第8课)。冒号很重要:没有冒号(${foo-x})时,只有变量不存在才使用默认值,变量为空时则不会。^^ 和 ,, 需要 bash 4;macOS 自带的 bash 是 3.2,所以依赖它们之前先在那里测试一下。

截取路径

${var#pattern} 从开头删除匹配部分,${var%pattern} 从末尾删除。一个 # 或 % 删除最短匹配,## 或 %% 删除最长匹配。用下方示例分别取出文件名和文件夹。

path="/usr/local/bin/run.sh"# 示例路径
echo "${path##*/}"# 删到最后一个 / 为止 → run.sh()
echo "${path%/*}"# 删掉最后一个 / 及其后面 → /usr/local/bin(dirname)
echo "${path%.sh}"# 只删掉 .sh

可以把 # 想成从左边截,% 想成从右边截(键盘上 # 在 % 的左边)。一个符号截掉最短的匹配,两个符号截掉最长的匹配。当 file=archive.tar.gz 时,${file%.*} 得到 archive.tar,${file%%.*} 得到 archive。basename 和 dirname 做同样的事,但每次都要启动一个独立的。

数组

把几个值放进括号里就成了:用 [编号] 取出一个,用 [@] 取出全部,${#array[@]} 给出个数。

array=(苹果 香蕉 葡萄)# 创建一个有三个元素的数组
echo "${array[0]}"# 第一个 → 苹果(编号从 0 开始)
echo "${array[@]}"# 全部 → 苹果 香蕉 葡萄
echo "${#array[@]}"# 个数 → 3

在里一定要带写 "${array[@]}":即使元素包含空格,每一项也会保持完整。用 array+=(cherry) 添加元素,用 ${!array[@]} 列出索引号。数组是 的功能——普通的 sh 没有,zsh 从 1 开始计数——所以同一个在那里可能表现不同。

关联数组

用 -A 创建的保存“名字 → 值”的配对(相当于其他语言中的字典或映射)。

declare -A person# 声明一个关联数组
person[name]="爱丽丝"# 把值存到标签 name 下
echo "${person[name]}"# 取出 → 爱丽丝
for k in "${!person[@]}"; do echo "$k"; done# 遍历所有键

declare -A 是必需的:没有它, 会把 person 当作普通数组,每个名字都会悄悄变成 0。中键的顺序是不保证的,所以在意顺序时请对键排序。关联数组需要 bash 4 或更高版本,所以 macOS 自带的 bash 里没有。

内置变量与算术

会替你填好一些。写在 $(( )) 里,乘方和余数也能算。要算小数就用 。

echo "$?"# 上一条的(0 = 成功)
echo "$RANDOM"# 0 到 32767 之间的随机数
echo "$(( 2 ** 10 ))"# 乘方 → 1024
echo "scale=2; 10/3" | bc# 小数运算 → 3.33

$? 每执行一条命令都会改变,所以如果以后还要用,就立刻保存它(status=$?)。其他有用的:$$ 是 的号,$0 是的名字,$SECONDS 是 Shell 已经运行了多久,$LINENO 是当前行号。$RANDOM 用在游戏里没问题,但不能用于密码——凡是需要保密的东西,都请使用像 openssl rand 这样正规的生成工具。

使用 printf 格式化

在不同系统上的表现略有差异。 在哪里都一样,还能精确控制宽度、对齐和小数位数。

printf "你好,%s!\n" "世界"# 把值填到 %s 的位置 → 你好,世界!
printf "%.2f\n" 3.14159# 保留两位小数 → 3.14
printf "%05d\n" 42# 不足五位用 0 补齐 → 00042

printf 自己不会换行,所以格式要以 \n 结尾。如果给出的值比占位符多,格式会重复使用:printf "%s\n" a b c 会打印三行——打印列表的简洁方法。%-10s 让文字在 10 个字符宽的栏里左对齐,可以对齐简单的表格。

挑战

file 中保存的是 report.final.pdf。只用,分别打印去掉的名字、单独的扩展名,以及全部大写的完整名字。

提示 % 从右边截,## 从左边尽可能多地截,^^ 变成大写。

查看参考答案
file="report.final.pdf"
echo "${file%.*}"
echo "${file##*.}"
echo "${file^^}"

${file%.*} 从右边去掉最短的“.某某”→ report.final。${file##*.} 去掉最后一个点之前的所有内容 → pdf。${file^^} 把每个字母都变成大写( 4 或更高版本)。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

使用 case 分支

把一个值和许多模式比较时,case 比一串 if 更整洁(相当于其他语言中的 switch)。

case "$x" in# 根据 x 的值
0) echo "零";;# 值为 0 时
1) echo "一";;# 值为 1 时
*) echo "其他";; esac# * 匹配其余所有情况

每个模式都可以用 | 列出几种写法,比如 yes|y|Y),也可以使用和文件名一样的:*.jpg|*.png) 匹配图片文件名。第一个匹配的模式胜出,其余的都会跳过,所以把兜底的 *) 放在最后。;; 结束每一种情况;忘记写它是 case 里最常见的错误。

for、while 与 until

for 遍历列表或范围,while 在条件为真时一直,until 在条件为假时一直循环。另外还有 C 风格的 for。

for ((i=1; i<=3; i++)); do echo "$i"; done# C 风格的 for
while (( count < 3 )); do ((count++)); done# 条件为真时一直重复
until (( count >= 3 )); do ((count++)); done# 条件为假时一直重复

while 是逐行读取输入(while read -r line; do … done < file)和等待某件事就绪时的自然选择。break 提前跳出循环,continue 跳到下一轮。无限的 while true 循环需要一个出口——break,或者在手动运行的时候按 Ctrl+C。

正则匹配与文件检查

=~ 检查是否符合模式。-e、-f、-d 等用来检查文件或文件夹是否存在,以及它是哪一种。

if [[ "$email" =~ ^[a-z]+@[a-z]+\. ]]; then echo 匹配; fi# 正则表达式匹配
if [[ -f "$file" ]]; then echo "文件存在"; fi# -f:是普通文件吗?
if [[ -d "$dir" ]]; then echo "文件夹存在"; fi# -d:是文件夹吗?
if [[ -z "$name" ]]; then echo "为空"; fi# -z:是空的吗?

=~ 右边的模式不要加;加了引号它就变成普通文本。括号里的部分会保存在 中,所以你可以取出其中的片段:执行 [[ $d =~ ([0-9]+)-([0-9]+) ]] 之后,${BASH_REMATCH[1]} 就是第一个数字。更多文件测试:-s(非空)、-r、-w 和 -x(可读、可写、可运行)以及 -nt(比……新)。

挑战

写一个,遍历 1 到 15,遇到 3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是两者的倍数打印 FizzBuzz,其余情况打印数字本身。

提示 % 得到除法的余数;余数为 0 表示“能整除”。想一想哪个测试必须放在最前面。

查看参考答案
for i in {1..15}; do
  if (( i % 15 == 0 )); then echo "FizzBuzz"
  elif (( i % 3 == 0 )); then echo "Fizz"
  elif (( i % 5 == 0 )); then echo "Buzz"
  else echo "$i"
  fi
done

当 i 能被 3 整除时,(( i % 3 == 0 )) 为真。15 的测试放在最前面,因为 if/elif 会在第一个为真的测试处停下——否则 15 会停在 Fizz。其他数字都会走到 else,原样打印出来。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

变量作用域

local 创建只在内部使用的, 创建不能修改的常量。declare 可以设置整数(-i)、小写(-l)、大写(-u)等属性。

greet() { local msg="你好"; echo "$msg $1"; }# msg 在函数外看不到
readonly PI=3.14# 不能修改的常量
declare -i num=42; num+=10# 整数类型 → 自动完成运算(52)
declare -u up="hi"; echo "$up"# 总是大写 → HI

没有 local 的话,你在函数里设置的每个变量都是全局的,可能会其他地方的同名变量——这种 bug 很难发现。在函数里请默认使用 local。readonly 保护那些绝不能改变的值;试图修改时会打印错误。declare -p name 会显示变量是如何定义的,时很有帮助。

使用 getopts 处理选项

让接受 -v 或 -f 文件名 这类的标准工具。字母后面的冒号(:)表示“这个选项需要一个值”。

while getopts ":vf:" opt; do# v 不需要值,f 需要一个值
case $opt in# 按选项
v) verbose=true ;;# 有 -v 时打开 verbose
f) file="$OPTARG" ;; esac; done# -f 的值在 中

":vf:" 开头的 : 会开启安静的错误处理:遇到未知选项时 opt 的值是 ?,缺少值时是 :,这样你就能打印自己的帮助信息。 只处理短选项(-v);长选项(--verbose)必须自己读取。结束后,shift $((OPTIND - 1)) 会去掉这些选项,让 $1 成为第一个真正的。

source 与 export

source(或 .)在当前 中运行另一个文件,载入其中的和设置。export 则把也传给。

source ./lib/helpers.sh# 把另一个文件的函数和变量载入当前 Shell
. ~/.bashrc# . 是 source 的简写
export API_KEY="abc123"# 让子进程也能看到它

正常运行会启动一个新的 Shell,所以脚本结束时,它的变量和 都会消失;source 则在你当前的 Shell 里运行它,修改 ~/.bashrc 后就是这样重新加载的。export 会给变量做个标记,让从这个 Shell 启动的能收到它——但反过来不行:子进程不能改变父进程的变量。之类的秘密请放在不共享、也不到 的文件里。

挑战

写一个 greet,向它收到的每个名字问好,带空格的名字("Mina Kim")要保持完整;一个名字都没收到时,打印 'Usage: greet NAME...' 并报告失败。

提示 $# 是个数,return 1 报告失败,带的 "$@" 让每个参数保持完整。

查看参考答案
greet() {
  if (( $# == 0 )); then echo "Usage: greet NAME..."; return 1; fi
  local name
  for name in "$@"; do echo "Hello, $name!"; done
}
greet Leo "Mina Kim"

(( $# == 0 )) 捕获没有名字的调用。local 让 name 只留在函数内部。"$@" 把 Leo 和 Mina Kim 作为两个参数传入,所以函数打印两行——没有引号的话会打印三行。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

使用 trap 清理

trap 设定收到(退出、错误、中断)时要运行的。用来清理临时文件尤其方便。

TMP=$(mktemp)# 安全地创建临时文件
cleanup() { rm -f "$TMP"; }# 清理用的
trap cleanup EXIT# 脚本结束时总会清理
trap 'echo 已中断; exit 1' SIGINT# 按下 Ctrl-C 也能安全处理

EXIT 是一种类似信号的事件,脚本每次结束时 都会触发它——无论是正常结束、通过 exit 结束,还是被 set -e 停止——所以一个 trap 就能所有退出的方式。ERR 在每次命令失败时运行,适合打印行号($LINENO)。mktemp 会创建一个名字唯一的文件,这样同一个脚本同时运行两份时,也不会覆盖彼此的临时文件。

防御式 set 选项

在开头写上 set ,脚本就更不怕出错。-e(出错即停)、-u(禁止使用未设置的)和 -o pipefail(察觉中的失败)一起使用。-x 用于。

set -euo pipefail# 稳健脚本的标准第一行
set -x# 逐条输出正在运行的(调试)
set +x# 关闭调试

set -e 有例外:在 if 条件里、在 或 || 列表里,或者在你检查结果的命令里失败时,它不会停止,因为你已经在处理这个失败了。可能会无害地失败的命令,可以用 || true 放行。只在你调试的那一部分周围打开 set -x,追踪输出就会很短。

后台任务与 wait

在后面加上 &,它就作为运行。jobs 列出这些任务,wait 等到它们全部结束。

sleep 5 &# 在后台运行
jobs# 仍在运行的后台任务列表
wait# 等到所有后台任务都结束
time sleep 1# 测量命令用了多长时间

& 会立刻把还给你,并打印一个作业编号和 ID。fg 把后台作业带回前台,Ctrl+Z 暂停正在运行的作业,bg 让它在后台继续运行。关闭时,后台作业通常会停止;nohup command & 或者 这样的工具可以让它们在你退出登录后继续运行。

常用安全模式

真实中反复出现的防御模式:检查必需的值、检查是否存在、失败时立即停止。

: "${VAR:?必须设置 VAR}"# 缺少 VAR 时报错并停止
command -v jq && echo "已找到 jq"# 检查命令是否已安装
cp a b || { echo 失败; exit 1; }# 失败时输出提示并停止

command -v 是检查某个工具是否存在的可移植方法;如果只想要答案,就把输出丢掉(command -v > { "please install jq"; exit 1; })。把这类检查放在最前面,脚本就会一开始就带着清楚的消息失败,而不是在工作进行到一半时才失败。用 { …; } 把几条命令包起来;其中的空格和最后的 ; 都是必需的。

挑战

写一个 backup.sh:把里的每个 .txt 文件复制到名为 backup-YYYY-MM-DD(今天的日期)的文件夹,复制失败时带着错误消息停止,没有 .txt 文件时安静地跳过,最后打印复制了多少个文件。

提示 开头写 set -euo pipefail,用 date +%F 取名,一个 for ,以及在 后面加 { …; exit 1; }。

查看参考答案
#!/bin/bash
set -euo pipefail
dest="backup-$(date +%F)"
mkdir -p "$dest"
count=0
for f in *.txt; do
  [[ -e "$f" ]] || continue
  cp "$f" "$dest/" || { echo "copy failed: $f" >&2; exit 1; }
  count=$(( count + 1 ))
done
echo "copied $count file(s) to $dest"

date +%F 让每天对应一个文件夹, -p 在文件夹已存在时不会报错。[[ -e "$f" ]] || continue 在 *.txt 什么也没匹配时跳过循环。>&2 把错误消息送到错误流。这里用 count=$(( count + 1 )) 而不用 ((count++))。这是因为当 count 为 0 时 ((count++)) 会返回“失败”,从而让 set -e 停止。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

理解每个 set 选项

set -euo pipefail 是三个的组合。知道每一个能防止什么事故,就明白它为什么成了标准写法。

set -e# 只要有失败就立即停止
set -u# 使用未定义的会报错(能发现拼写错误)
set -o pipefail# 中间一步失败,整个管道就算失败

只用 -e 会漏掉管道中间的失败:false | true 被算作成功,因为管道通常只报告最后一条命令的结果。pipefail 让任何一部分失败时整个管道都失败。-u 能抓住 $flie 这样的拼写错误,否则它会悄悄变成空值——这在 -rf "$dir/$flie" 中很危险,因为那样会删掉 $dir 里的所有内容。

使用 shopt 调整通配

调整文件名的匹配方式()。globstar 让 ** 深入子文件夹,nullglob 在没有匹配时给出空值。

shopt -s globstar# ** 也能匹配到子文件夹里( 4+)
for f in **/*.sh; do echo "$f"; done# 所有子文件夹中的每个 .sh 文件
shopt -s nullglob# 没有匹配时给出空值,而不是模式原文

shopt -s 打开一个,shopt -u 关闭它;单独的 shopt 会列出全部选项。打开 nullglob 后,没有文本文件时,for f in *.txt 就什么也不做,而不是以 *.txt 这段文字运行一次。dotglob 让 * 也包括,nocaseglob 在文件模式中忽略。globstar 需要 bash 4。

IFS 与安全逐行读取

(内部)决定单词分割时在哪里切开。要安全地逐行读取文件,就把 IFS= 和 read -r 一起用。

IFS=',' read -ra f <<< "a,b,c"# 按逗号切分,存入 f
while IFS= read -r line; do# 保留空格和特殊字符
echo "$line"; done < file.txt# 安全地一次读取一行

read -r 让反斜杠保持原样,IFS= 阻止 read 去掉行首和行尾的空格。如果文件最后一行没有换行符,普通的 while read 会丢掉它;在 read 后面加上 [[ -n $line ]] 就能接住它。只为一条设置 IFS(IFS=',' read …),这样改动就不会影响的其他部分。

挑战

某个用 -rf $LOGDIR/*.log 删除旧日志。请改写它,让它在 LOGDIR 为空、未设置或含有空格时都安全,而且永远不会删除文件夹。

提示 ${VAR:?message} 会在值缺失时停止;给加,但把 * 留在引号外面。

查看参考答案
set -u
: "${LOGDIR:?LOGDIR is not set}"
rm -f -- "$LOGDIR"/*.log

如果 LOGDIR 为空,旧的那一行就会变成 rm -rf /*.log。:? 会改为带着消息停止脚本。引号让带空格的文件夹名保持完整,而 * 留在引号外,所以仍然能匹配。-- 防止以 - 开头的名字出问题,而不带 -r 的 -f 永远不会删除文件夹。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

用 nvm 管理版本

不同的项目可能需要不同版本的 Node。 可以安装多个版本并在它们之间切换。

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash# 安装 nvm(版本号请查看官方文档)
nvm install 22# 安装 Node 22
nvm use 22# 在当前 中使用 Node 22
node -v# 确认切换后的版本

nvm 把 Node 安装在你的里,所以安装全局包永远不需要 。写有版本号(22)的 .nvmrc 文件,能让任何人在那个项目里输入 nvm use 就用上正确的版本。nvm alias default 22 设置新启动时使用的版本,nvm 显示已经安装了哪些版本。在 Windows 上,独立的 nvm-windows 项目做同样的事。

npm scripts 与本地二进制

中的 会把 _modules/.bin 加进 ,所以不用也能使用项目里的工具。

npm init -y# 快速生成 package.json
npm install lodash# 添加运行时依赖
npm install -D typescript# 只在开发时使用的依赖
npm run build# 运行 scripts.build

脚本之间可以互相调用(在另一个脚本里写 run lint npm run test)。额外的写在 -- 之后:npm run test -- --watch。start 和 test 是特殊的名字,不加 run 也能用(npm start、npm test)。node_modules/.bin 只在 npm run 期间位于 PATH 中。所以安装在项目里的工具,即使直接输入名字会提示 ' not found',在脚本里也能正常工作。

用 npx 一次性运行

无须就能运行包中的工具。项目中已有时使用现有工具,需要时再下载。要指定最新版,可像示例一样加上 @latest。

npx create-next-app@latest my-app# 搭建项目骨架的示例
npx prettier --write .# 运行 prettier(本地的或下载的)
npx --yes cowsay hello# 不询问确认,直接运行一次

npx 先在项目的 _modules/.bin 里查找工具,只有找不到时才下载到里。加上 @ 和版本号可以固定版本(npx prettier@3 --check .)。用 npx 运行一个陌生的包,就是在你的电脑上执行来自互联网的,所以要核对准确的名字——确实存在名字相似的假冒包。

挑战

新建一个文件夹 hello-,里面要有 、一个打印 'Hello from npm' 的文件 hello.js,以及一个运行它的 npm hello。然后运行这个脚本。

提示 npm init -y 会创建 package.json;npm pkg set 不用打开就能添加脚本。

查看参考答案
mkdir hello-npm && cd hello-npm
npm init -y
echo "console.log('Hello from npm')" > hello.js
npm pkg set scripts.hello="node hello.js"
npm run hello

npm init -y 用默认值写出 package.json。 … > hello.js 创建。npm pkg set scripts.hello=… 把脚本加进 package.json(也可以手动编辑这个文件)。npm run hello 会先打印它要运行的,然后打印 Hello from npm。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

AI 编程助手安装备忘

用官方安装或 包来安装。装好后立刻用版本检查。

curl -fsSL https://claude.ai/install.sh | bash# Claude Code(Anthropic)
curl -fsSL https://chatgpt.com/codex/install.sh | sh# OpenAI Codex
npm install -g @xai-official/grok# Grok CLI(以 方式安装为例)
claude --version && codex --version# 检查安装(示例)

每个助手都会从项目里的一个文件读取指示(Claude Code 读 CLAUDE.md,Codex 和许多其他工具读 AGENTS.md),所以项目的规则只需告诉它一次。除非你允许,它们在运行命令或修改文件之前都会先询问——读一读它们打算运行什么;本课程里的命令正是你到时会看到的。这些工具变化很快,所以要查看版本并经常更新。

Firebase 与其他必备 CLI

用于、登录和的 ,常和快速的包管理器 pnpm 一起安装。

npm install -g firebase-tools# Firebase CLI
firebase login# 通过登录
firebase projects:list# 项目列表
npm install -g pnpm# pnpm

firebase init 会提出一些问题并写出 firebase.json。firebase deploy 上传项目,firebase emulators:start 在你自己的电脑上运行这些服务以便测试。自动构建无法通过浏览器登录,所以改用存放在里的服务账号密钥。pnpm 让多个项目共享每个包的同一份副本,从而节省磁盘空间。

PATH 与权限问题

是 查找的文件夹列表。安装后仍找不到时,检查安装目录是否在 PATH 中。用 which 查看位置。如果使用 ,也要确认当前 shell 已加载 nvm。

which node npm# 程序所在的位置
echo "$PATH"# 查看搜索路径
npm config get prefix# 的位置
command -v firebase || echo "not found"# 检查是否已安装

PATH 是一个用 : 分隔的文件夹列表,从左到右查找,第一个找到的胜出——所以装了两套 时可能互相遮挡;which -a node 会把它们全部列出来。要添加一个文件夹,就把 export PATH="$HOME/.local/bin:$PATH" 写进 ~/.zshrc 或 ~/.bashrc,然后打开一个新。 install -g 出现 EACCES 错误,表示 npm 想写入系统文件夹;使用 nvm,或者把 npm 的 prefix 移到你的里,就能解决。

挑战

写一个简短的,检查 、 和 是否已安装:找到的就打印它的版本,没找到的就打印 'missing: NAME'。

提示 只有当 NAME 在你的 里时, -v NAME 才会成功。把它的输出送到 ——只有才重要。

查看参考答案
for tool in node npm git; do
  if command -v "$tool" >/dev/null; then
    echo "$tool $("$tool" --version)"
  else
    echo "missing: $tool"
  fi
done

循环对每个工具名运行一次。command -v 决定两部分中运行哪一部分。$("$tool" --version) 运行工具本身来获取版本:git 打印 'git version 2.x',node 打印前面带 v 的版本号,如 v22.11.0,npm 只打印数字。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

各语言的单行执行选项

几乎每种语言都有一个“把作为、立即运行”的。只是选项字母不同,用法完全一样。

python3 -c "import sys; print(sys.version)"# 单行
node -e "console.log(1 + 1)"# 单行
perl -e 'print "hello\n"'# Perl 单行
ruby -e 'puts (1..5).to_a'# Ruby 单行
php -r 'echo PHP_VERSION;'# PHP 用 -r

AI 修改文件时使用的模式

让 修改时,得到的答案几乎总是这种形式:用 传入一小段 ,读取文件、替换、再写回去。关键是先用 assert 确认要替换的内容确实存在。

python3 - <<'PY'# 从这里到 PY 是一段 Python 程序
p = "src/app.py"# 要修改的文件
s = open(p, encoding="utf-8").read()# 读取整个文件
old = "before text"# 要查找的字符串(少了这一行会出现 NameError)
new = "after text"# 要换上的字符串
assert old in s# 找不到目标就在这里停下(最重要的一行)
s = s.replace(old, new)# 替换
open(p, "w", encoding="utf-8").write(s)# 写回文件
PY# heredoc 结束——必须顶格写在行首

heredoc 应用 — 生成配置文件

在中生成多行的配置或文档时, 也是最整洁的办法。标记不加时,$ 会当场被替换成值。

cat <<EOF > .env# 标记不加引号 → 变量会被替换
PORT=$PORT# $PORT 以实际的值保存
EOF# 到这里结束
cat <<'EOF' > template.txt# 加了引号 → $PORT 保持原样
bash <<< "echo one-liner"# 用 只传一行

sed 进阶 — 精确定位要改的行

在前面加上地址(行号或),就只改那些行。用 -i 直接修改文件时,加上 -i.bak 这样的——既能留下备份,在 macOS 和 上表现也一样。

sed -n '10,20p' log.txt# 只输出第 10 到 20 行
sed '/^#/d' config.txt# 删除以 # 开头的行
sed -i.bak 's/3000/8080/' config.txt# 修改文件,同时留下备份(.bak)
sed -E 's/([a-z]+)@([a-z.]+)/\2 \/ \1/' mail.txt# 用分组调换顺序
sed 's/error/ERROR/gI' log.txt# 忽略,全部替换
printf 'appended\n' >> notes.txt# 到最后一行之后(在哪里都能用)

awk 进阶 — 汇总与格式化

的真正实力体现在汇总和 格式化上。读取表格、按项目分组、整齐输出,一行就能完成。

awk '{sum[$1] += $2} END {for (k in sum) printf "%-10s %5d\n", k, sum[k]}' sales.txt# 按项目求合计,以表格输出
awk -F, '{print $2}' data.csv# 处理 时,-F, 按逗号切分
awk 'NF > 0' notes.txt# 去掉空行(只写模式就会输出该行)
awk '{n = split($0, a, "/"); print a[n]}' paths.txt# 从中只取文件名
awk 'NR == FNR {m[$1] = $2; next} $1 in m {print $1, m[$1], $2}' a.txt b.txt# 连接两个文件(join)
awk 'BEGIN {printf "%.2f\n", 22 / 7}'# 只写 BEGIN 就成了计算器

jq 与 xargs — 连接各种语言的胶水

用 处理,“对找到的每个结果运行”用 。两个都会用,能用连起来的范围就大大扩展了。

curl -s https://api.example.com/u | jq '.name'# 从 JSON 中取一个
jq -r '.items[] | .id' data.json# 把成每行一项(-r 去掉)
ls *.txt | xargs wc -l# 对找到的每个文件运行命令
grep -rlZ TODO . | xargs -0 sed -i.bak 's/TODO/DONE/g'# 用 + 批量修改(-Z 和 -0 让带空格的文件名也安全,并保留备份)

挑战

把 1 到 100 的和打印两次:一次用通过 输入的简短 ,一次用 和 。

提示 - 从读取程序;awk 可以把一列加起来,并在 END 中打印总和。

查看参考答案
python3 - <<'PY'
print(sum(range(1, 101)))
PY
seq 1 100 | awk '{s+=$1} END {print s}'

python3 - 运行 <<'PY' 和 PY 之间的;PY 外面的让 不改动里面的任何内容。seq 1 100 每行写一个数字,awk 把每个数字加到 s 上,最后打印一次。两种方法都打印 5050。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

贪婪匹配与懒惰匹配

* 和 + 会尽可能多地抓取()。在后面加 ?,写成 *? 和 +?,就会尽可能少地抓取(懒惰匹配)——这只在 语法( -P、)中有效。在基本和中,请改用“不是那个字符”来缩小范围,如 [^"]*。

echo '<b>a</b><b>b</b>' | grep -Po '<b>.*</b>'# 贪婪:从第一个 <b> 到最后一个 </b> 成为一整块
echo '<b>a</b><b>b</b>' | grep -Po '<b>.*?</b>'# 懒惰:<b>a</b> 和 <b>b</b> 分开
grep -Eo '"[^"]*"' data.json# 扩展语法:缩小为“除外的任意字符”
grep -Po 'id=\K\d+' app.log# \K——检查前面的部分,但只打印数字
python3 -c "import re; print(re.findall(r'\d+(?=ms)', 'took 2300ms'))"# 先行断言 (?=…)——只取 ms 前面的数字

反向引用——再次出现的相同文本

在模式里用 \1 再次调用抓到的分组,就能找到“同样的文本又出现一次”的地方。可以用来查找或修正不小心打了两遍的单词。

grep -Ew '([a-z]+) \1' notes.txt# 找出连续出现两次的单词(the the)
sed -E 's/\b([a-z]+) \1\b/\1/g' notes.txt# 把重复的单词变成一个
grep -E '^(.)(.).?\2\1$' words.txt# 倒着读也一样的 4~5 个字母的单词(level、noon)
sed -E 's/(.*)=(.*)/\2=\1/' config.txt# 交换 key=value 的两边

同一任务在各操作系统上的做法

并排比较各操作系统如何完成同一件事。 和 macOS 用 和 ;Windows 用 的 和 -replace。

grep -rn -E 'TODO|FIXME' .# Linux 和 macOS:搜索文件夹里的所有文件
Select-String -Path *.txt -Pattern 'TODO|FIXME'# Windows PowerShell:同样的任务
sed -i.bak -E 's/[[:space:]]+$//' notes.txt# Linux 和 macOS 通用:删除行尾空格(保留 .bak 备份)
(Get-Content notes.txt) -replace '\s+$', '' | Set-Content notes.txt# Windows PowerShell:同样的任务
findstr /R /C:"^[0-9][0-9]*$" numbers.txt# Windows :只有数字的行(没有 +,所以写 [0-9][0-9]*)

rg、perl 和 git——更快、更强的工具

(rg)比 更快,能直接理解 \d,还会自动跳过 中列出的文件(brew install ripgrep、apt install ripgrep)。macOS 和 自带的 是 的源头,而 可以用搜索的和历史。

rg -n 'TODO|FIXME'# 下的全部内容,带行号
rg -o '\d{4}-\d{2}-\d{2}' -g '*.log'# 只取 .log 文件中的日期
perl -pe 's/(\d+)-(\d+)/$2-$1/g' a.txt# Perl:12-34 → 34-12
perl -pi -e 's/foo/bar/g' *.txt# Perl:就地修改多个文件
git log --grep='fix.*login' -i --oneline# 用正则搜索信息
git log -G 'apiKey' --oneline# 用正则搜索代码改动

更多 Python re 用法

的 re 使用 语法,所以 \d \w \s、懒惰匹配和都能用。模式要写成原始 r"…"——普通的 "\b" 会变成退格字符。

import re# 加载模块
re.search(r"\d+", "a12b").group()# 第一个匹配 → "12"
re.findall(r"\d+", "a12b345")# 所有匹配 → ["12", "345"]
re.sub(r"\s+", " ", "a b")# 替换 → "a b"
re.split(r"[,;]\s*", "a, b;c")# 用正则拆分 → ["a", "b", "c"]
m = re.match(r"(?P<y>\d{4})-(?P<m>\d{2})", "2026-10")# 命名分组——m["y"] 是 "2026"
pat = re.compile(r"error", re.I)# 只编译一次;re.I 忽略

awk 与正则

使用。/模式/ 检查整行,$3 ~ /模式/ 只检查那一列,gsub 用来替换。给 -F 传一个,就能同时按多种拆分。

awk '/ERROR|WARN/' app.log# 和 -E 一样
awk '$4 ~ /^user=a/' app.log# 第 4 列以 user=a 开头的行
awk '$3 !~ /INFO/ {print $3, $4}' app.log# 不是 INFO 的行的第 3、4 列
awk '{gsub(/[0-9]+ms/, "N ms"); print}' app.log# 替换行中所有像 2300ms 这样的值
awk -F'[=: ]+' '{print $2}' config.txt# 把 =、: 和连续空格都当作分隔符

挑战

contacts.txt 的各行之间夹杂着电子邮件地址。把每个地址打印一次,排好序,除此之外什么都不打印。

提示 -o 只打印匹配的部分,-E 允许使用 + 和 {2,}, -u 去掉重复项。

查看参考答案
grep -oE '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' contacts.txt | sort -u

模式依次是:字母、数字或 ._%+-(名字部分)、@、一个、一个点,再加至少两个字母。-o 把每个匹配单独打印成一行,即使一行里有好几个。真正的电子邮件规则更复杂,但对日志来说这个模式已经够用了。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

选项能站的位置和 --

通常紧跟在后面,但 的工具()在对象后面也接受,而 BSD 的工具(macOS)不接受。所以命令、选项、对象的顺序才是到处都通用的习惯。-- 后面的一切只会当作对象来读,这能救下以横杠开头的名字。

ls -l notes.txt# 到处都通用的顺序
ls notes.txt -l# 在 Linux 上能用,在 macOS 上不行
rm -- -weird.txt# 删除名字以横杠开头的文件
grep -- -v notes.txt# 找「-v」这段文字,而不是当成选项

读懂 man 和 --help 里的方括号

man 或 --help 的第一行就是这个的模式,而写法都是同一套。方括号 [ ] 标出可以不写的部分,... 表示这个位置可以重复,竖线 | 表示二者选一,大写或斜体的词就是你要填的位置。读懂这一行,没用过的命令也能叫得动。

ls --help | head -5# 模式就在最前面几行
man ls# 完整手册——按 q 退出
man -k copy# 按用途去找命令
type cd# 这个名字是、内建命令还是别名

退出码 0 和 $?

每个结束时都会留下——0 表示成功,其他值表示失败。$? 里装着刚结束那个命令的码,而 看的正是这个值。很多命令都有「安静」,让你不看输出只用退出码。

ls notes.txt; echo $?# 成功时打印 0
ls missing.txt; echo $?# 失败时打印不是 0 的值
grep -q echo todo.sh && echo found# -q 什么都不打印,只留下退出码
true; echo $?# true 总是成功,false 总是失败

挑战

du 是你还没学过的。用 --help 查出它的模式,然后把下载文件夹里最占地方的五项列出来,最大的排在最后。

提示 --help 的第一行就是模式。-s 让每个对象只给一个合计,-h 用人看得懂的单位, -h 认得这些单位并据此排序。

查看参考答案
du --help | head -20
du -sh ~/Downloads
du -sh ~/Downloads/* | sort -h | tail -5

看 du --help 就知道模式是 du []... [FILE]...——意思是两个位置都可以不写,而且可以写好几个。所以把 -sh 放进选项位置,把 ~/Downloads/* 放进对象位置,再把结果递给 sort -h,用 -5 只看最后五行。你从没背过 du,只靠模式就写出来了。

先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。

仔细看看磁盘:fdisk -l、blkid 和 parted print

画出那棵树,另外三个会看看树的里面。fdisk -l 以扇区为单位打印表;blkid 显示类型和 UUID——一个永远不变的序列号,不像 sda1 这个名字,加装磁盘时可能会变; print 用 parted 自己的说法显示分区表。它们都只读不改。

sudo fdisk -l /dev/sda# 分区表,一个扇区一个扇区地看
sudo blkid# 每个分区的文件系统类型和 UUID
sudo parted -s /dev/sda print# 同一张分区表,parted 眼中的样子
cat /proc/partitions# 内核自己知道的信息

文件里的磁盘:loop 设备

练习不需要备用磁盘。 设备能让一个普通文件像磁盘一样工作:创建一个你想要大小的文件,格式化它,再用 -o loop 挂载。虚拟机的磁盘镜像和 U 盘安装盘正是这样工作的。

truncate -s 16M usb.img# 一个 16 MB 的空文件
mkfs.vfat usb.img# 像 U 盘一样格式化
sudo mkdir -p /mnt/usb && sudo mount -o loop usb.img /mnt/usb# 把文件当作磁盘挂载
df -h /mnt/usb# 一块 16 MB 的“磁盘”,其实是个文件

把磁盘变成你的,并在开机时自动挂载

刚格式化好的磁盘属于 root。用 chown 把挂载点交给自己,就能不用 写入。另外,挂载只持续到电脑重启为止;在真实上,要在 /etc/fstab 里加一行,让它每次开机都自动挂载,最好用 blkid 给出的 UUID 而不是名字。重启前先用 -a 测试这一行——写错的 fstab 可能让服务器启动不了。

sudo chown you /mnt/data# 挂载点现在属于你了
echo "mine" > /mnt/data/mine.txt# 不再需要 sudo
echo "/dev/sda1 /mnt/data ext4 defaults 0 2" | sudo tee -a /etc/fstab# 每次开机都挂载(真实服务器上请用 UUID=…)
sudo umount /mnt/data && sudo mount -a && df -h /mnt/data# 检查 fstab 这一行是否有效

把几种包管理器放在一起比较

每个包管理器做的都是那几件事——查找、安装、列出、删除——只是用词略有不同。 和 apt 为整个系统安装(所以需要 ),而 和 装进 或项目文件夹(所以不需要)。pip freeze 和 就是配方,让你在任何地方重建同样的环境。

apk search htop# 按名字查找包
sudo apk del figlet# 再把删掉
pip freeze > requirements.txt# 记下这个 venv 里的 包
npm ls# 这个 Node 项目用到的包

深入 curl——POST、请求头、时间限制和重试

到目前为止你只是在获取内容(GET)。要向 发送内容,用 -X POST 换一种方法,把要发送的内容写在 -d 后面,再用 -H 加上请求头(内容类型、访问令牌)。-v 会显示来来回回的完整请求和响应,想弄清楚为什么不成功时就用它。为了应对很慢或者暂时断开,用 --max-time 设定最多等多久,用 --retry 再试一次。在里加上 -f,让告诉你是否失败。令牌之类的秘密不要直接写进,而是放在里。

curl -X POST -H "Content-Type: application/json" -d '{"name":"you"}' https://httpbin.org/post# 发送 ——对方会把它收到的内容回给你
curl -v -o /dev/null https://sonub.com/robots.txt# 显示所有请求头和响应头(> 是你发出的,< 是收回来的)
curl -fsS --max-time 10 --retry 3 -o /dev/null https://sonub.com/up && echo OK# 10 秒之内,最多重试三次——服务器检查脚本的核心
curl -H "Authorization: Bearer $TOKEN" https://api.github.com/user# 把访问令牌放在环境变量里(没有它就会得到 401)

挑战

把练习磁盘 sda 分成两半。前一半用 ext4 格式化,后一半用 FAT(vfat)格式化,分别挂载到 /mnt/data 和 /mnt/share,再用 -h 比较两者。

提示 一条 就能建立 gpt 表和两个分区:1MiB 到 50%,以及 50% 到 100%。然后执行 和 mkfs.vfat,建两个挂载点,挂载两次。

查看参考答案
sudo parted -s /dev/sda mklabel gpt mkpart data ext4 1MiB 50% mkpart share fat32 50% 100%
lsblk /dev/sda
sudo mkfs.ext4 /dev/sda1
sudo mkfs.vfat /dev/sda2
sudo mkdir -p /mnt/data /mnt/share
sudo mount /dev/sda1 /mnt/data
sudo mount /dev/sda2 /mnt/share
df -h /mnt/data /mnt/share

gpt 是现代的分区表,在 gpt 分区表里,mkpart 要先写一个名字(data、share)。两个分区会变成 sda1 和 sda2,各自拥有自己的。df -h 会列出两者和它们的大小:FAT 给自己留的空间更少,所以它的剩余空间看起来和 ext4 有点不同。

这道题请在练习终端里做,不要在自己的电脑上做——它会清除并重建磁盘。请先按“重置会话”,让磁盘变空。

参考 Learn X in Y Minutes(Bash,CC BY-SA)重新编写。部分命令仅供参考。

课程完成!太棒了,你又前进了一步。