macOS 命令行从零指南
一份写给技术新手的命令行手册:从终端到底是什么讲起,最终覆盖 Git 和 Apple 开发工具链。
本文的记忆方法:几乎每个命令的名字都不是乱起的——它们是有源头的缩写、有出处的比喻(第 0 章会讲这背后的历史成因)。命令名大致分三类——
- 缩写型:
pwd= print working directory(打印工作目录)。展开缩写,就永远不会忘。 - 合成型:
mkdir= make directory(创建目录)。两个词各取一段拼起来。 - 比喻型:Git 的
stash(藏匿处)、macOS 的caffeinate(咖啡因,喝了不睡觉)。理解比喻,命令就有了画面感。
后文每个命令都会标注它的命名来源。先问"这是什么的缩写",再记用法——这是学命令行最省力的路径。
0. 认识终端:这一切的起点
图形界面这么好用,为什么还要命令行
因为两者擅长的事不同。图形界面擅长"看"和"探索";命令行擅长四件图形界面做不好的事:
- 精确。"找出这个文件夹里所有大于 100MB、三个月没动过的文件"——鼠标点不出这句话,命令行一行完成。
- 组合。命令可以像积木一样拼接(第 3 章的管道),几十个小工具能组合出无穷种能力。
- 自动化。命令能存成脚本反复执行、定时执行;而你昨天在 Finder 里的一通点击无法"再放一遍"。
- 无处不在。服务器没有图形界面,部署网站、远程排障时命令行是唯一入口。
对开发者还有一层:Git、构建工具、包管理器这些核心工具,本体全是命令行程序——图形工具只是给它们套的壳。学会本体,才不被壳限制。
一点历史:你的 Mac 是 Unix 的后代
把时间拨回 1969 年,贝尔实验室。两位工程师 Ken Thompson 和 Dennis Ritchie 在一台闲置的小型机上写出了一个操作系统,取名 Unix。今天你 Mac 上的大部分命令,就诞生在那几年。
那个年代没有显示器。人与计算机对话用的是电传打字机(teletype,缩写 TTY):一台带键盘的打印机,你敲一行字,它通过电缆发给主机;主机的回复直接打印在纸上。这台打字机位于通信线路的末端,所以被叫作 terminal——"终端",线的尽头。
这个词沿用至今。你打开的 Terminal.app 本质是那台打字机的软件仿真:一行行文本、一问一答的交互方式,都是从纸带时代原样继承的。(macOS 里它的血统随处可见:终端设备至今仍叫 tty,ps 命令的输出里就有一列 TTY。)
再看家谱。Unix 后来开枝散叶,其中一支经加州大学伯克利分校改良(BSD),被乔布斯离开苹果后创办的 NeXT 公司选作系统基础;1997 年苹果收购 NeXT,这套系统演化成了 Mac OS X——也就是今天的 macOS。
1969 Unix(贝尔实验室)
├── 1978 BSD(伯克利改良版)
│ └── 1989 NeXTSTEP(乔布斯的 NeXT 公司)
│ └── 2001 Mac OS X ——→ 今天的 macOS
└── 1991 Linux(Linus Torvalds 参照 Unix 从零重写的自由版本)两个对你有实际意义的结论:
- macOS 是"根正苗红"的 UNIX(通过了官方认证),命令行血统五十多年,不是后来补装的功能。
- Linux 是 Unix 的"神似"重写,绝大多数命令与 macOS 相通。本文学的东西,在你将来租的任何一台 Linux 服务器上原样可用——学一次,Mac 和服务器通吃。
为什么命令名都这么"难懂"
知道了历史,就能回答这个新手最大的困惑。电传打字机每秒只能传约 10 个字符,每敲一个键都要等它咔哒一声打在纸上——键入是有真实成本的。于是先驱们把命令压缩到极限:list 压成 ls,change directory 压成 cd,copy 压成 cp。
这不是故意神秘,是那个年代的"省流量"。有个流传很广的问答:多年后有人问 Ken Thompson,如果重新设计 Unix 会改什么,他答:"我会把 creat 拼成 create。"——当年连一个字母 e 都要省下来。
所以,这些短命令都是有源头的压缩包。本文的方法就是帮你把每个压缩包"解压"回原文——理解了展开形式,缩写自然就记住了。
终端和 Shell 是两回事
- 终端(Terminal.app):那个黑色(或白色)的窗口。它只负责显示文字和接收键盘输入,本身不理解任何命令——正如当年的打字机不理解你打的字。
- Shell:窗口背后真正干活的解释器程序。你输入命令,它解析、执行、把结果送回终端显示。macOS 默认的 Shell 是 zsh。
命名Shell 意为"外壳"——操作系统真正的核心叫 kernel(果核、内核),Shell 包裹在核外面,是人与内核对话的中间层。壳与核,一对贯穿始终的比喻。zsh = Z Shell,名字取自作者在普林斯顿求学时一位助教 Zhong Shao 的登录账号 "zsh"。它的前辈 bash = Bourne-Again SHell,一个双关:既是"Bourne Shell 的再生"(致敬初代作者 Stephen Bourne),又谐音 "born again"。
打开方式:Cmd + 空格 呼出 Spotlight,输入 Terminal 回车。你会看到一行提示符(prompt):
jerry@MacBook-Pro ~ %
│ │ │ └── %:zsh 的提示符,意思是"轮到你说话了"(bash 用 $,root 用 #)
│ │ └──── 当前位置(~ 表示你的主目录,下一章展开)
│ └──────────────── 机器名
└────────────────────── 用户名后文示例里,你要输入的只是提示符之后的部分。
一条命令的解剖
ls -l -a ~/Desktop
│ │ │ └── 参数:对什么操作(这里是桌面目录)
│ │ └───── 选项:调整行为
│ └──────── 选项:调整行为
└─────────── 命令:做什么结构规则很简单,几乎所有命令都循此形:
ls -l -a # 短选项:一个横线 + 单字母
ls -la # 短选项可以合并
git log --oneline # 长选项:两个横线 + 完整单词,常见于现代工具(git、brew、swift)一个 macOS 特色要提前知道:系统自带的老牌 Unix 命令(ls、cp、rm 等源自 BSD)大多只认短选项——ls --all 会直接报错。长选项是 GNU 和现代工具的风格。
选项字母也有约定俗成的惯例。注意:是惯例,不是标准——同一个字母在不同命令里可能含义不同,拿不准时以 man 为准。但高频惯例仍值得先记:
| 选项 | 通常的全称 | 含义 |
|---|---|---|
-a | all | 全部(含隐藏项) |
-l | long | 长格式(详细信息) |
-h | human-readable | 人类可读(把 1048576 显示成 1M);单独使用时常是 help |
-R | recursive | 递归(连同子目录一起)。小写 -r 在 cp/rm 里也是递归,但在 ls 里是 reverse 反序——"惯例有例外"的典型 |
-f | force | 强制,不再确认 |
-v | verbose | 啰嗦模式(输出详细过程);有时是 version |
-i | interactive | 交互式(做之前问一下) |
-n | number | 数量 |
遇到不会的命令,三招自救
man ls # manual(手册):官方完整文档,按 q 退出,/ 搜索
ls --help # 多数命令支持,输出简短用法
tldr ls # 第三方工具(见第 10 章):只给最常用的几个例子,新手首选命名man= manual(手册)。tldr= too long; didn't read(太长不看)——名字即态度:man 手册太长,我只要例子。
键盘生存技巧
Tab 自动补全命令和路径(最重要的键,没有之一)
↑ / ↓ 翻阅历史命令
Ctrl + C 强行终止当前命令
Ctrl + A / E 光标跳到行首 / 行尾
Cmd + K 清屏(或输入 clear 命令)新手安全守则
- 命令行删除不进废纸篓。
rm删掉就是删掉了。 - 看到 `sudo` 提高警惕——它意味着以最高权限执行,系统不再拦你。
- 不要粘贴运行看不懂的命令,尤其是含
sudo、rm -rf、curl ... | sh的。 - 拿不准时,先用
man或tldr查一下再敲回车。
1. 移动:你在哪里,有什么,去哪里
先弄清三个基础词:文件、目录、文件系统
- 文件(file):一段有名字的数据。一份代码、一张图片、一个 app,在系统眼里都是文件。
- 目录(directory):装文件(和其他目录)的容器——就是图形界面里的"文件夹",同一个东西的两个名字。
命名directory 本义是"名录、通讯录"。这个名字暴露了它的本质:目录其实是一张清单,记录着"名字 → 数据在磁盘上的位置"的对应关系,像通讯录记录"人名 → 电话"。中文译作"目录",取的也是书籍目录的同一个比喻。"文件夹"(folder)则是后来图形界面发明的办公室比喻——牛皮纸文件夹。
- 文件系统(file system):所有目录层层嵌套,构成一棵倒挂的树。树根是
/,称为根目录(root)。你电脑上的每个文件,都能从根出发一路走到:
/ ← 根:一切从这里长出
├── Applications ← 所有 app 住这里
├── System ← macOS 本体(别动它)
└── Users ← 所有用户
└── jerry ← 你的主目录,简写 ~,你的文件都在这棵子树上
├── Desktop
├── Documents
└── Projects
└── MyApp在 Finder 里双击进入文件夹,和在命令行里 cd 进目录,走的是同一棵树——只是一个用鼠标,一个用键盘。
最后一个概念:命令行没有"当前窗口",但有个等价物——工作目录(working directory),即你此刻"站"在树上的哪个节点。所有相对操作都以它为基准。
生存三命令
新手先记住这三个命令,它们回答三个问题:
pwd我在哪里?
命名print working directory,打印工作目录。
pwd
# 输出:/Users/jerry/Projectsls这里有什么?
命名list,列出。Unix 早期为了少敲键盘,把单词压缩到两个字母。
ls # 列出当前目录
ls -l # long:详细模式(权限、大小、修改时间)
ls -a # all:包括隐藏文件
ls -la # 合并:详细 + 全部
ls -lh # 详细 + 人类可读的文件大小
ls ~/Desktop # 也可以看别的目录,不用先走过去关于隐藏文件:名字以 . 开头的文件默认不显示(如 .gitignore、.zshrc)。这是 Unix 的约定:配置类文件藏起来,不打扰日常视线。它们并不神秘——Finder 里按 Cmd + Shift + . 同样能看到。
ls -l 的输出信息密度很高,学会读它,一行顶 Finder 一个弹窗:
-rw-r--r-- 1 jerry staff 1024 7 14 10:30 README.md
│ │ │ │ │ │ └── 文件名
│ │ │ │ │ └── 最后修改时间
│ │ │ │ └── 大小(字节;加 -h 会显示成 1K)
│ │ │ └── 所属群组
│ │ └── 所有者
│ └── 链接数(新手可忽略)
└── 类型与权限:首字符 - 是文件、d 是目录(directory);
后面 rwx = read/write/execute 读/写/执行(第 4 章 chmod 再会)cd去别的地方
命名change directory,切换目录。
cd Projects # 进入当前目录下的 Projects
cd /Users/jerry # 用绝对路径直达
cd .. # 回上一级
cd ~ # 回家(用户主目录)
cd # 不带参数也是回家
cd - # 回到上一次所在的目录(来回横跳很好用)路径的记号系统
| 记号 | 含义 | 记忆 |
|---|---|---|
/ | 根目录(整个文件系统的起点) | root |
~ | 你的主目录 /Users/你的用户名 | home,波浪号像屋顶 |
. | 当前目录 | 一个点 = 这里 |
.. | 上一级目录 | 两个点 = 再往上 |
- | 上一次所在目录 | 减号 = 退回 |
以 / 开头的是绝对路径(从根出发,任何位置都有效);不以 / 开头的是相对路径(从当前位置出发)。
练习:cd ~ 回家 → ls -la 看看家里有什么 → cd Desktop → pwd 确认位置 → cd - 跳回去。
2. 文件操作:创建、复制、移动、删除
mkdir创建目录
命名make directory,创建目录。
mkdir Notes # 创建一个目录
mkdir -p App/Sources/Views # -p = parents:父目录不存在就一并创建touch创建空文件
命名本义"触摸"。它的原始职责是更新文件的修改时间戳(摸一下,文件就"新"了);文件不存在时会顺手创建一个空的——后者反而成了最常见用法。
touch README.mdcp复制
命名copy。
cp a.txt b.txt # 复制文件并命名
cp a.txt ~/Desktop/ # 复制到别处
cp -R Sources/ backup/ # -R = recursive:复制目录必须递归mv移动(兼职:重命名)
命名move。在文件系统看来,"重命名"就是"移动到同目录的新名字",所以一个命令干两件事。
mv draft.md final.md # 重命名
mv final.md ~/Documents/ # 移动rm删除(危险区)
命名remove。
rm a.txt # 删除文件
rm -r build/ # recursive:删除目录
rm -rf build/ # + force:不问任何问题直接删。敲回车前务必看清路径
rmdir empty_dir # remove directory:只能删空目录(所以用得少)再强调一次:`rm` 不经过废纸篓。 rm -rf 后面跟错路径是命令行世界最经典的事故。
ln -s软链接(快捷方式)
命名link;-s= symbolic(符号的),即"软"链接,类似 Finder 的替身。
ln -s /path/to/real/folder ~/shortcuttar / zip打包与压缩
命名tar= tape archive,磁带归档——名字暴露年龄:它诞生时的工作是把一堆文件打包后写入磁带做备份。磁带早已退役,命令活到了今天。zip即"拉链",拉上就把东西合在一起。
tar -czf backup.tar.gz Sources/ # 打包并压缩(c=create 创建 z=gzip 压缩 f=file 指定文件名)
tar -xzf backup.tar.gz # 解包(x = extract 抽出)
tar -tzf backup.tar.gz # 只列出内容不解压(t = list table)
zip -r archive.zip folder/ # zip 格式,跨平台分享更通用
unzip archive.zip -d output/ # -d = destination 解压到指定目录tar 的选项组合像咒语,其实只需记两句:czf 压,xzf 解——create 和 extract 的区别而已。
open从终端回到图形世界(macOS 特有)
命名直白的"打开"。它是终端和 Finder/App 之间的传送门,Linux 上没有这个待遇。
open . # 用 Finder 打开当前目录(每天都会用)
open MyApp.xcodeproj # 用默认 app(Xcode)打开
open -a Safari https://apple.com # -a = application:指定用哪个 app
open -R file.swift # -R = reveal:在 Finder 中定位这个文件3. 查看与搜索:读文件、找内容、连接命令
cat一口气打印整个文件
命名concatenate(拼接)。它的本职是把多个文件首尾相连输出,cat 单个文件只是这个能力的副产品,却成了日常用法。
cat notes.txt
cat a.txt b.txt > merged.txt # 本职工作:拼接两个文件less分页阅读大文件
命名一个程序员冷笑话。早期的分页工具叫more("再来一页"),后来的改进版反其道命名为less,取谚语 "less is more"(少即是多)。
less app.log
# 操作:空格翻页,/关键词 搜索,n 下一个匹配,q 退出head / tail只看开头 / 结尾
命名直白的"头"和"尾"。
head -n 20 file.log # 前 20 行
tail -n 50 file.log # 后 50 行
tail -f app.log # -f = follow:跟住文件,实时打印新增内容(盯日志神器)wc统计数量
命名word count,数单词。但最常用的其实是数行数。
wc -l file.swift # -l = lines:统计行数grep在内容里搜索
命名本文最值得讲的词源。上古编辑器 ed 中,"对全文(global)应用正则表达式(regular expression)并打印(print)匹配行"的指令写作g/re/p。用的人太多,干脆独立成命令。所以 grep 天生就是"全局正则搜索打印"。
顺带认识正则表达式:一门用符号描述"什么样的文本算匹配"的迷你语言——^ 表示行首、$ 行尾、. 任意一个字符、* 重复任意次。新手完全可以先把 grep 当普通文本搜索用,等哪天需要"匹配所有以 test 开头的行"时再回头学正则,它值得。
grep "TODO" main.swift # 在文件里找
grep -rn "TODO" Sources/ # -r 递归整个目录,-n 显示行号(最常用组合)
grep -i "error" app.log # -i = ignore case:忽略大小写
grep -v "debug" app.log # -v = invert:反选,排除含 debug 的行
grep -c "func" main.swift # -c = count:匹配的行数(一行里出现多次只算一行)find按名字/属性找文件
grep 搜"内容",find 搜"文件本身"(名字、大小、修改时间)。
find . -name "*.swift" # 从当前目录递归找 Swift 文件
find . -name "*.png" -size +1M # 大于 1MB 的图片
find . -name ".DS_Store" -delete # 找到并删除(清理 Finder 的缓存文件)管道与重定向:Unix 的灵魂
Unix 哲学:每个程序只做好一件事,复杂任务靠组合。这句话出自管道的发明者 Doug McIlroy,他的原版表述值得一读:
写只做一件事、并把它做好的程序;写能协同工作的程序;写处理文本流的程序——因为文本流是通用接口。
前面学的命令确实都很"小":ls 只会列、grep 只会搜、wc 只会数。但三十个小命令一旦可以自由拼接,能表达的操作是无穷的——这就是命令行诞生六十年仍不可替代的原因。组合的语法只有三个符号:
命令A | 命令B # 管道:A 的输出作为 B 的输入
命令 > 文件 # 重定向:输出写入文件(覆盖)
命令 >> 文件 # 追加写入命名|形如一根管子(pipe),数据像水一样从左流到右。
实际体验一下组合的威力:
ls -la | grep "\.swift" # 列目录,只看 Swift 文件(\. 把正则里的"任意字符"转义回句点本身)
grep -r "TODO" Sources/ | wc -l # 全项目有多少行带着 TODO
history | grep "git" # 我之前敲过哪些 git 命令?
xcodebuild ... > build.log 2>&1 # 构建日志存档(2>&1 表示错误输出也一并收进来)几个高频小工具
echo "Hello" # 回声:原样打印
which swift # "哪一个":这个命令的可执行文件在哪个路径
history # 历史命令列表命名echo即"回声",喊什么回什么。which即"是哪一个",回答"我敲 swift 时系统实际运行的是哪个文件"。
which 引出一个值得现在就懂的概念——PATH:
你有没有想过,敲 ls 时 Shell 怎么知道去哪找这个程序?答案是一个叫 PATH 的环境变量(environment variable,Shell 的全局设置项)。它的值是一串用 : 分隔的目录清单,Shell 按顺序在这些目录里找同名的可执行文件,找到即运行。
echo $PATH # 打印你的清单($ 前缀的意思是"取这个变量的值")
# /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:...由此可以解释两个新手常见现象:报错 command not found = "清单上的目录翻遍了,没这个名字"(多半是没安装,或装到了清单之外的位置);而 Homebrew 装完某些工具让你改 ~/.zshrc,往往就是在把它的目录加进 PATH。
4. 系统管理:进程、磁盘与权限
先补一个基础概念。程序(program)是躺在磁盘上的一堆文件;进程(process)是程序被运行起来后、活在内存里的实例——一个在动的副本。打开两个 Safari 窗口跑着的是同一个程序、可能是同一个进程;而每敲一次 ls 都会诞生一个短命的新进程,干完活就消失。每个进程出生时领到一个编号,叫 PID(process identifier)。
你早就见过进程管理器——"活动监视器"就是。本章的 ps 和 top 是它的命令行形态。
ps / top看进程
命名ps= process status,进程状态。top即"最顶端"——按资源占用排序,最吃 CPU 的排最上面。
ps aux # 列出所有进程(a=含其他用户的 u=详细格式 x=连无终端的后台进程也算上;合起来≈全部)
ps aux | grep Simulator # 配合管道找特定进程
top -o cpu # 实时监控,按 CPU 排序,q 退出kill终止进程
命名名字吓人,本质温和——它的原义是给进程"发送信号"(signal),默认信号恰好是"请退出"。-9是第 9 号信号 SIGKILL:不再商量,立即处决。
kill 12345 # 礼貌地请 PID 为 12345 的进程退出
kill -9 12345 # 强制终止(进程无响应时)
killall Simulator # 按名字终止(kill all matching)sudo借用管理员权限
命名superuser do,"以超级用户的身份去做"。超级用户是 Unix 里权限不设限的账号,名叫 root(根)——它拥有根目录/,站在整棵权限之树的根上,一切在其之下。
sudo xcode-select -s /Applications/Xcode.app
# 会要求输入你的登录密码;之后短时间内不再重复询问用前默念安全守则第 2 条:系统不拦 root,敲错自己兜底。
chmod / chown权限与归属
命名change mode(改权限模式)、change owner(改所有者)。ch- 前缀家族。
chmod +x deploy.sh # 给脚本加"可执行"权限(写完脚本跑不动,八成是缺这个)
ls -l # 开头的 rwxr-xr-x 就是权限:r 读 w 写 x 执行df / du磁盘还剩多少 / 谁占的
命名disk free(磁盘剩余)与 disk usage(磁盘用量)。一对镜像命令:df 看"还有多少",du 看"用了多少"。
df -h # 各分区剩余空间(-h 又是 human-readable)
du -sh * # 当前目录每一项占多大(-s = summary 只给总数)
du -sh * | sort -rh | head -5 # 组合技:找出最大的 5 个
du -sh ~/Library/Developer/* # 开发者的磁盘杀手基本都住这里命名家族:-ctl、-util 和 -d
macOS/Unix 的系统命令有三个高频后缀,认识它们,看到新命令就能猜出身份:
| 后缀 | 含义 | 例子 |
|---|---|---|
-ctl | control,某服务的控制器 | launchctl(控制 launchd)、simctl(控制模拟器) |
-util | utility,某类东西的工具箱 | diskutil(磁盘)、plutil(plist)、textutil(文档) |
-d | daemon,常驻后台的守护进程 | launchd、sshd——它们不是给你敲的,是背后默默运行的 |
daemon(守护神)是 Unix 对后台服务的老式浪漫称呼。launchctl 读作 "launch control":控制 macOS 的服务总管 launchd。diskutil list # 磁盘与分区
launchctl list | grep com.apple # 当前加载的后台服务
uname -m # unix name:查架构(arm64 / x86_64)
sw_vers # software version:macOS 版本号5. macOS 特色命令:苹果的命名品味
这一章的命令都是 macOS 独有的,Linux 上没有。苹果的命名比传统 Unix 命令更完整、更具象,反而好记。
pbcopy / pbpaste剪贴板管道
命名pasteboard = 苹果对剪贴板的正式称呼(写 Apple 平台 app 的话,对应的 API 正是 NSPasteboard / UIPasteboard——命令行和框架同源)。
cat ~/.ssh/id_ed25519.pub | pbcopy # 公钥复制进剪贴板,去网页上粘贴
pbpaste > snippet.swift # 剪贴板内容存成文件
pbpaste | wc -l # 剪贴板里有几行?mdfind命令行版 Spotlight
命名metadata find,按元数据查找。Spotlight 的底层是一套元数据索引:find 现场遍历目录,mdfind 查现成的索引,所以通常快得多。代价是依赖索引——被排除在 Spotlight 之外的目录、刚写入还没被索引的文件,它就查不到;那种时候还得请 find 出马。
mdfind -name "Package.swift" # 全盘按名字找(走索引,一般秒出)
mdfind -onlyin ~/Projects "URLSession" # 限定范围;对已索引的常见文件类型,还能搜到文件内容defaults读写偏好设置
命名来自 macOS 的"用户默认设置数据库"(user defaults database)。你在 app 里用的UserDefaultsAPI,读写的就是同一套东西——命令行是它的直接入口。
defaults read com.yourname.MyApp # 看你 app 存了什么(调试 UserDefaults 直观)
defaults delete com.yourname.MyApp # 清空,模拟"首次启动"
defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder
# 让 Finder 显示隐藏文件caffeinate不许睡觉
命名本章最佳命名。caffeinate = "给……灌咖啡因"。给 Mac 灌咖啡,它就不睡了。
caffeinate -i ./long_task.sh # 任务执行期间阻止睡眠,结束自动解除
caffeinate -d -t 3600 # display:保持屏幕常亮 1 小时sips命令行图像处理
命名scriptable image processing system,可脚本化的图像处理系统。
sips -z 512 512 icon.png --out icon-512.png # 缩放(-z 后先高再宽)
sips -s format jpeg in.png --out out.jpg # 转格式
sips -g pixelWidth -g pixelHeight image.png # -g = get:查属性写个循环就能批量生成全套 App Icon 尺寸,不必开任何软件。
其他好玩且有用的
say "构建完成" # 朗读文字。挂在长命令后当提醒:xcodebuild ... && say 完成
screencapture -i shot.png # 截图(-i = interactive 交互选区)
ditto -c -k --keepParent MyApp.app MyApp.zip
# 复制/打包并完整保留 macOS 元数据,公证前打包用它
networkQuality # 苹果自带测速(macOS 12+)
qlmanage -p file.pdf # 命令行触发 Quick Look(ql = Quick Look)命名ditto是拉丁语血统的英文词,意为"同上、原样照抄"——比 cp 更忠实的复制。say就是"说"。
6. Homebrew:Mac 的软件"酿酒厂"
什么是包管理器(package manager):命令行世界的 App Store——统一负责软件的安装、升级、卸载。但它比 App Store 多干一件关键的事:处理依赖(dependency)。命令行软件常常建立在别的软件之上(A 依赖 B,B 又依赖 C),包管理器知道整张依赖网,一条命令连锁装齐。这是个通用概念,你会反复遇到它的各路亲戚:Linux 的 apt、JavaScript 的 npm、Swift 的 SPM——思想全部相通。
macOS 没有自带的命令行包管理器,Homebrew 是社区的事实标准。第 10 章的所有工具都靠它安装。
命名Homebrew = "家酿啤酒"。作者把"在自己 Mac 上编译软件"比作自家酿酒,并沿着这个比喻构建了一整套术语宇宙——理解比喻,所有概念一次记牢:
术语 字面义 实际含义 formula 配方 一个命令行软件的安装脚本 cask 酒桶 图形界面 app("大桶装") bottle 瓶装酒 预编译好的包(不用现场"酿",开瓶即饮) tap 酒桶龙头 接入第三方软件源(接上龙头,酒就流过来) cellar 酒窖 软件的实际安装目录
安装 Homebrew 本身(官网 brew.sh 的一行命令),然后:
brew install ripgrep # 装命令行工具(formula)
brew install --cask rectangle # 装图形 app(cask)
brew search json # 搜索有什么可装
brew info node # 查看包详情
brew list # 已安装清单
brew upgrade # 全部升级
brew uninstall node # 卸载
brew cleanup # 清理旧版本,释放磁盘
brew doctor # "看医生":体检环境问题进阶两招:
# 环境迁移:当前机器所有 brew 软件导出成清单,新 Mac 一键装回
brew bundle dump # 生成 Brewfile
brew bundle # 按 Brewfile 全部安装
# 后台服务管理(本地数据库等)
brew services start postgresql
brew services list7. Git:给代码拍快照
先说它解决什么问题
没有版本控制的世界你多半见过:报告-最终版.docx、报告-最终版2.docx、报告-真的最终版.docx。版本控制(version control)是这个困境的系统化解法:它把项目每次有意义的变化记录成一张快照(Git 里叫 commit),你可以随时查看任何一张、比较两张的差异、退回任何一张,还能和别人并行修改后再合并。写代码尤其需要它——改坏了能回头,多人协作不互相覆盖。
Git 诞生于 2005 年:Linus Torvalds 需要管理全球数千人协作的 Linux 内核源码,现有工具都不满意,于是自己动手,两周写出雏形。今天它是这个领域的绝对标准。
名字的来历
命名git 是英式俚语,大意"讨厌鬼"。作者 Linus Torvalds 的自嘲:"我是个自大的混蛋,我把所有项目都用自己命名——先是 Linux,现在是 git。" 官方手册的自我介绍则是 "the stupid content tracker"(傻瓜内容追踪器)。
先建立心智模型,再背命令
Git 的所有命令都在这四个区域之间搬运东西。看懂这张图,后面每个命令都有了位置感:
工作区 暂存区 本地仓库 远程仓库
(working dir) (staging area) (repository) (origin)
你正在改的文件 → 打包待提交的改动 → 已保存的快照历史 → GitHub 上的副本
git add git commit git push
←──────────────── git restore ←─── git pull / fetch- 暂存区(staging area):一个"待提交清单"。改了 10 个文件,可以只挑 3 个放进清单,作为一次干净的提交。
- commit:一次快照。词义双关——既是"提交",也是"承诺":提交了就写进历史。
- origin:不是什么魔法词,只是"你 clone 来源的那个远程仓库"的默认代号。
7.1 初始配置(一次性)
git config --global user.name "Jerry"
git config --global user.email "[email protected]"
git config --global init.defaultBranch mainconfig --global= 全局配置,写入~/.gitconfig,所有仓库共享。
7.2 日常循环:五个命令过一天
命名clone克隆(完整复制远程仓库);status状态;add添加进暂存区;push/pull推/拉——方向以你的电脑为基准:push 推出去,pull 拉回来。
git clone https://github.com/user/repo.git # 第一次:克隆
git status -sb # 现在什么状态?(-s 简洁 -b 显示分支)
git add -p # 交互式暂存:逐块确认改动(比 add . 更能做出干净提交)
git commit -m "feat: 支持深色模式"
git push # 推到远程
git pull # 拉取别人的更新fetchvspull:fetch(取回)只下载远程的新提交、不动你的代码;pull = fetch + 整合——默认用 merge 整合,也可以配置成 rebase(git config pull.rebase true)。想先看看再决定,用 fetch。
7.3 查看历史:发生过什么
命名log即日志(源自航海日志 logbook);diff= difference,差异;show展示;blame直译"责怪"——"这行代码是谁写的?!"名字促狭,用途正经:逐行标注最后修改者。
git log --oneline --graph -10 # 最近 10 条历史:一行一条 + 分支拓扑图
git log -p file.swift # 某个文件的历次改动细节(-p = patch 补丁)
git diff # 我改了什么(工作区 vs 暂存区)
git diff --staged # 即将提交什么(暂存区 vs 上次提交)
git show abc123 # 查看某次提交的完整改动
git blame -L 10,20 file.swift # 第 10–20 行分别出自谁的哪次提交建议养成的习惯:每次 commit 前先 git diff --staged 过一眼——确认要提交的正是你以为的东西,很多事故止于这一眼。
7.4 分支:平行世界
命名branch 即"树枝"——从主干上分杈,长自己的提交,成熟后 merge(合流)回主干。HEAD是"你现在站的位置":当前检出的那个提交。
git switch -c feature/dark-mode # -c = create:创建并切换(switch 是现代命令,语义清晰)
git switch main # 切回主干
git branch # 列出分支,* 标记当前位置
git merge feature/dark-mode # 把功能分支合并进当前分支
git branch -d feature/dark-mode # 删除已合并的分支(-d = delete)7.5 后悔药:每个"撤销"命令的准确含义
Git 的撤销命令多,因为"后悔"分很多种。按词义区分,不会用错:
命名restore恢复原状;reset重置(把历史指针往回拨);revert反转(用一次新提交抵消旧提交);amend修补(英语里"修正案"的那个词);stash藏匿处(把半成品藏起来);reflog= reference log,引用日志——HEAD 每次移动的黑匣子。
git restore file.swift # 后悔改了:丢弃工作区的修改
git restore --staged file.swift # 后悔 add 了:从暂存区撤下
git commit --amend # 后悔刚才的提交:修补信息或补充漏掉的文件
git reset --soft HEAD~1 # 撤销最近一次提交,改动保留(soft 温柔)
git reset --hard HEAD~1 # 连提交带改动全部抹掉(hard 无情,慎用)
git revert abc123 # 已推送的提交要撤销时用它:不改历史,追加反向提交
git stash # 改到一半要切分支:先藏起来
git stash pop # 回来后取出(pop:像从栈顶弹出)
git reflog # 黑匣子:reset 错了、分支删错了,来这里找回坐标
git reset --hard HEAD@{2} # 按黑匣子记录回到误操作之前记住 reflog 的存在——提交过的东西大多能从这里找回。但别把它当备份:它只存在于你本机,且记录会定期过期清理(默认保留约 90 天、不可达的约 30 天)。真正的保险,仍然是及时推送到远程。
7.6 进阶四件套
命名rebase= re-base,"重新设定基底"——把你的分支挪到新的起点上重演,历史成一条直线;cherry-pick摘樱桃——只摘别的分支上想要的那一颗提交;bisect= bi(二) + sect(切),二分查找;worktree工作树——同一仓库长出多个工作目录。
git rebase main # 把当前分支重新"嫁接"到 main 最新处
git rebase -i HEAD~3 # -i 交互式:合并、改写、丢弃最近 3 个提交(推送前整理历史)
git cherry-pick abc123 # 摘取某个提交到当前分支
git bisect start # 二分定位是哪个提交引入了 bug:
git bisect bad # 标记当前版本有问题
git bisect good v1.2.0 # 标记这个老版本没问题
# 之后 git 自动跳转,每测一次标记一次,log₂n 次锁定凶手
git bisect reset # 结束,回到原分支
git worktree add -b hotfix/crash ../hotfix # -b 新建分支并检出到新目录,同时干两件事不用 stash
# (分支已存在时去掉 -b,直接写分支名)7.7 .gitignore
告诉 Git 哪些文件永远不追踪。Apple + Web 项目常用:
.DS_Store
xcuserdata/
DerivedData/
.build/
Pods/
node_modules/
.env8. 网络命令
先铺三个基础概念,本章命令都建立在它们之上:
- IP 地址:设备在网络中的门牌号,形如
17.253.144.10。 - 域名与 DNS:人记不住数字门牌,于是有了
apple.com这样的域名;DNS(domain name system)就是那本把域名翻译成 IP 的全球电话簿。 - 端口(port):同一个 IP 上不同服务的舱门编号。网页走 80(HTTP)和 443(HTTPS),你本地起的开发服务器常在 3000 或 8080。"端口被占用"=那扇门已有进程守着。
ping对方活着吗
命名拟声词,来自潜艇声呐的"乒"声——发出脉冲,听回声判断目标是否存在、距离多远。网络 ping 原理一模一样:发个小包,等回音,报告往返耗时。
ping -c 4 apple.com # -c = count:只发 4 个包(不加会一直 ping,Ctrl-C 停)
# 回显里能看到解析出的 IP 和每包往返毫秒数。ping 得通说明网络可达;
# ping 不通则别急着定罪——不少服务器和防火墙会直接屏蔽 pingcurl命令行浏览器
命名client for URLs,"URL 的客户端"。任何能用 URL 表达的请求它都能发,是开发者调 API 的第一工具。
curl https://api.github.com/zen # 最简单:GET 并打印
curl -s https://api.example.com/users | jq # -s = silent:安静模式,配合 jq 格式化 JSON
curl -LO https://example.com/file.zip # 下载:-L 跟随重定向,-O 按原名保存
curl -sI https://example.com # -I = head:只看响应头
curl -v https://api.example.com # -v = verbose:显示完整请求过程,调试用
# 发 POST 请求(写 app 前先验证接口行为)
curl -X POST "https://api.example.com/users" \
-H "Content-Type: application/json" \
-d '{"name": "Jerry"}'选项也讲命名:-X指定方法,-H= Header,-d= data。
ssh / scp安全地远程登录与传文件
命名secure shell(加密的远程 Shell)与 secure copy(加密版 cp——注意它的参数顺序和 cp 完全一致:从哪里,到哪里)。
ssh [email protected] # 登录远程服务器
ssh-keygen -t ed25519 -C "[email protected]" # key generator:生成密钥对
ssh-copy-id [email protected] # 把公钥装到服务器上,之后免密登录
scp file.zip [email protected]:/var/www/ # 上传
scp [email protected]:/var/log/app.log . # 下载("到哪里"是当前目录 .)密钥对(key pair)值得一个比喻:ssh-keygen 生成一把锁(公钥,public key,可以随便给人)和一把钥匙(私钥,private key,永远不离开你的 Mac)。ssh-copy-id 是把锁挂到服务器门上;之后持有配对私钥的你推门即入。GitHub 免密推送用的也是同一套机制——把公钥贴到 GitHub 设置里,就是"挂锁"。
lsof谁占用了这个端口
命名list open files,列出打开的文件。为什么查端口的命令叫"列文件"?因为 Unix 哲学:一切皆文件——网络连接在系统眼里也是一种文件。
lsof -i :3000 # 3000 端口被谁占了(Web 开发日常)
kill -9 $(lsof -ti :3000) # 组合技:直接杀掉占用者(-t 只输出 PID,正好喂给 kill)dig查域名解析
命名domain information groper,"域名信息摸索器",也正好是英文"挖掘"——挖 DNS 记录。
dig apple.com +short # 域名解析到哪个 IP(+short 只给答案)
dig @8.8.8.8 apple.com # 指定用 Google 的 DNS 服务器查(对比排查解析问题)改了 DNS 不生效时,清一下缓存:
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder9. Apple 开发工具链
先弄清本章的核心动词——构建(build):把人写的源代码变成机器能运行的 app 的全过程,包括编译(compile,把代码翻译成机器指令)、链接(link,把各模块与系统库拼装成一体)、打包资源(图标、素材等)。在 Xcode 里按 ⌘B 就是构建;本章命令是同一件事的命令行形态。为什么需要命令行形态?因为自动化——CI 服务器(每次提交代码后自动打包测试的机器)上没有人替你按 ⌘B。
这一章命令偏长,但命名都很直白——苹果的现代工具几乎不搞缩写。
xcode-select选择用哪个 Xcode
命名字面义"Xcode 选择器"。装了正式版 + beta 版时,命令行工具听谁的,由它决定。
xcode-select --install # 新 Mac 第一步:安装命令行开发工具(git 等都靠它)
xcode-select -p # -p = print path:当前生效的 Xcode 路径
sudo xcode-select -s /Applications/Xcode-beta.app # -s = switch:切换xcrun工具链的大门
命名Xcode run,"通过 Xcode 运行"。Xcode 工具链里藏着上百个小工具(simctl、notarytool……),它们不在系统 PATH 里,统一经 xcrun 调用。看到xcrun xxx,读作"运行 Xcode 里的 xxx"。
xcrun --find swift # 找出工具的实际路径
xcrun --show-sdk-path # 当前 SDK 在哪xcodebuild命令行构建
命名Xcode + build,无需解释。它就是 Xcode 按下 ⌘B 的命令行形态。
xcodebuild -list # 列出项目的 scheme
xcodebuild -scheme MyApp build # 构建
xcodebuild test -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' # 在模拟器上跑测试
xcodebuild archive -scheme MyApp -archivePath ./build/MyApp.xcarchive # 打包输出很吵,建议配第 10 章的 xcbeautify 美化。
simctl模拟器遥控器
命名simulator control——第 4 章的 -ctl 家族成员。
xcrun simctl list devices | grep Booted # 哪台模拟器开着
xcrun simctl boot "iPhone 16 Pro" # 开机
xcrun simctl openurl booted "myapp://detail?id=42" # 测试深链(booted = 当前开机的那台)
xcrun simctl push booted com.me.MyApp push.apns # 发模拟推送
xcrun simctl io booted screenshot shot.png # 截图
xcrun simctl status_bar booted override --time "9:41" --batteryLevel 100
# 商店截图专用:满电 + 9:41——初代 iPhone
# 发布会亮相定格的时刻,苹果宣传图的传统
xcrun simctl delete unavailable # 删掉失效的旧模拟器,常能省几十 GB
xcrun simctl erase all # 全部恢复出厂codesign / security签名排查
命名code + sign,给代码签名。security管钥匙串——证书都住在里面。
codesign -dv --verbose=4 MyApp.app # 这个 app 用什么证书签的
security find-identity -v -p codesigning # 我机器上有哪些可用的签名身份报"没有证书"类错误时,先用第二条确认证书是否真的在钥匙串里。
notarytool + stapler公证流水线
命名notarize = 公证。用 Developer ID 证书签名、在 App Store 之外分发的 macOS app,需要先把包交给苹果"公证处"做自动安全扫描,否则用户打开时会被 Gatekeeper 拦下。stapler= 订书机——公证通过后把票据(ticket)"钉"在 app 上;不钉票据 Gatekeeper 也能联网查验,钉上的价值是离线也能通过验证。一个流程,两个具象的名字。
xcrun notarytool submit MyApp.zip \
--apple-id [email protected] --team-id TEAMID \
--password "app专用密码" --wait # 提交并等待结果
xcrun stapler staple MyApp.app # 通过后钉上票据plutilplist 工具箱
命名property list utility,-util 家族成员。
plutil -p Info.plist # 友好格式打印
plutil -lint Info.plist # lint:语法体检
plutil -replace CFBundleShortVersionString -string "2.0" Info.plist # 脚本改版本号崩溃排查二人组
命名atos= address to symbol,把崩溃日志里的十六进制地址翻译回函数名。dwarfdump导出 DWARF 调试信息——这个名字是冲着 ELF(精灵,一种 Unix 可执行文件格式)起的奇幻文字梗:有精灵,就得有矮人。如今 DWARF 是跨平台的调试信息标准,macOS 的 Mach-O 可执行格式同样用它。
dwarfdump --uuid MyApp.app.dSYM # 确认 dSYM 和崩溃日志的 UUID 是否匹配
atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-arch arm64 -l 0x100000000 0x1000a4c2c # 地址 → 函数名+行号swiftSwift 工具链与包管理
swift repl # 交互式环境,随手验证语法
swift run # 运行 SPM 可执行项目
swift build && swift test # 构建 + 测试
swift package init # 新建一个 Swift Package
swift package update # 更新依赖日常维护
rm -rf ~/Library/Developer/Xcode/DerivedData # Xcode 行为诡异时的万能药(缓存目录,可放心删)10. 现代效率工具:命名彩蛋大全
以下全部 brew install <名字> 安装。它们是传统命令的现代替代品,更快、默认值更聪明,且名字一个比一个有梗。
ripgrep (rg)更快的 grep
命名rip 有"撕开、飞速掠过"之意——把文件堆撕开来搜。命令名缩成两个字母rg,向 Unix 传统致敬。
rg "TODO" # 递归搜索,自动跳过 .gitignore 里的内容(node_modules 等)
rg -t swift "URLSession" # -t = type:只搜某类文件
rg -l "deprecated" # 只列文件名fd更快的 find
命名find 砍掉多余字母就是 fd——名字本身就是它的产品宣言:更短、更简单。
fd '\.swift$' # 找 Swift 文件(对比 find . -name "*.swift")
fd -e png # -e = extension:按扩展名
fd -H '\.DS_Store' -x rm # -H 含隐藏文件,-x = execute:找到即执行fzf万能模糊搜索
命名fuzzy finder,模糊查找器。"模糊"意为不必精确输入,敲几个字母就能命中。
git branch | fzf | xargs git switch # 模糊选分支并切换再启用它的 Shell 集成——在 ~/.zshrc 里加一行 source <(fzf --zsh)——之后 Ctrl-R 模糊搜命令历史、Ctrl-T 模糊选文件路径。单论提升终端效率,此工具性价比第一。
xargs = 把上一步的输出变成下一个命令的参数(arguments),管道世界的转接头。bat带高亮的 cat
命名双关。既是 cat(猫)→ bat(蝙蝠,"长了翅膀的猫"),又暗示 better cat。
bat main.swift # 语法高亮 + 行号 + Git 修改标记其余快速过一遍
| 工具 | 替代谁 | 命名来源 | 一句话 |
|---|---|---|---|
eza | ls | exa 的社区继任者 | eza -la --git 显示 Git 状态的 ls |
jq | — | JSON query | JSON 的瑞士军刀:`curl -s url \ |
tldr | man | too long; didn't read | 只给例子的手册,新手第一查 |
gh | — | GitHub 官方 CLI | gh pr create 不离终端发 PR |
htop | top | h = 作者 Hisham | 可交互、可视化的 top |
tree | ls -R | 树 | tree -L 2 打印目录树 |
hyperfine | — | 超精细 | 基准测试:hyperfine "cmd1" "cmd2" 对比谁快 |
xcbeautify | — | Xcode + beautify | `xcodebuild ... \ |
附录 A:命名规律速查表
缩写型(展开即记住)
| 命令 | 全称 | 义 |
|---|---|---|
pwd | print working directory | 我在哪 |
ls | list | 列出 |
cd | change directory | 切换目录 |
cp / mv / rm | copy / move / remove | 复制 / 移动 / 删除 |
ln | link | 链接 |
wc | word count | 计数 |
ps | process status | 进程状态 |
df / du | disk free / disk usage | 磁盘剩余 / 用量 |
man | manual | 手册 |
chmod / chown | change mode / owner | 改权限 / 改所有者 |
uname | unix name | 系统信息 |
合成与首字母型
| 命令 | 来源 |
|---|---|
mkdir | make directory |
grep | ed 编辑器指令 g/re/p(global / regex / print) |
sed | stream editor(流式编辑器) |
awk | 三位作者姓氏首字母:Aho、Weinberger、Kernighan |
tar | tape archive(磁带归档——名字暴露年龄) |
sudo | superuser do |
ssh / scp | secure shell / secure copy |
curl | client for URLs |
lsof | list open files |
dig | domain information groper |
fzf | fuzzy finder |
tldr | too long; didn't read |
比喻型(有画面就忘不掉)
| 命令 | 画面 |
|---|---|
brew 全家 | 酿酒厂:formula 配方、cask 酒桶、tap 龙头、cellar 酒窖、bottle 瓶装 |
git stash | 藏匿处:半成品先藏起来 |
git cherry-pick | 摘樱桃:只摘想要的那颗提交 |
git rebase | 重设基底:把树枝嫁接到新位置 |
caffeinate | 灌咖啡因:Mac 不许睡 |
stapler | 订书机:把公证票据钉在 app 上 |
ping | 声呐"乒"一声 |
less | "less is more":more 的升级版 |
bat | 长翅膀的猫(better cat) |
daemon (-d 后缀) | 守护神:后台默默运行的服务 |
通用选项与后缀
选项:-a all │ -l long │ -h human/help │ -R recursive │ -f force
-v verbose │ -i interactive │ -c count │ -n number │ -p print/parents
后缀:-ctl = control(控制器) │ -util = utility(工具箱) │ -d = daemon(后台服务)再次强调这些是惯例而非标准,例外常见:ls 的 -r 是 reverse 反序(递归要用 -R)、grep 的 -c 数的是行数而非次数。拿不准,man 说了算。
附录 B:高频组合技
学完管道之后,这些一行流会自然看懂:
# 杀掉占用 3000 端口的进程
kill -9 $(lsof -ti :3000)
# 找出当前目录最大的 10 个文件/文件夹
du -sh * | sort -rh | head -10
# 统计项目 Swift 代码行数(排除依赖目录)
fd -e swift -E Pods -E .build | xargs wc -l | tail -1
# 构建完成语音提醒
xcodebuild -scheme MyApp build | xcbeautify && say "构建完成"
# 把最近一次提交改动的文件列表复制到剪贴板
git diff --name-only HEAD~1 HEAD | pbcopy # 明确比较两个提交,避免混入未提交的改动
# 全项目有多少行带着 TODO
rg "TODO" | wc -l
# 清理开发磁盘三连(先看大小,再动手)
du -sh ~/Library/Developer/Xcode/DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData
xcrun simctl delete unavailable附录 C:学习路径建议
不要试图一次记住全部。按周推进,每天 10 分钟真实使用胜过一次通读:
- 第 1 周:第 0–2 章。只练 pwd / ls / cd / mkdir / cp / mv / rm / open,强迫自己用终端代替 Finder 做文件操作。
- 第 2 周:第 3 章。重点吃透 grep 和管道
|,开始用history | grep xxx找自己敲过的命令。 - 第 3 周:第 6–7 章。装 Homebrew,把 Git 日常五命令(status / add / commit / push / pull)变成肌肉记忆,理解四区域图。
- 第 4 周:第 4、5、8 章按需查用;装上第 10 章的 fzf / rg / tldr——此后遇到新命令,先
tldr一下。 - 之后:第 9 章在真实的打包、签名、公证场景中边用边学;Git 进阶命令(rebase / bisect / reflog)等需要时回来查。
记忆的关键不是背,而是每次敲命令前默念一遍它的全称:print working directory、change directory、superuser do……两周后,缩写会自己长在手上。