亲手运行 AI 给出的答案
当你让 整理文件或自动化任务时,它常常会用 来回答。学会阅读 Shell,才能在运行前亲自确认是否安全。
只要请 帮忙,它真的什么都能做。但如果什么都不懂就全部交给它,你就会被 AI 束缚,还会误以为 AI 做的事情是自己亲手完成的。
设想一位主管把重要工作交给员工,却完全不了解工作内容。即使员工做得很好,主管也难以判断结果;员工离开后,工作也很难接着做。把任务交给 AI 时,同样需要基础知识。
AI 也是一样。只有懂得基础的人,才能理解 AI 做了什么、确认它是否正确,并在卡住时自己接着做下去。想成为驾驭 AI 的人,就必须懂得基础。
正是基础中的基础,也是起点。学习 Shell 命令,你会掌握操作系统的使用方法和管理的基础,而下一步就是亲自运营服务器。
不要害怕。遇到不懂的命令,问 AI 就能轻松学会。亲手一行一行地输入,一步一步学下去就好。
学习 时,人们往往先想到 ,但安装和运行 AI 工具所需的实用基础,是与 。
Shell 虽然不是构建 AI 的主要语言,却比 Python 更早出现,长期用于文件管理和日常任务自动化。
无需深入钻研,从几个常用命令开始,就能轻松入门,掌握受用一生的基本功。
在运行、管理文件和自动化开发方面非常有用。但 也可以通过、应用、笔记本和 使用,所以并不是每个 AI 用户都必须使用。
从基础到 与开发工具安装,再到运行 、、 等其他语言和,学完立即亲手运行。
打开、处理文件、查找并统计文本、把串起来。学完这四课,你就能自己使用终端了。
存放数值(),判断和重复(条件与),给一组起个名字(),安全地做出属于自己的自动化。
安装 和 开发工具,再在里用 、、 和完成实际工作。
把所有共同遵守的格式集中起来——空格位、、、连接符号。学完就能读懂从未见过的命令。这一课可以先读。
是一台真实的 电脑。你可以亲手准备一块空白磁盘,再从互联网下载并安装软件——和在真实上用的步骤完全一样。
当你让 整理文件或自动化任务时,它常常会用 来回答。学会阅读 Shell,才能在运行前亲自确认是否安全。
Shell 的几十年来几乎没变,是 macOS、 和上随处可见的通用计算机语言。学会一次,即可长期用于 macOS、Linux、服务器、平台和 AI 工具。
macOS 默认使用 ,本课程的大部分基础命令和也能直接使用。但 zsh 对未加的如何拆分、如何编号,与 bash 不同。以 bash 为准:首行 表示直接运行脚本时使用 bash。macOS 自带的 bash 3.2 不支持 -A 等功能,这些示例请在课程中运行。
用大约 20 行 ,就能启动一个真正向返回页面的 Web 。这个例子生动地说明: 归根结底是"约定好互相收发一段文本"。
#!/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
mkfifo "$PIPE"
创建一个。 收到的请求和我们生成的响应都通过这条通道来回传递。
nc -l "$PORT"
用 nc(netcat)打开 8084 ,等待浏览器连接。一行就代替了服务器。
response()
读取请求的第一行(方法与)并记录日志,把 index.html 作为 HTTP 200 响应返回。
while true; do … done
响应后连接断开,重新开始以接待下一位访客,服务器因此持续运行。
把 index.html 放在同一目录,运行 bash server.sh,然后在浏览器中打开 :8084。并非用于生产,但非常适合理解 HTTP 的原理。
学习输入、理解并找到。
说得简单点 你和电脑用文字进行的第一次对话:你打一行,它回一行。
练习创建、复制、移动、删除和读取文件与文件夹。
说得简单点 在文件管理器里用鼠标做的事(新建、复制、移动、删除),改成打字来完成。
查找匹配行、排序和去重文本、统计行数或单词数,并在文件夹中查找文件本身。
说得简单点 给超长文本用的荧光笔、排序器和计数器。
使用 | 连接输出与输入,使用 > 和 >> 把结果写入文件。
说得简单点 就像传送带:每条的结果传给下一条,最后装进箱子(文件)。
将值保存在中,并把变量、结果和计算结果嵌入文字。
说得简单点 就像贴了标签的盒子:把值放进去,以后按名字取出来。
使用 if 选择行为,使用 for 或 while 重复工作。
说得简单点 就像“下雨就带伞”和“爬十级台阶”:做判断,也做重复。
把常用组合成,传递,并理解与 。
说得简单点 就像一张常做事情的菜谱卡:叫一声名字,整件事就做完了。
组合所学知识,编写能验证输入并安全自动化工作的。
说得简单点 就像多米诺骨牌:前一步成功了,下一步才会倒下。
避免和陷阱,编写遇错安全停止的。
说得简单点 就像系好安全带:和安全能在事故发生前把它挡住。
学习在使用 :查看版本、运行、用 管理包,以及用 一次性运行工具。
说得简单点 就像的应用商店:npm 负责取来工具, 负责运行 。
简要说明如何在安装 编程助手与 。准备好 Node/ 后,多数工具只需一条即可开始。
说得简单点 就像把工具搬进新工作间,再逐一试试能不能用。
并不亲自做所有事,它是调度其他语言的指挥。学习用 -c 执行单行、用 传入多行,以及 最常用的两个工具: 与 。
说得简单点 就像翻译员:Shell 把活儿交给 、awk 或 sed,再把结果带回来。
(regex)是一种用来写下“想找的文本长什么样”的小语言。日期、邮箱和 的值各不相同,但形状一样:用 查找,用 修改,再在 和 中检查。你还会学到 macOS、 和 Windows 自带的。
说得简单点 就像一枚刻着“三位数字-四位数字”形状的印章,能找出所有吻合的文字。
只遵守几种格式。学会命令、、对象这几个位置,就不必一个一个死记,连没见过的命令也能读会写。
说得简单点 就像一份每格都写着名称的表格——认得格子,什么表格都填得出来。
是一台真实的 ,带有一块空磁盘,并且能连上互联网。把磁盘分出一个,格式化它,挂到一个文件夹上,再在里面保存文件——然后用 获取网页,用 clone 复制项目,用 、 和 安装,和在真实上一模一样。
说得简单点 就像设置一台全新的电脑:先准备好空硬盘,再下载并安装你的应用。
使用“命令 [] ”结构
查看位置并切换文件夹
使用 输出文字
使用 man 和 --help 查找帮助
需要管理员权限的命令前加上
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
命令
有些后面什么都不用加——名字本身就是整条命令。
命令 选项 对象
几乎所有都遵守的格式——做什么、怎么做、对什么做。
cd 到哪去
只接收一个要去的地方。留空就回到主文件夹。
echo 文字
会把你给的文字原样打印出来,有空格就用括起来。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
是输入文字的窗口。读取并执行命令的叫作 ,本课程使用 。$ 或 % 这样的表示可以输入命令了。在它后面输入命令,再按 Enter。这种用文字操作计算机的方式叫作 (命令行界面)。
深入了解 提示符显示的往往不只是 $。在很多电脑上,它看起来像 you@laptop:~$——依次是用户名、电脑名、当前所在的文件夹(~ 表示),最后是 $。命令运行期间提示符会消失,直到命令结束才重新出现;看不到提示符,就说明 Shell 还在忙。如果命令好像卡住了,按 Ctrl+C 停止它,提示符就回来了。
多数包含动作、。例如 -l notes.txt 表示详细列出 notes.txt 这个文件。遇到不认识的命令,可以在 man 后面写上它的名字,或者在命令后面加上 --help,就能看到它的说明书(里也会打开真正的手册,按 q 关闭)。
深入了解 选项通常以短横线开头。一个短横线后面跟单字母的短选项,而且可以合在一起写:ls -l -a 和 ls -la 是一样的。两个短横线后面跟好读的长名称,比如 ls --all。参数是命令要处理的对象——通常是文件名或文件夹名——很多命令一次可以接收好几个:ls notes.txt photos。如果名字里有空格,要用括起来,它才会保持为一个参数。
查看, 切换目录, 查看内容。~ 表示,.. 表示。
深入了解 就是文件的地址。绝对路径从磁盘的最顶层 / 开始(例如 /home/you/notes.txt),所以在任何地方都有效。相对路径从你现在的位置开始(photos/cat.jpg),所以只有在正确的文件夹里才有效。单独输入 cd 会直接回到主目录,cd - 则跳回你之前所在的文件夹。输入名字时按 Tab, 会帮你补全剩下的部分()——打错字的情况会少很多。
在这个页面上,不用安装任何东西,就能在里马上练习。在 Mac 上,打开“”应用(在 Spotlight 中搜索“终端”)。在 Windows 上,从开始菜单打开 ;要使用和本课程相同的 ,请安装 并打开 Ubuntu。在大多数 桌面上,按 Ctrl+Alt+T。
深入了解 Mac 的默认 是 zsh,它几乎能理解本课程的所有命令;如果想用和这里完全相同的 Shell,输入 bash 即可。在 PowerShell 里,少数名字(、、)能用,但行为不同,很多其他命令根本不存在,所以在 Windows 上推荐使用 WSL。不管在哪里,方向键都能调出之前的命令:按 ↑ 就能再次运行或修改上一条命令,不必重新输入。
凡是改变整台电脑的操作 —— 安装,或往 /etc 这类系统目录里写文件 —— 都属于管理员(root),直接执行会得到 。在前加上 ,同一条命令就以管理员权限运行,例如 sudo add tree。不会要密码;在自己的电脑上则要输入登录密码。只在命令确实需要时才用它。
深入了解 sudo 通常让一条命令以管理员权限运行,可用于安装软件包(apk add、apt install)、修改 /etc 或 /usr、挂载磁盘。它不会防止操作失误,执行前要检查命令。中出现权限错误时,先检查文件所有者和权限。 install -g 是否需要 sudo,也取决于安装位置。
把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。
ls -la ~/projects
ls:列出文件夹里有什么。-l长格式:每行一项,并显示权限、所有者、大小和日期。-a全部:连名字以点开头的(例如 )也显示出来。~/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 课会正式学它,但从这里开始每一课都会见到 —— 把它读作「然后把这个递过去」就好。
ls|wc -l
把文件列出来, -l 数它打印了多少行,于是得到的就是「有几个文件」。
cat notes.txt|head -3
打印整个文件, -3 只留下其中前三行。
echo "hello shell"|wc -c
-c 统计 输出的数,包括末尾的换行符。结果不会保存到文件。
创建文件夹和空文件
复制、移动、重命名和删除
使用 、、 查看内容
安全使用删除
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
mkdir 选项 对象
新建文件夹。加上 -p 时,路上缺的上层文件夹也一起建好。
cp 选项 对象 到哪去
复制有两个位置——复制什么,然后放到哪。整个文件夹要加 -r。
mv 对象 到哪去
移动和改名是同一个格式——写新文件夹就是移动,写新名字就是改名。
rm 选项 对象
删除你写出的东西。这里没有回收站,所以用 -i 先问一句。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
创建文件夹, 创建空文件。mkdir -p 可以一次创建缺少的。和大多数一样,它们成功时什么也不输出,所以请用 查看结果。
深入了解 touch 还有第二个用途:如果文件已经存在,它不动内容,只更新“最后修改”时间,所以运行两次也安全。相反,如果文件夹已经存在,mkdir 会报错 'File exists'——除非加上 -p,它会默默接受已存在的文件夹。带空格的名字需要加(mkdir "my notes");很多人用 - 或 _ 代替空格,以免这种麻烦。
创建副本并保留原文件, 则移动文件或改名。两者都先写来源,再写目标。例如 mv memo.txt diary.txt 会在同一文件夹中将 memo.txt 改名为 diary.txt。
深入了解 当最后一个是一个已存在的文件夹时,cp 和 mv 会把文件以原来的名字放进去:cp a.txt b.txt backup/ 会把两个文件复制到 backup 里。要复制整个文件夹必须加 -r(),否则 cp 会拒绝;mv 不需要任何就能移动文件夹。用 mv 重命名即使是巨大的文件也是瞬间完成,因为只改了名字,什么都没有复制。
显示全部内容, 和 显示开头或结尾。 不经过回收站,必须谨慎。
深入了解 如果不另外指定,head 和 tail 显示 10 行:head -n 3 显示三行。tail -f 会持续跟踪文件,新增的行一出现就打印出来,人们在运行期间就是这样查看日志的(按 Ctrl+C 停止)。长文件请用 less:它一次显示一屏,按 Space 翻页,/ 搜索,q 退出。因为 rm 不经过回收站,好习惯是先用相同的名字或模式运行 ,确认后再 rm。
把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。
cp -r photos backup/photos-2026
cp:复制。-r:复制文件夹及其中的所有内容。没有 -r, 会拒绝复制文件夹。photos源:要复制的文件夹。backup/photos-2026目标:副本会以 photos-2026 的名字创建在 backup 里。backup 文件夹必须已经存在。什么也没有——这类命令成功时不输出任何内容。
复制成功时,cp 什么也不输出。用 backup 检查——应该能看到 photos-2026。如果 backup 不存在,就会出现 'No such file or directory';先用 -p backup 创建它,再重新复制。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
ls -l|head -3
详细列表长得会滚出屏幕,所以用 -3 只看前三行。
mkdir -p backup&&cp notes.txt backup/&&ls backup
前一步成了才走下一步 —— 就是「那件事成了才做」,最后一步把结果显示给你看。
ls photos|wc -l
不打开 photos 文件夹,就数出里面有多少项。
使用 查找行
使用 grep -i 和 -n
使用 排序
使用 统计
使用 find 按名称和类型查找文件和文件夹
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
grep 选项 要找的内容 在哪找
要找的内容在前,到哪里找在后。这个顺序不会变。
sort 选项 文件
给文件的行排序。-r 倒过来排,-n 按数字排。
wc 选项 文件
统计文件内容:-l 数行,-w 数单词,-c 数。字节数可能不同于字符数。
find 在哪找 条件 动作
find 读起来像一句话——在哪找、符合什么、找到后做什么。
试一试 点一下示例,它的就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
word file 只显示匹配行。-i 忽略,-n 显示行号,-r 搜索。
深入了解 grep 逐行读取文件,并打印所有在任意位置包含该模式的行——搜索 也会找到 'concatenate'。加上 -w 就只匹配完整的单词。当模式里有空格或符号时,要用括起来(grep "error " app.log)。配合 -r 和一个文件夹使用时,grep 会在每个匹配行前面写上文件名,这样你就能看到每处结果在哪里。
将各行排序。 只合并相邻的重复行,所以要先用 sort 把分散的重复行放到一起。 -l 统计行数。下一单元会用连接这些,完成简单统计。
深入了解 sort 按文字比较,所以默认情况下 10 排在 9 前面;数字请用 sort -n,倒序用 sort -r。uniq 只合并相邻的相同行,所以它几乎总是跟在 sort 后面。不带的 wc 会依次打印三个数字——行数、单词数和数;-l、-w 和 -c 可以只选其中一个。
查找文件里的行,find 则查找文件和文件夹本身。它从起始文件夹出发,走遍它下面的每一个文件夹,把每个匹配项的单独打印成一行。基本格式是 find 位置 条件 动作。
不带条件只输入 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/
grep:查找包含某个模式的行。-r:搜索该文件夹及其所有子文件夹中的每个文件。-i忽略:TODO、Todo 和 todo 都会匹配。-n行号:显示每个匹配所在的行号。"todo"要查找的模式。只有一个词时可以不加,但加上是个好习惯。src/查找位置:src 文件夹。src/app.js:12: // TODO: check the password length src/utils/date.js:3:// todo: handle time zones
每个结果都写成“文件名 : 行号 : 该行内容”。打开那个文件的那一行去处理它。没有任何匹配时, 什么也不打印——这不是错误。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
grep "echo" todo.sh|wc -l
找出匹配的行, -l 数这些行,于是你知道它出现了多少次。
sort notes.txt|head -3
先把行排好序,再留下前三行 —— 也就是字母序最前面的三条。
find . -name "*.txt"|wc -l
find 每找到一个就打印一行,所以数行数就是数文件数。
使用 | 连接命令
使用 > 保存输出
使用 >>
理解流
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
命令 | 命令
左边的输出成为右边命令的输入,中间不经过文件。
命令 > 文件
把输出送进文件而不是屏幕。原来的内容会被。
命令 >> 文件
做同样的事,但不,而是接在末尾。
命令 < 文件
把文件当作的输入送进去——箭头指着文字流动的方向。
命令 2> 文件
2 指的是,所以这样能把错误和结果分开。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
A | 命令B 把 A 的输出交给 B 作为输入。 | txt 中,ls 列出名称,grep txt 只显示含有 txt 的行。先单独运行左边的命令查看结果,再加上 | 和下一条命令。
深入了解 每个命令都有一个(读取的地方)和一个标准输出(写入的地方——通常是屏幕)。把左边命令的输出连接到右边命令的输入,两者同时运行,所以数据一产生就流过去。管道要一步一步搭:先运行第一条命令,看看它的输出,再加上 | 和下一条命令。
> ,>> ,< 使用文件作为输入;2> 单独保存错误。
深入了解 在启动之前就设置好重定向。所以 data.txt > data.txt 会毁掉文件:> 先清空 data.txt,然后 sort 读到的是一个空文件。请写到一个新文件里(sort data.txt > sorted.txt)。数字是各个流的名字:0 是输入,1 是普通输出,2 是错误,所以 2> 只把错误送到文件,>(等同于 1>)只送普通输出。
把输入保存到文件,同时也发送到屏幕或下一条。可以把它想成 T 形,将输出分成两路。 | tee list.txt 可以边看列表边保存。要到已有文件,用 tee -a。
深入了解 写入需要管理员权限的文件时,tee 也是常用的方法。 "text" | tee /etc/somefile 可以成功。但 sudo echo "text" > /etc/somefile 会失败。这是因为重定向(>)是由你自己的 完成的,而不是 sudo。如果只想要文件、不想在屏幕上再显示一份,就在末尾加上 > 。
把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。
ls ~/Downloads | grep pdf | wc -l
ls ~/Downloads列出 Downloads 文件夹。当输出进入时, 每行写一个名字。|管道:把这个列表交给下一条,而不是显示出来。grep pdf只保留包含 pdf 的行。|第二个管道:把剩下的行继续传下去。wc -l统计到达的行数。7
只有最后一条命令会写到屏幕上;其他命令只是把输出传下去。那个数字就是名字里包含 pdf 的文件个数。想看名字本身,就去掉最后一部分(| -l)再运行一次。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
ls|sort -r|head -3
三条接连:列出来、把顺序倒过来、留下前三条。每条都只做自己那一小件事。
cat notes.txt|wc -l>count.txt&&cat count.txt
> 把数字存进文件, 再把那个文件打印出来,让你看到确实存进去了。
ls|grep txt|wc -l
先缩小范围再数 —— 回答「这样的有几个?」的常用办法。
创建并读取变量
安全变量
使用 $(( )) 计算
使用 $( ) 获取命令结果
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
名字=值
把值放进自己起名的盒子里。= 两边不要留空格。
"$名字"
用 $ 把值取出来,保留,空格才不会散掉。
$(命令)
运行一个,把它打印出来的内容当作值来用。
$((值))
括号是两层就表示计算—— 会替你算出来。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
使用 name=value 创建,使用 $name 读取。= 两边不能有空格。
深入了解 名只能用英文字母、数字和 _(不能用汉字),而且不能以数字开头。按照习惯,自己的变量用小写,和其他共享的设置(、HOME)用大写。变量只存在于创建它的 里:打开一个新,它就不见了。要让每次会话都保留这个值,就把那一行写进 ~/.bashrc(Mac 上是 ~/.zshrc)。
使用 "$name" 可以保留值中的空格,这是安全 的核心习惯。
深入了解 里 $ 仍然起作用,所以 "$name" 会得到值,但双引号会保留空格,并阻止 * 变成文件名。如果后面直接跟着字母,就用大括号标出名字在哪里结束:"${name}s" 打印值再加上 s,而 "$names" 会去找一个叫 names 的变量。单引号会完全关闭变量('$name' 打印的就是 $name)。
$(( )) 放入计算结果,例如 $(( 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"
today=创建一个叫 today 的。= 两边不能有空格。$(date +%F)运行 date,并把它的结果放在这里;+%F 要求日期以 2026-10-17 的形式输出。;第一条结束——接着在同一行运行下一条。cp notes.txt复制 notes.txt ……"notes-$today.txt"…… 到这个新名字:$today 会被替换成日期,让它保持为一个名字。什么也没有——这类命令成功时不输出任何内容。
复制成功时什么也不打印。现在 会显示 notes-2026-10-17.txt;明天再运行同一行,就会得到一个带明天日期的新文件——一个简单的每日备份。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
count=$(ls|wc -l)&&echo "files: $count"
$( ) 接住连起来的打印的东西,于是一整串连接变成了一个值。
echo "$HOME"|wc -c
里的值也能像别的输出一样原样递过去。
first=$(head -1 notes.txt)&&echo "$first"
把文件的第一行放进自己起的名字里,再打印出来。
使用 if / elif / else
使用 比较
使用 for 处理列表
区分数字和比较
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
if [[ 条件 ]]; then 动作; fi
if 开头,fi 收尾。条件决定动作到底要不要运行。
for 名字 in 列表; do 动作; done
列表里的项目一个一个进到名字的位置,每进一次动作就运行一次。
while [[ 条件 ]]; do 动作; done
条件为真就一直重复,所以里面必须有东西去改变这个条件。
case 值 in 要找的内容) 动作;; esac
拿一个值去比对好几种形状。每个用 ;; 结束,最后用 esac 收起全部。
试一试 点一下示例,它的就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
if 检查条件,成立时执行 then 后面的。用 elif 检查其他条件,用 else 写所有条件都不成立时的操作,最后以 fi 结束。把 if 条件; then ... fi 读成“如果条件成立,就做这件事”。
深入了解 if 后面的条件其实是一条命令:if 运行它并检查它的,0 表示真。所以 if -q milk notes.txt; then … 不需要任何括号也能工作。把 then 和 fi 之间的行缩进,这样能看清每个块在哪里结束;如果把所有内容写在一行,别忘了 then 前面的 ;。
用 ==、!=,数字用 -eq(等于)、-gt(大于)、-lt(小于)、-ge(大于或等于)、-le(小于或等于),文件和文件夹用 -f、-d。
深入了解 在 里可以用 (并且)和 ||(或者)连接测试,! 表示“不”:[[ -f notes.txt && ! -d backup ]]。拿不准时,比较中的就加上;在旧式的单方括号 [ ] 里则必须加,因为空变量会在里留下一个空洞。只涉及数字时,(( )) 读起来更自然:if (( score >= 80 )); then …
for item in list; do ... done 会逐项处理列表,也可 {1..5} 或文件。
深入了解 in 后面的列表在循环开始前就了,所以 for f in *.txt 会对每个文本文件运行一次,文件名放在 $f 中。如果没有文件匹配,模式本身会被使用一次($f 变成 *.txt),所以要用 [[ -e $f ]] 检查,或者打开 (第9课)。不要对 $() 做循环;名字里有空格时,用模式更安全。
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
for f in *.txt对这个文件夹里的每个 .txt 文件重复一次;每次 f 里存着一个文件名。do要重复的从这里开始。echo "$f: …"打印文件名,后面跟上它的行数。$(wc -l < "$f")统计那个文件的行数。用 < 把文件作为输入送进去, 就只打印数字。done结束——继续处理下一个文件。notes.txt: 3 lines todo.txt: 12 lines
每个文件一行。循环运行的次数和 .txt 文件的个数一样多。如果一个都没有,它会以 *.txt 这段文字运行一次,wc 会报错——第9课的 可以避免这种情况。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
for f in *.txt; do wc -l "$f"; done|sort -n
打印出来的东西汇成一股流,所以一个 -n 就把全部排好了。
ls|grep -q txt&&echo "txt here"
-q 什么都不打印,只报告成功还是失败,而 看的正是这个。
if ls|grep -q todo; then echo "found"; fi
连起来的也能当 if 的条件 —— 它递过去的是成功或失败,不是文字。
定义并调用函数
使用 $1 等参数
说明 #!/bin/bash
理解
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
名字() { 动作; }
给一组起个名字。大括号包着主体,$1 是接到的第一个值。
名字 值
自己写的和别的一样调用——名字在前,值在后。
bash 文件
把交给 。文件没有也能这样跑起来。
chmod +x 文件
+x 标记这个文件可以运行,只需要做一次。
./文件
./ 的意思是「就在这里的文件」,因为 不会翻。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
name() { ; } 为一组命令命名,内通过 $1、$2 读取。
深入了解 先定义函数,再调用。函数中的 $1、$2 是传给该函数的参数。文字结果可用 输出,再用 result=$(myfunc) 接收。return 不返回文字:return 0 表示成功,return 1 等非零值表示失败,取值范围为 0~255。
是把多条保存到 hello.sh 这样的文件中。首行 叫作 shebang,表示用 ./hello.sh 直接选择 。因此,即使当前 是 zsh,脚本也由 bash 执行。也可以用 bash hello.sh 直接指定执行。
深入了解 shebang 必须是第一行,并且必须正好以 #! 开头。很多脚本改用 #!/usr/bin/env bash:env 会找到 bash 安装在哪里,这在 bash 不在 /bin 里的系统上很有用。.sh 后缀只是给人看的提示;决定由哪个程序运行文件的是 shebang,而不是文件名。
先执行 +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
backup()定义一个叫 backup 的。{ … ; }函数体。{ 后面的空格和 } 前面的 ; 都是必需的。cp "$1" "$1.bak"把第一个复制一份,名字后面加上 .bak。&& echo …打印一条消息——只在复制成功时。backup notes.txt调用函数:在函数内部,$1 变成 notes.txt。saved notes.txt.bak
第一行只是定义函数,什么也不打印。第二行运行它并打印消息。换一个文件名调用,同样的步骤就会在那个文件上运行——这正是函数的意义所在。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
bash hello.sh|wc -c
打印的东西,也能像任何的输出一样继续往下递。
chmod +x hello.sh&&./hello.sh
标记成可运行再运行,一行写完 —— 后半只有在前半成了之后才发生。
bash todo.sh|head -1
只看打印出来的第一行。
通过 查看成功失败
使用 与 || 连接处理
使用 for 处理多个文件
理解 定时执行
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
命令 && 命令
只有前一个成功了,才运行后一个。
命令 || 命令
只有前一个失败了,才运行后一个——这是你的安全出口。
命令 >> 文件 2>&1
把结果和都收进同一个记录文件。
时刻 命令
的格式——五个时间位置,后面跟。它在自己的电脑上运行,这个里不会跑。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
成功返回0,失败返回其他值。 读取上次结果,A B 只在 A 成功时运行 B。
深入了解 非零的退出码由每个自己决定——例如 在没有匹配时返回 1,出错时返回 2。A || B 和 && 正好相反:只有 A 失败时才运行 B,例如 backup || "could not create"。成功的以 exit 0 结束,失败的以 exit 1(或其他数字)结束,这样运行它的一方——人、 或另一个脚本——就能检查结果。
把和文件组合起来,即可对许多目标自动执行相同工作。
深入了解 改动文件前,在命令前加 预览要执行的内容,这叫作。备份名称中加入 $(date +%F) 可以按日期区分,但同一天再次运行仍会重名。要为每次执行保留独立备份,还需加入时间。每一步输出提示,更容易看清进度。
用于定时运行。用 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
*/15分钟:每 15 分钟(0、15、30、45)。9-18小时:从 9 点到 18 点。* *每月的任何一天、任何月份。1-5星期几:星期一(1)到星期五(5)。/home/you/check.sh要运行的,带完整—— 从你的主文件夹开始,而不是从脚本所在的文件夹。>> /home/you/check.log把它的输出到日志文件末尾。2>&1把错误也送到同一个日志里,方便以后查看。什么也没有——这类命令成功时不输出任何内容。
这一行不是在下输入的:用 crontab -e 保存它,cron 就会自己运行——工作日从 9:00 到 18:45,每 15 分钟一次。里什么也不会出现;打开 check.log 查看发生了什么,用 crontab -l 确认这一行已经保存。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
mkdir -p logs&&ls>logs/list.txt&&cat logs/list.txt
三步,每一步都等前一步成功 —— 就是这样不出事的。
bash todo.sh>out.txt2>&1&&cat out.txt
2>&1 把送进和结果相同的文件,于是一份记录里装着全部经过。
cat missing.txt||echo "no such file"
是 && 的另一面 —— 正因为前面失败了,这句话才出现。
说明三种引用方式
理解单词分割与通配
使用 set -euo pipefail
始终引用
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
命令 "对象"
带空格的名字只有在里才算一个对象。没有引号就看成两个。
"$名字"
里 $ 依然会把值取出来,但会把值保持成一整块。
'文字'
表示「就是这些字符」——连 $ 也失去作用。
命令 -- 对象
-- 后面的东西不会当成来读,这能救下以 - 开头的名字。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
不同,的方式也不同。
拿不准时,用双引号包起来是基本原则。
深入了解 做个小测试就能看出区别:当 name="a b" 时, $name 打印 a b(空格被压缩了),而 echo "$name" 打印 a b。要在文本里放一个单引号,就用双引号把它括起来("it's")。在双引号里只有少数几个字符仍然特殊——$、`、" 和 \——在它们前面加反斜杠就能保护它们。
先按空白拆分未加的(分词),再把其中的 *、? 为匹配的文件名(展开)。如果 file 的值是 my notes.txt, $file backup/ 会把它当成两个文件。写成 cp "$file" backup/ 就能作为一个名称传入。
深入了解 分割使用的是 中的字符——默认是空格、制表符和换行符。接着,通配会把 *、? 和 [abc] 替换成匹配的文件名,所以一个保存着 * 的变量可能突然变成文件夹里所有的文件名。这两件事都只会发生在没有加引号的展开上;在双引号里永远不会发生。
set -euo pipefail 有助于及早发现中的错误。各作用如下。
这些选项无法防止所有错误。还要用 检查,并确认实际结果。
深入了解 把 set -euo pipefail 放在 紧接着的下一行,让它作用于整个脚本。shellcheck 用包管理器安装(brew install shellcheck、 apt install shellcheck)。它能在很多里工作,在你输入时就给错误加上下划线。它的警告带有像 SC2086(缺少)这样的编号,每个编号都有一个页面解释如何修正。
把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。
cp -- "$src" "$dest/"
cp:复制。--结束:-- 后面的所有内容都是名字,即使以 - 开头。"$src"带的源:带空格的名字也保持为一个。"$dest/"带引号的目标文件夹;结尾的 / 表明它必须是一个文件夹。什么也没有——这类命令成功时不输出任何内容。
复制成功时什么也不打印。没有引号的话,像 'my report.txt' 这样的名字会被拆成两个参数;没有 -- 的话,以 - 开头的名字(比如 -r)会被当成选项。两道保护都在,不管值里有什么,每个值都会正好作为一个名字传过去。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
ls *.txt|wc -l
先把星号填好,所以 -l 数的是真实的文件名。
wc -l "report 2024.txt"|head -1
把带空格的名字守成一个文件;没有引号就看成两个了。
grep 'e' "report 2024.txt"|wc -l
要找的内容用,带空格的文件名用双引号,然后再数。
用 node -v 查看 Node.js 版本
用 node app.js 运行 JavaScript 文件
用 npm 安装包并运行脚本
用 npx 在不的情况下运行一次工具
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
node 文件
把 文件交给 Node,它就照着运行。
命令 选项
--version 几乎在所有工具上都通用,是确认是否装好的最快方法。
npm 动作
在第二个位置接子——比如 run、install、init。
npm 动作 选项 对象
在 install 后填写包名。连接互联网后即可运行。
试一试 点一下示例,它的就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
是在之外(、)运行 的()。制作网页的工具、(CLI)工具、构建和许多 编程助手都依赖它。学完 后掌握 Node,安装开发工具会轻松很多。
深入了解 Node.js 使用和 Chrome 相同的 JavaScript 引擎(V8)。但它不是用来显示网页的,而是读写文件、建立网络连接、运行其他程序。偶数版本(22、24 等)会成为长期支持(LTS)版本,多年都能得到修复,所以学习时选它最稳妥。可以从 nodejs.org 安装,也可以用 安装(见进阶内容)。
用 -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)。
安装和管理可复用包。项目的 记录名称、版本、所需包(dependencies)和运行()。在该文件夹运行 npm install 安装包,用 npm run start 执行 start 脚本。 可以运行包提供的命令,无须。
深入了解 npm install 会创建两样东西。_modules 是下载的代码——通常非常大;不要修改它,也不要它。 列出安装的确切版本——需要提交它,这样每个人得到的都一样。npm ci 严格按照锁文件安装,自动构建用的就是它。像 ^4.17.21 这样的版本表示“这个版本或兼容的更新版本”。
把一条大家真正常用的命令拆开来看。先读每一段的作用,再看屏幕上会出现什么。
npm install --save-dev prettier
npm 自带的包管理器。install下载一个包并把它加入项目。--save-dev记录在 下:开发时需要,应用真正运行的时候不需要。-D 是简写。prettier包名——这里是 Prettier,一个把整理得整整齐齐的工具。added 1 package, and audited 2 packages in 2s found 0 vulnerabilities
会报告它添加了多少个包(一个包可能会带来其他包),并运行一次快速的安全检查(audit)。现在 package.json 的 devDependencies 下列出了 prettier, prettier --write . 就可以在项目上运行它。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
node app.js|wc -l
Node 打印出来的也只是文字,所以 能像数别的一样数它。
cat package.json|grep name
读文件,只留下你真正关心的那一行。
node --version&&npm --version
两个检查写在一行;前一个工具真的在,后一个才会运行。
说明 是许多工具安装的前提
了解 Claude Code、Codex、Grok、 的常见安装命令
区分(-g)与一次性运行()
养成安装后检查版本的习惯
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
命令 选项
问工具的版本号,确认它真的装好了。
command -v 命令
存在就打印,不存在就什么都不打印。
命令 动作 选项 对象
安装大多是这个格式——工具、子命令、、包名。请在自己的电脑上用。
curl 选项 值 -o 文件
从网址下载,并保存到 -o 后面的文件。联网后也能运行。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
现代开发不只靠。下面这些工具都在里运行。
第10课学过的 Node 和 就是起点。
深入了解 这些工具大多需要一个账号:你在里登录一次(例如 login),工具就会记住你。它们都是 里的普通,所以本课程里的一切——、、——对它们同样适用。AI 编程助手可以替你运行,这让理解一条命令在做什么变得更重要,而不是更不重要。
官方方式可能变化,请始终以官方文档为准。常见的起点如下。
安装后用 claude --version、codex --version、firebase --version 这样确认版本。
深入了解 安装后找不到时,先打开新的,让更新后的 生效。手动重新加载设置时,应使用当前 对应的文件,例如 zsh 的 ~/.zshrc、bash 的 ~/.bashrc。更新应沿用安装方式。npm 工具可用 npm install -g firebase-tools@latest 更新;删除则在 npm uninstall -g 后写包名。
| 只使用可信官方。 权限出错时,优先修复 而不是 。团队与 中用 固定版本更安全。
深入了解 先把安装下载下来,运行之前读一读。用 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
curl从一个网址下载。-fsSL四个短写在一起:遇到错误就失败、保持安静、但仍显示错误、跟随跳转。https://claude.ai/install.sh安装的官方地址。-o install.sh把它保存到文件里,而不是马上运行。什么也没有——这类命令成功时不输出任何内容。
下载成功时什么也不打印。用 less install.sh 阅读这个文件,然后用 install.sh 运行它。如果用 | bash 代替 -o install.sh,脚本会在你看都没看的情况下立即运行。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
cat tools.md|grep -i git|head -3
读一遍,只留讲 的行(-i 不分),再只看其中三行。
which bash&&bash --version|head -1
先找到这个,然后才问它的版本。
ls|sort|head -2
最朴素的连接 —— 列出来、排序、截断。
能用 -c 和 -e 执行其他语言的单行
能把程序保存为文件并用 python3 文件.py 运行
能用 heredoc(<<PY)把多行程序交给
能用 sed 批量修改文件中的文本
能用 awk 从表格数据中并汇总
能在运行 AI 给出的代码前先安全确认
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
awk '{动作}' 文件
对每一行运行你写的动作。$1 是。
awk '条件 {动作}' 文件
在大括号前面放一个条件,只有符合的行才会执行动作。
sed 's/要找的内容/值/' 文件
s 表示替换——找这个,换成那个。斜杠就当作分隔的线来读。
python3 文件
把 .py 文件交给 ,它就照着运行。
python3 -c '动作'
-c 不用文件,就地运行一行 。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
只用 完成所有计算很快就会碰壁。Shell 的角色是决定"何时叫谁":计算和数据交给 ,表格汇总交给 ,文本批量修改交给 ,再由 Shell 把结果串起来。懂 Shell,就能调用自己从没学过的语言。
多数语言都有"把当作直接运行"的: 是 -c "代码", 是 -e "代码",Perl 是 -e,Ruby 是 ruby -e。只要养成用 python3 -c "print(2**10)" 代替计算器的习惯,就会强大得多。
如果一段不是用一次就丢,就把它存成文件。把写进 hello.py,输入 hello.py 就能直接运行。
用第 7 课学过的 会更简单。
运行结束后输入 ,就能看出是成功(0)还是失败(1)。所以可以像第 8 课那样,用 接上下一条。
用 可以一次传入多行。输入 - <<'PY',从下一行开始写 ,最后单独写一行 PY。两个标记之间的内容会交给 Python。- 表示从而非文件读取代码。编程也会用这种形式执行多行代码。
用包住(<<'PY'),正文会原封不动地传过去;不包(<<PY), 会先替换正文里的 $ 和 $()。 里很少出现 $,但或中一旦有 $ 就会悄悄出错,所以传代码时以加引号为默认做法。
用 替换文字的基本形式是 s/查找/替换/。
要直接改文件本身,就写 sed -i.bak 's/查找/替换/' 文件。它会把原文件保留为 文件.bak,所以很安全(macOS 和 的差别请看下面的“注意”)。
既是一门小语言,也是处理表格文本的。它按空白拆分每一行,$1 是,$2 是第二列,NF 是列数,NR 是行号。awk '模式 { 操作 }' 用模式选择行,用 { } 指定操作。省略模式会处理所有行,省略操作会输出匹配行。BEGIN 在读取前执行一次,END 在读完后执行一次,可用于准备和汇总。
从读、向标准输出写的之间,可以用自由连接。例如 sales.txt | '{print $2}' | -n | -3 一行就完成这些事。
awk 与 就这样用管道相连,而 则通过 -c 或 接收。这就是“ 是指挥”的真正含义。
通常正是用这种 形式作答。粘贴之前只需确认三件事:①用眼睛读一遍它要改哪些文件;②看看有没有 或 > 这类操作;③重要文件先用 原文件 原文件.bak 备份。会读 ,就意味着能验证 AI 的答案。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
awk '{print $1}' sales.txt|sort|uniq -c
取出, 把相同的行聚到一起, -c 给每一组计数。
python3 hello.py|wc -c
打印, 计数 —— 彼此完全不必知道对方。
sed 's/=/ /' config.txt|awk '{print $1}'
把 = 换成空格, 才能把名字当作来读。
能用 . * + ? [ ] ^ $ 等基本符号描述文本的形状
能用 grep -E 找出匹配的行,并用 -o 只取出匹配的部分
能用 sed -E 和 调换和修正文本
能说明和的区别,以及为什么 \d 在 grep -E 中不是数字
能在 Shell 脚本中用 [[ 值 =~ 模式 ]]、在 Python 中用 re 模块检查值
能选用 macOS、Linux 和 Windows 自带的正则命令
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
grep -E '要找的内容' 文件
-E 打开更完整的形状语法,在那里 | 表示「或者」。
grep -oE '要找的内容' 文件
-o 只打印匹配上的那一段,而不是整行。
sed -E 's/要找的内容/值/g' 文件
g 会把一行里匹配上的全部替换掉,而不只是第一个。
[[ 值 =~ 要找的内容 ]]
=~ 直接在 里把值和形状比对一下。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
搜索 010-1234-5678 只能找到这个号码。要找同样 3-4-4 位数字的格式,就写 [0-9]{3}-[0-9]{4}-[0-9]{4}。[0-9] 表示一位数字,{3} 表示重复三次。这种搜索规则就是。、、、vim、、、PHP 的基础符号相似,但细节语法需要分别确认。
. 表示任意一个字符(a.c 能匹配 abc 和 a1c)。[abc] 表示方括号里的某一个字符,[^abc] 表示不在其中的一个字符,[a-z] 或 [0-9] 表示某个范围内的一个字符。常用的字符集合有简写:\d 是数字,\w 是单词字符(字母、数字或 _),\s 是空白。大写(\D \W \S)表示相反的意思。 工具还接受 [[:digit:]]、[[:space:]] 这样的 名称。
* 表示前一个字符出现 0 次或更多次,+ 表示 1 次或更多次,? 表示 0 次或 1 次(colou?r 能匹配 color 和 colour),{n} 表示正好 n 次,{n,m} 表示 n 到 m 次。重复符号只作用于紧挨在它前面的一个字符(或一个方括号、一个分组)。而且重复会尽可能多地抓取():".*" 会从行里第一个一直取到最后一个引号,所以想停在一对引号之内,就写 "[^"]*"。
^ 是行首,$ 是行尾。^error 匹配以 error 开头的行,done$ 匹配以 done 结尾的行,^$ 匹配空行。\b 是单词边界,所以 \bart\b 能找到 art,却不会匹配 party。这些符号不占用任何字符,只标出一个位置。别把它们和方括号开头的 ^([^0-9])搞混——那表示“不是”,意思完全不同。
red|blue 表示 red 或 blue。( ) 把几个字符变成一个整体——(ab)+ 能匹配 ab、abab 和 ababab。分组还能把找到的内容取出来:在 中用 \1 \2,在 中用 ${BASH_REMATCH[1]},在 中用 m.group(1)。所以把 2026-10-15 变成 15.10.2026 只需要一行。
. * + ? ( ) [ ] { } ^ $ | \ 都有特殊含义。想找这个字符本身,就在前面加 \:点写成 \.,星号写成 \*,左括号写成 \(。如果照原样写 192.168.0.1,每个 . 都表示“任意字符”,于是 192x168y0z1 也会匹配。另外,在 中一定要用把模式括起来,否则还没看到它们,Shell 就已经按自己的方式改动了 * ? $ { }。
在 和 中会遇到三种语法:
在 工具里写 [0-9];如果确实想用 \d,就用 grep -P()。、、PHP 和 (rg) 天生就认识 \d。
macOS 和 自带 、、、find 和 。不过各个操作系统略有不同。
下面的“各操作系统自带的”表汇总了示例。
在 中,用 [[ $value =~ 模式 ]] 检查一个值。
在 中先 import re,再用下面的。模式写成前面带 r 的原始,如 r"\d+"。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
grep -E "ERROR|WARN" app.log|wc -l
注意:里的 | 对 来说是「或者」,而外面那个 | 才是连接的符号。
grep -o "user=[a-z]*" app.log|sort|uniq -c
只取出匹配的部分,排好序,再数每一个出现了多少次。
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 |
和 默认读取,所以 + ? | ( ) { } 前面要加 \ 才能当功能用。加上 -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 ,也要检查中的实际结果。
能把任何命令按位置读出来——做什么、怎么做、对什么做
能分清选项的四种形状——-l、--all、-la、-n 5
会在对象位置上用,并知道是 Shell 先把它填好
能用 --help 和 man 自己查出陌生命令的模式
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
命令 选项 对象
如果只记一个,就记这个格式——做什么、怎么做、对什么做。
命令 --help
几乎每个都会在这里告诉你它自己的模式。这就是学会陌生命令的路。
命令 选项 对象 到哪去
先接来源,再接目标。并非所有双都如此,要确认每个位置的作用。
命令 要找的内容
对象位置上的 * 由 先填好,所以看到的永远是真实的文件名。
命令 选项 | 命令 选项
两边各自守着自己的格式,只是把结果递过去。
试一试 点一下示例,它的命令就会放进的输入行,按 Enter 才会运行。运行之前也可以先改一改。
我们敲下的几乎每一行都是、、对象这个顺序。 -la photos 的意思是「看列表(ls),详细到(-la),photos 文件夹」。命令总在最前面,被动手的东西总在最后面。一旦看出位置,长长的一行就不可怕了——一个位置一个位置地读就行。
决定如何工作。短选项以一个连字符开头(-l),长选项以两个开头(--all)。 的 -la 合并了 -l -a。有些选项还要接值,如 -n 5、--output report.txt。先按“命令 选项 目标”来读,再查帮助确认必需选项和能否合并。
目标位置填写文件或文件夹。、 这类先写来源,最后写目标。文件名模式中,* 匹配零个或多个字符,? 匹配一个字符,[0-9] 匹配一位数字。 先匹配的文件名,再交给命令。{a,b} 是另一种展开,无论文件是否存在,都会生成 a 和 b 两个名称。
用符号连起来——| 把结果递给下一个命令,> 写进文件,>> 接在文件末尾, 只在成功之后,|| 只在失败之后,; 就直接运行下一个。包裹的记号只有四种——"..." 把文字保持成一整块,$(...) 把命令的结果塞进来,$((...)) 做计算,[[...]] 检查条件。成块的写法总是一开一合成对——if 配 fi,for 配 done,{ 配 }。
不用背。在名字后面加 --help,或者敲 man 加名字,然后读第一行,模式就写在那里。那一行里方括号 [ ] 表示「可以不写」,... 表示「可以写好几个」,直接写出来的词就是你要填的位置。另外每个命令都用 0 表示成功,用 $? 就能看到。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
ls *.txt|wc -l
对象位置,然后,然后又是一个有自己位置的 —— 两边是同一个格式。
ls --help|head -5
会在头几行写明自己的模式,所以把那段帮助交给 。
cat notes.txt report.txt|wc -l
对象位置上的两个文件汇成一股流,行数把两个一起算。
用 找到磁盘和分区,并说明什么是分区、和挂载点
用 给空白磁盘分区,用 格式化,再用 挂载
用 -h 查看剩余空间,在新磁盘上写入文件,然后安全地卸载
用 curl 和 git clone 从互联网下载
用包管理器安装程序——系统用 apk, 用 里的 pip,Node 项目用 npm
每个都遵守固定的格式。格式学会一次,没见过的命令也能读懂。
lsblk 对象
加上对象后, 只显示那块磁盘和它里面的。
sudo mkfs.ext4 对象
对象是,不是整块磁盘——而且上面的一切都会被清除。
sudo mount 对象 到哪去
先写要挂什么,再写挂到哪里:先设备,后文件夹。
curl 选项 值
-s 让 安静,-I 只要响应头;值是网址。
命令 动作 名字
包管理器都是同一个形状:管理器、要做的事、包—— add、 install、 install。
试一试 点一下示例,它的就会放进练习终端的输入行,按 Enter 才会运行。运行之前也可以先改一改。
把每块磁盘都当作 /dev 下面的一个文件。(list block devices)会把它们画成一棵树:
lsblk 只看不改,所以它永远是第一步。每次打开这一课或按下“重置会话”,磁盘都会重新变空——想练多少次都可以。
是磁盘上的一块区域,用起来就像一个独立的驱动器。磁盘最开头的分区表记录着每块区域从哪里开始、到哪里结束。 负责写分区表:mklabel msdos 新建一个空的分区表,mkpart primary ext4 1MiB 100% 添加一个从 1 MiB 到磁盘末尾的分区。-s 的意思是“不要提问”。改动磁盘是管理员的工作,所以以 开头。之后用 看,sda 下面会出现 sda1。
新还只是一片空地,还放不了文件。格式化就是在上面写入一个——它像一本目录,记着每个文件的名字、数据放在哪里、属于谁。 创建 ext4,这是 常用的文件系统;mkfs.vfat 创建 FAT,Windows 和 macOS 也能读(适合 U 盘)。格式化会清除这个分区上原有的一切。
把磁盘连接到文件夹,而不是使用 C:、D:。这叫挂载,连接的文件夹叫挂载点。执行 /dev/sda1 /mnt/data 后,写入 /mnt/data 的文件就保存在 sda1。用 -h 查看容量。新属于 root,可用 sudo 写入,或用 sudo chown you /mnt/data 更改所有者。用完后以 sudo umount /mnt/data 卸载。
( )是一个没有画面的网页。你在浏览器里输入一个地址时,浏览器会向发送请求( 请求),服务器则送回响应:(200 表示 OK,404 表示找不到,301 和 302 表示已经搬到了另一个地址,500 表示服务器出错)、响应头(内容的类型、大小等信息),以及正文(、 或文件)。浏览器会把正文画成网页;curl 则把收到的内容原样显示在里,或者保存成文件。所以 curl 常用来检查服务器是否还活着、下载文件和调用 。 最常用的:
安装里常见的 -fsSL,就是这四个选项合在一起。curl 收到的内容可以用交给别的命令:| 只看开头几行,| 只取 JSON 里的一个。这个 经过 SONUB 的网络中继与真正的互联网通信;因为它是模拟器,下载可能要花几秒钟。
clone 从 这样的复制一个项目——所有文件,连同完整的历史记录。 获取一个文件;git clone 获取整个文件夹。--depth 1 只取最新版本,快得多。用 进入新文件夹,再用 四处看看。这个 是模拟器,所以下载需要几秒到几十秒。
包管理器就是用打字来操作的应用商店。它会找到,把程序和它需要的所有东西一起下载下来并安装好。每个系统都有自己的包管理器:
apk add figlet 安装程序,apk search 查找程序,sudo apk del 删除程序。
从 PyPI 安装 包。先创建 venv,与系统 Python 分开使用。运行 -m venv ~/venv,再用 source ~/venv/bin/activate 启用。出现 (venv) 后,用 pip install 安装,结束时输入 deactivate。此处创建约需一分钟。练习环境外没有 pip,会提示 not found;受保护的系统环境可能提示 externally-managed-environment。
按项目安装包。在一个空文件夹里,npm init -y 会创建 ,也就是项目所需内容的清单。npm install ms 把包下载到 _modules,并写进 package.json,这样以后任何人只要执行 npm install,就能重建一样的环境。每个项目都有自己的 node_modules,所以两个项目永远不会为版本打架。
很少单独使用。在两条命令之间放一个符号,左边打印出来的东西就直接进了右边。
lsblk|grep sda
列出所有磁盘和, 只留下关于练习磁盘 sda 的那几行。
sudo mkdir -p /mnt/data&&sudo mount /dev/sda1 /mnt/data
文件夹真的建好了才挂载磁盘 —— 就是「那件事成了才做」。
curl -s https://sonub.com/robots.txt|head -3
从互联网获取一个文件, -3 只显示它的前三行。
向用户询问一个值并存入。变量不必事先声明。
echo "你叫什么名字?"# 在屏幕上输出问题read name# 把回答读入变量 nameecho "你好,$name!"# 使用读到的值read 会等用户按下 Enter,然后保存输入的内容。加上 -p 可以在同一行显示问题(read -p "Name? " name),-s 会像输入密码时那样隐藏输入的字符,-r 则让反斜杠保持原样。如果不给变量名,答案会存进 $REPLY。
除了 , $PWD 也能告诉你。用括号 ( ) 包起来的命令在单独的 中运行,所以在里面 不会改变外面的位置。
echo "我现在在 $PWD"# 通过使用当前(cd /tmp; pwd)# 只在子 Shell 里移动 → 输出 /tmppwd# 外面仍在原来的位置子 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.bakShell 会在运行之前展开,所以命令本身根本看不到花括号—— 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 -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 -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 会显示每个匹配之后或之前的两行,让你看到日志中错误前后发生了什么。
逐行读取文件,替换文字或选择要输出的行。默认不会改动原文件。加上 -i 会直接修改文件,因此可以像下方示例一样用 -i.bak 保留备份。
sed 's/okay/great/g' file.txt# 把所有 okay 换成 greatsed '/^#/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课会介绍更多这类差异。
按空格(或 -F 指定的字符)把每行切开,把各段 $1、$2 … 来处理。它擅长求和、筛选和统计。
echo 'a b c' | awk '{print $2}'# 第二列 → bawk -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 -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 . -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 倒序)。
先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。
<< EOF()把多行文字整体交给作为输入。<<<()则是送入一行短文字的轻便方法。
cat << EOF# 到 EOF 为止的几行成为输入多行
文字
EOF# 两个 EOF 之间的内容就是正文grep "香蕉" <<< "苹果 香蕉 葡萄"# 用 <<< 输入一行<< 后面的词只是一个标记;EOF 是习惯,不是规定。写成 <<'EOF'(标记加)时,文本会按字面处理——$HOME 仍然是 $HOME——这正是把写进文件时需要的。 > config.txt << EOF … EOF 是快速创建几行小文件的方法。
(stdout)和标准错误(stderr)走的是不同的通道。2> 只接收错误,2>&1 把错误送到和输出相同的地方,/dev/null 是专门丢弃东西的地方。
command 2> error.log# 只把写入文件command > out.log 2>&1# 输出和错误一起写入 out.logcommand > /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 替换成 Aecho "${var//Some/A}"# // 会把所有 Some 都替换成 Aecho "${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 ))"# 乘方 → 1024echo "scale=2; 10/3" | bc# 小数运算 → 3.33$? 每执行一条命令都会改变,所以如果以后还要用,就立刻保存它(status=$?)。其他有用的:$$ 是 的号,$0 是的名字,$SECONDS 是 Shell 已经运行了多久,$LINENO 是当前行号。$RANDOM 用在游戏里没问题,但不能用于密码——凡是需要保密的东西,都请使用像 openssl rand 这样正规的生成工具。
在不同系统上的表现略有差异。 在哪里都一样,还能精确控制宽度、对齐和小数位数。
printf "你好,%s!\n" "世界"# 把值填到 %s 的位置 → 你好,世界!printf "%.2f\n" 3.14159# 保留两位小数 → 3.14printf "%05d\n" 42# 不足五位用 0 补齐 → 00042printf 自己不会换行,所以格式要以 \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 比一串 if 更整洁(相当于其他语言中的 switch)。
case "$x" in# 根据 x 的值 0) echo "零";;# 值为 0 时 1) echo "一";;# 值为 1 时 *) echo "其他";; esac# * 匹配其余所有情况每个模式都可以用 | 列出几种写法,比如 yes|y|Y),也可以使用和文件名一样的:*.jpg|*.png) 匹配图片文件名。第一个匹配的模式胜出,其余的都会跳过,所以把兜底的 *) 放在最后。;; 结束每一种情况;忘记写它是 case 里最常见的错误。
for 遍历列表或范围,while 在条件为真时一直,until 在条件为假时一直循环。另外还有 C 风格的 for。
for ((i=1; i<=3; i++)); do echo "$i"; done# C 风格的 forwhile (( 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 会显示变量是如何定义的,时很有帮助。
让接受 -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 ./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 设定收到(退出、错误、中断)时要运行的。用来清理临时文件尤其方便。
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 ,脚本就更不怕出错。-e(出错即停)、-u(禁止使用未设置的)和 -o pipefail(察觉中的失败)一起使用。-x 用于。
set -euo pipefail# 稳健脚本的标准第一行set -x# 逐条输出正在运行的(调试)set +x# 关闭调试set -e 有例外:在 if 条件里、在 或 || 列表里,或者在你检查结果的命令里失败时,它不会停止,因为你已经在处理这个失败了。可能会无害地失败的命令,可以用 || true 放行。只在你调试的那一部分周围打开 set -x,追踪输出就会很短。
在后面加上 &,它就作为运行。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 -euo pipefail 是三个的组合。知道每一个能防止什么事故,就明白它为什么成了标准写法。
set -e# 只要有失败就立即停止set -u# 使用未定义的会报错(能发现拼写错误)set -o pipefail# 中间一步失败,整个管道就算失败只用 -e 会漏掉管道中间的失败:false | true 被算作成功,因为管道通常只报告最后一条命令的结果。pipefail 让任何一部分失败时整个管道都失败。-u 能抓住 $flie 这样的拼写错误,否则它会悄悄变成空值——这在 -rf "$dir/$flie" 中很危险,因为那样会删掉 $dir 里的所有内容。
调整文件名的匹配方式()。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= 和 read -r 一起用。
IFS=',' read -ra f <<< "a,b,c"# 按逗号切分,存入 fwhile 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 永远不会删除文件夹。
先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。
不同的项目可能需要不同版本的 Node。 可以安装多个版本并在它们之间切换。
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash# 安装 nvm(版本号请查看官方文档)nvm install 22# 安装 Node 22nvm use 22# 在当前 中使用 Node 22node -v# 确认切换后的版本nvm 把 Node 安装在你的里,所以安装全局包永远不需要 。写有版本号(22)的 .nvmrc 文件,能让任何人在那个项目里输入 nvm use 就用上正确的版本。nvm alias default 22 设置新启动时使用的版本,nvm 显示已经安装了哪些版本。在 Windows 上,独立的 nvm-windows 项目做同样的事。
中的 会把 _modules/.bin 加进 ,所以不用也能使用项目里的工具。
npm init -y# 快速生成 package.jsonnpm 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',在脚本里也能正常工作。
无须就能运行包中的工具。项目中已有时使用现有工具,需要时再下载。要指定最新版,可像示例一样加上 @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。
先尝试,再查看答案。检查示例文件是否存在,需要时先创建练习文件。在自己的电脑上操作,也请使用练习文件夹。
用官方安装或 包来安装。装好后立刻用版本检查。
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),所以项目的规则只需告诉它一次。除非你允许,它们在运行命令或修改文件之前都会先询问——读一读它们打算运行什么;本课程里的命令正是你到时会看到的。这些工具变化很快,所以要查看版本并经常更新。
用于、登录和的 ,常和快速的包管理器 pnpm 一起安装。
npm install -g firebase-tools# Firebase CLIfirebase login# 通过登录firebase projects:list# 项目列表npm install -g pnpm# pnpmfirebase init 会提出一些问题并写出 firebase.json。firebase deploy 上传项目,firebase emulators:start 在你自己的电脑上运行这些服务以便测试。自动构建无法通过浏览器登录,所以改用存放在里的服务账号密钥。pnpm 让多个项目共享每个包的同一份副本,从而节省磁盘空间。
是 查找的文件夹列表。安装后仍找不到时,检查安装目录是否在 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让 修改时,得到的答案几乎总是这种形式:用 传入一小段 ,读取文件、替换、再写回去。关键是先用 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 结束——必须顶格写在行首在中生成多行的配置或文档时, 也是最整洁的办法。标记不加时,$ 会当场被替换成值。
cat <<EOF > .env# 标记不加引号 → 变量会被替换PORT=$PORT# $PORT 以实际的值保存EOF# 到这里结束cat <<'EOF' > template.txt# 加了引号 → $PORT 保持原样bash <<< "echo one-liner"# 用 只传一行在前面加上地址(行号或),就只改那些行。用 -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 '{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 就成了计算器用 处理,“对找到的每个结果运行”用 。两个都会用,能用连起来的范围就大大扩展了。
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)比 更快,能直接理解 \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-12perl -pi -e 's/foo/bar/g' *.txt# Perl:就地修改多个文件git log --grep='fix.*login' -i --oneline# 用正则搜索信息git log -G 'apiKey' --oneline# 用正则搜索代码改动的 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 忽略使用。/模式/ 检查整行,$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 的第一行就是这个的模式,而写法都是同一套。方括号 [ ] 标出可以不写的部分,... 表示这个位置可以重复,竖线 | 表示二者选一,大写或斜体的词就是你要填的位置。读懂这一行,没用过的命令也能叫得动。
ls --help | head -5# 模式就在最前面几行man ls# 完整手册——按 q 退出man -k copy# 按用途去找命令type cd# 这个名字是、内建命令还是别名每个结束时都会留下——0 表示成功,其他值表示失败。$? 里装着刚结束那个命令的码,而 看的正是这个值。很多命令都有「安静」,让你不看输出只用退出码。
ls notes.txt; echo $?# 成功时打印 0ls 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 显示类型和 UUID——一个永远不变的序列号,不像 sda1 这个名字,加装磁盘时可能会变; print 用 parted 自己的说法显示分区表。它们都只读不改。
sudo fdisk -l /dev/sda# 分区表,一个扇区一个扇区地看sudo blkid# 每个分区的文件系统类型和 UUIDsudo parted -s /dev/sda print# 同一张分区表,parted 眼中的样子cat /proc/partitions# 内核自己知道的信息练习不需要备用磁盘。 设备能让一个普通文件像磁盘一样工作:创建一个你想要大小的文件,格式化它,再用 -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# 不再需要 sudoecho "/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 项目用到的包到目前为止你只是在获取内容(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)重新编写。部分命令仅供参考。