真正让我离不开 diff 的,不是什么花哨的参数,而是它在排查问题时的不可替代性。有次线上 Nginx 配置被人改过,服务直接起不来,我拿备份和现场文件一比对,三秒钟就定位到了多出来的一行缓存配置。Linux 命令里,diff 是我用得最频繁的文件比较工具之一,它简单到让人轻视,但真正吃透它的输出格式、退出码和补丁用法,能帮你省下大量排查时间。
这篇文章我打算抛开手册式的参数罗列,从一个真实使用者的角度,讲清楚 diff 的原理、实战场景、输出格式选择、目录对比、补丁流转,以及我踩过的那些坑。适合刚接触 Linux 的运维新手,也适合想系统梳理 diff 用法的开发者。我尽量把操作背后的“为什么”也讲出来,这样你在遇到新场景时,才能知道该用哪个参数、怎么解读结果。
1. 真正让我离不开 diff 的几个场景
1.1 配置文件的“案发现场”还原
运维和开发最常遇到的情况,就是“配置莫名其妙被改了”。你可以说这是同事误操作,也可以甩锅给自动化脚本,但最终都要回到一个问题:到底改了什么?
这时候 diff 就是最直接的还原工具。比如你有/etc/nginx/nginx.conf的备份,也有当前正在用的版本,一条命令就能看到所有差异行。相比肉眼逐行对,diff 给出的差异是“结构化”的——它告诉你第几行变了、是添加还是删除、上下文是什么。
我习惯在改动任何重要配置之前,先复制一份带时间戳的备份,例如:
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.$(date +%Y%m%d%H%M%S)这样出了问题,随时可以 diff 备份和当前文件。这招在排查配置漂移(configuration drift)时尤其好用。所谓配置漂移,就是服务器上的配置和标准模板逐渐不一致的过程,往往是某人手动改了一下、某个程序启动时写了一下,日积月累就变得不可控了。diff 能帮你快速看清漂移发生在哪里。
1.2 代码审查与合并冲突前的自查
除了运维场景,开发者在提交代码前也经常用到 diff。虽然日常开发大多用 git diff,但 git 底层的文本比较逻辑和系统 diff 师出同门。有时候你在一个没有 git 的环境里,或者只想快速对比两份代码文件、两个目录的差异,系统 diff 依然是救急工具。
还有一类场景是合并冲突。你从同事那里拿到一个文件,想看看和你本地版本差多少,又不想直接加载整个 IDE,命令行里一条 diff 命令就能得到全貌。这种轻量级对比,在 SSH 到服务器临时排查问题时尤其高效。
1.3 diff 的边界:它不是万能的
必须说清楚,diff 不是银弹。它擅长的是文本文件的逐行比较,但它不知道文件内容的“语义”。
举个例子,diff 能告诉你name = Alice改成了name = Bob,但它不会告诉你这个字段在程序里代表什么、影响哪个模块。它也不能真正理解两个 JSON 文件结构是否“等价”——顺序不同、缩进不同,diff 都会当成差异。遇到二进制文件(比如图片、压缩包、可执行文件),diff 默认会输出一串“Binary files differ”,不会给你细节。有这种需求,后面我会讲应该用什么替代方案。
认清边界之后,你才能把 diff 放在正确的位置:它是“找不同”的工具,不是“解释为什么不同”的工具。
2. diff 是怎么工作的:读懂它的“语言”
2.1 逐行比较背后的算法逻辑
diff 不是把两个文件放在一起“扫一眼”就完事的。它底层使用的是一种基于动态规划的差分算法,经典实现是 Myers 差分算法(Myers diff algorithm)。这个算法的核心目标,是在两个文本序列之间找到一条“最短编辑脚本”——也就是用最少的“删除”和“添加”操作,把文件 A 变成文件 B。
你可以把它理解成对比两条由字符组成的“珍珠项链”,算法尝试找到两串项链中共有的最长子序列,同时保留它们各自独有的部分。这段最长的公共子序列越长,说明两个文件越相似,需要变动的部分就越少。虽然 diff 是按“行”处理的,但算法本质和字符串比对是一致的。
这个细节不是重点,你不需要背算法名字。但理解“diff 找的是最少变更”这一点很重要。它决定了 diff 的输出可能不止一种——理论上两段文本之间的差异可以有多套解释,算法只会挑一条“最短路径”展示给你。这解释了为什么有时候你会觉得 diff 的显示方式“奇怪”:它可能在文件中间另起了一行,把原本连续的几处变动合并成了一个大块。这不是 bug,而是算法选择了行数更少的表达路径。
2.2 三种行变更标记:a、d、c
diff 默认的输出格式(常被称为 normal format)里,有三种变更标志:
- a(append/add):在某个位置之后添加行
- d(delete):在某个位置删除行
- c(change):在某个位置替换行
下面是一个最基础的示例。假设old.txt内容如下:
apple banana cherrynew.txt内容如下:
apple blueberry cherry date运行:
diff old.txt new.txt输出:
2c2 < banana --- > blueberry 4a4 > date我来逐步拆解这段输出。
2c2的意思是:old 文件的第 2 行到第 2 行,对应 new 文件的第 2 行到第 2 行,发生了替换。<开头的是旧文件的第 2 行内容(banana),>开头的是新文件的第 2 行内容(blueberry),中间的---是分隔符,表示“旧的到此为止,新的从这里开始”。
4a4的意思是:在 old 文件的第 4 行之后追加内容,追加的是 new 文件的第 4 行(date)。因为 old 文件原来只有 3 行,第 4 行并不存在,所以这里4a4的写法表示“在第 4 行后面追加”,而不是替换。
如果你看到3d2这种标记,意思是 old 文件的第 3 行被删除后,new 文件第 2 行之后接续旧内容。
刚开始看不懂这些数字是正常的。有一个简单的速记方法:等号左边的数字永远对应旧文件的坐标,等号右边的数字永远对应新文件的坐标。字母 a 是追加、d 是删除、c 是修改。
2.3 退出码:被忽略的自动化入口
很多人不知道,diff 是会“说话”的——通过退出码。这是我在写自动化脚本时最常用到的特性。
- 退出码 0:两个文件完全一致
- 退出码 1:两个文件存在差异
- 退出码 2:执行过程中出现错误,比如文件不存在、权限不足
在 shell 脚本里可以这样用:
if diff -q old.conf new.conf > /dev/null; then echo "配置没有变化" else echo "配置有变化,需要处理" fi-q(或--brief)表示只报告文件是否不同,不输出具体内容。配合退出码,可以方便地实现“有变化才执行某个动作”的逻辑。比如监控某个配置文件是否被动过,发现变化就触发备份或告警。
这个思路可以做成一个小小的定时任务:
*/5 * * * * diff -q /etc/app/config.yml /etc/app/config.yml.bak > /dev/null || echo "config changed!" >> /var/log/config_watch.log退出码是 diff 在自动化场景下的隐藏 API。很多人只知道它是“对比文件”的命令,却忽略了它在脚本中作为判断条件的威力。
3. 实战:配置文件对比的完整操作
3.1 一次真实对比的输出解读
光讲理论太虚,我拿一个具体场景走一遍。假设 Nginx 某个站点的配置site.conf改动前是:
server { listen 80; server_name example.com; location / { proxy_pass http://backend; } }改动后变成了:
server { listen 80; server_name example.com; location / { proxy_pass http://backend; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } gzip on; }使用默认格式对比:
diff site.conf.old site.conf.new输出:
5a6 > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 8a10 > gzip on;解释一下:旧文件第 5 行之后,新文件第 6 行开始多了proxy_set_header;旧文件第 8 行之后,新文件第 10 行开始多了gzip on。这两处都是纯新增,没有修改,所以只有 a 操作。
如果同事把proxy_pass后面的地址从http://backend改成了http://backend-new,输出就会变成5c5,同时显示旧行和新行。
读这段输出时,我的习惯是先看有多少个变更块,再逐个看变更块涉及的行号和内容。变更块越少,说明文件的整体结构越稳定,问题往往越容易定位。
3.2 控制对比灵敏度:-i、-b、-w、-B
有时候你对“大小写不同”并不敏感,或者对“缩进变了”不在意,这时候可以调整 diff 的对比灵敏度。常用参数有以下几组:
-i(--ignore-case):忽略大小写差异。适合比较配置文件里值的大写小写变动,比如True和true。-b(--ignore-space-change):忽略空白数量的变化,但会保留空白是否存在。也就是说它允许a b和a b视为相同,但如果a b变成ab,仍会认为有差异。-w(--ignore-all-space):完全忽略所有空白字符差异,包括空格的有无。适合比较被重新排版过的代码文件。-B(--ignore-blank-lines):忽略空行的增删。
我举个实际例子。file1.txt内容是:
version = 1.0 enable_log = truefile2.txt内容是:
version=1.0 enable_log = true肉眼能看出差异在空格,但如果用diff -w对比,输出为空,表示两者被视为相同。这常用于比较“被工具格式化过”的文件和“原版”文件,快速判断是否存在实质性改动。
不过我要提个醒:-w这类参数是一把双刃剑。在某些语言(如 Python)里,缩进本身就是语法的一部分;在 Makefile 里,Tab 和空格的区别能直接决定构建成败。如果你用-w比较这类文件,等于把关键信息给忽略了。所以在使用忽略参数时,一定要确认你忽略的东西真的是不重要的。
下表总结了我的推荐用法:
| 参数 | 作用 | 典型场景 |
|---|---|---|
-i | 忽略大小写 | 比较密钥、域名等大小写不敏感项 |
-b | 忽略空白数量 | 比较手改过的配置文件 |
-w | 忽略所有空白 | 比较重新排版后的代码 |
-B | 忽略空行增删 | 比较额外插空行的文件 |
3.3 调整上下文行数:让差异更容易定位
默认的 normal 格式只输出差异行本身,这让上下文信息严重缺失。你看到第 100 行被改了,但根本不知道这行在哪个 location 块或哪个 server 块里。-C和-U参数就是用来解决这个问题的。
diff -C 3 old.txt new.txt-C输出 context 格式,差异行前后各显示 3 行上下文,便于你了解差异发生的位置。-U输出 unified 格式,和-C类似,但用更紧凑的方式合并了新旧上下文(后面我会单独讲 unified 格式)。
以site.conf.old和site.conf.new为例:
diff -U 3 site.conf.old site.conf.new输出会是这样:
--- site.conf.old +++ site.conf.new @@ -2,7 +2,9 @@ server { listen 80; server_name example.com; location / { proxy_pass http://backend; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + gzip on; }@@ -2,7 +2,9 @@这段后缀我会在第五节详细解释。现在你只需要明白:带上上下文之后,你能直接看出新增行在location块里、gzip on加在哪个层级。默认的上下文行数是 3,但在大型配置文件中,我建议调到 5 甚至更多,确保差异块能覆盖到所在的功能区块。
4. 目录级比较:批量排查的利器
4.1 -r 递归比较:不止单个文件
diff 不只能比较两个文件,还能递归比较两个目录。最常见的需求是:发布包和线上目录到底差在哪里?备份是否完整?
diff -r /opt/app/backup /opt/app/current这个命令会递归比较两个目录下所有同名文件的内容,并输出哪些文件有差异。如果某个文件只存在于其中一个目录,也会明确指出来。
例如输出:
diff -r /opt/app/backup/config.yml /opt/app/current/config.yml 6c6 < timeout: 30 --- > timeout: 60 Only in /opt/app/current: logs/这说明config.yml里第 6 行的timeout值变了,而且current目录下多了一个logs/子目录。如果目录里文件很多,逐条看输出容易漏,建议配合-q使用。
4.2 --brief 快速定位:只想知道“谁不一样”
用-r全量递归会把每个文件的差异内容都打印出来。如果目录里有几百个文件,屏幕会被刷爆,而且大部分信息是无关紧要的细节。这时候-q就派上用场了:
diff -rq /opt/app/backup /opt/app/current输出会精简成类似:
Files /opt/app/backup/config.yml and /opt/app/current/config.yml differ Only in /opt/app/current: logs这一步的目的是“先缩小范围”。等你知道哪些文件不一样了,再逐个对具体文件跑完整 diff,效率会高很多。
我还习惯在对比前先确认两个目录各自的文件数量:
find /opt/app/backup -type f | wc -l find /opt/app/current -type f | wc -l如果数量都对不上,那一定存在文件缺失或新增,直接用-rq就能看个大概。这种“先粗筛、再细看”的思路,在处理大规模目录对比时非常重要。
4.3 排除干扰文件:--exclude 的实际用法
目录里总有些不需要对比的内容。比如日志文件、缓存文件、构建产物(node_modules、dist、target)、临时文件等。递归比较时它们会制造大量噪音。
--exclude参数可以指定要排除的文件或目录模式。比如:
diff -rq --exclude=logs --exclude='*.log' --exclude=node_modules /opt/app/backup /opt/app/current这个命令排除了名为logs的目录、所有.log后缀的文件,以及node_modules目录。
经验之谈:把排除规则写成一个变量,比如:
EXCLUDES="--exclude=logs --exclude='*.log' --exclude=node_modules --exclude=.git" diff -rq $EXCLUDES /opt/app/backup /opt/app/current这样下次执行同样的任务时,直接复用,不需要反复敲。另外要重点说明:排除规则不能解决“所有”问题,比如文件权限变化、属主变化,diff 是看不到的。这时候需要的是rsync -n或stat,那是另一个工具链的范畴了。
5. 输出格式怎么选:normal、context、unified、side-by-side
5.1 四种输出格式速览
diff 支持多种输出风格,最常用的四种是:
- normal(默认):只有差异行本身,最紧凑,但上下文缺失。
- context(
-C):带上下文的旧版格式,用***和---分割新旧文件。 - unified(
-U):带上下文的合并格式,用---和+++标识新旧文件,再以@@标注范围,git diff 默认就是这个风格。 - side-by-side(
-y):并排显示两个文件的内容行,中间用分隔符标识差异。
我拿下面的例子统一演示。旧文件:
line1 line2 line3新文件:
line1 line2 changed line3 line4normal 格式:
2c2 < line2 --- > line2 changed 3a4 > line4context 格式(diff -C 1 old.txt new.txt):
*** old.txt --- new.txt *************** *** 1,3 **** line1 ! line2 line3 --- 1,4 ---- line1 ! line2 changed line3 + line4unified 格式(diff -U 1 old.txt new.txt):
--- old.txt +++ new.txt @@ -1,3 +1,4 @@ line1 -line2 +line2 changed line3 +line4side-by-side 格式(diff -y old.txt new.txt):
line1 line1 line2 | line2 changed line3 line3 > line4并排格式在行较长时不太好读,但它有一个独特优势:能同时展示两个文件的完整行,适合快速看“左右两边各自长什么样”。我一般只在两个文件行数不多、长度也不长时使用-y。
5.2 为什么我习惯默认用 unified 格式
如果只选一种输出格式,我强烈推荐 unified。原因有三点。
第一,可读性好。-开头表示“从旧文件删除”,+开头表示“在新文件新增”,上下文直接内联,不需要像 context 格式那样分成两块再对应。人脑处理这种“一减一加”的标记,比处理两块对照要轻松得多。
第二,和工具链兼容。git diff 输出的就是 unified 风格,很多代码托管平台、评审工具也以此为准。如果你已经习惯看 git diff,那系统 diff 用-U输出的内容几乎是零成本切换。
第三,生成补丁更方便。diff -u生成的补丁可以直接交给patch命令使用,而且带上下文行号,应用时更宽容(后面详细讲)。
我在 shell 配置里加了一行 alias:
alias diff='diff -u --color=auto'这样日常敲 diff 时,默认就是 unified 格式,还会自动着色,差异行一目了然。已有颜色终端的朋友可以试试,体验提升非常明显。
5.3 通过 --color 增强可读性
GNU diff 支持--color参数,可以给差异行标上颜色。--color=auto表示在终端输出时着色,重定向到文件时不着色;--color=always则强制着色;--color=never关闭。
我用的 alias 里写的是--color=auto:
alias diff='diff -u --color=auto'这样在终端里看到的新增行会有区别于删除行的颜色,长文件对比时的视觉压力小很多。不过有一点要注意:如果你把带颜色的输出重定向到文件,或者通过管道传给less,某些情况下颜色转义序列会变成乱码。这时候可以考虑用--color=never,或者直接去掉颜色,等真正需要人眼观察时再打开。
5.4 看懂 unified 格式的 @@ 范围标记
unified 格式里最容易让人困惑的,就是@@那一行的含义。拿前面的例子说:
@@ -1,3 +1,4 @@我来拆开讲。
-1,3:旧文件中,从第 1 行开始,连续 3 行(即旧文件的 1~3 行)。+1,4:新文件中,从第 1 行开始,连续 4 行(即新文件的 1~4 行)。@@后面的内容是函数名或代码块信息,如果 diff 能识别出来会显示,否则为空。
如果范围只有单行,会省略逗号,写成像-1 +1这样。这个标记的核心价值,是给补丁应用工具提供“这段变更是从哪里开始”的信息,同时在人工阅读时,能快速知道一个差异块覆盖的范围。
在大型文件中,@@的数目和每个块的行数,能直接反映修改的“密度”。我通常扫描一遍@@的数量,就能对两个文件的差异规模有一个量级上的判断——是先看摘要、还是直接深挖某个区块,心里就有数了。
6. 从 diff 到 patch:让变更可流转、可回滚
6.1 生成并应用补丁的标准流程
diff 的另一个高频用法,是生成补丁文件,然后分发给其他机器或同事应用。这在批量运维场景中特别实用。
生成补丁的标准流程是:
diff -u old_file new_file > change.patch如果要递归对比目录并生成补丁,一般会加上-N参数:
diff -uN old_dir/ new_dir/ > change.patch-N(--new-file)的作用是把“只存在于某一侧”的文件视为与空文件对比,这样新增文件和删除文件也能被记录进补丁里。否则,如果新目录里有个文件是旧目录没有的,默认 diff 只会提示“Only in new_dir”,不会生成对应的增补内容,后面应用补丁时就漏掉了这个文件。
应用补丁时,切换到目标目录,然后执行:
patch -p1 < change.patch-p1的含义是忽略补丁文件路径中的第一级目录前缀。比如补丁里记录的路径是a/config.yml b/config.yml,应用时-p1会让 patch 忽略a/,直接在当前目录下找config.yml。
更稳妥的做法是先做一次 dry-run:
patch --dry-run < change.patchdry-run 模式会模拟补丁应用,但不实际修改任何文件。它会反馈哪些补丁能干净应用、哪些会失败。确认没有报错,再正式执行。这步操作几乎零成本,但能帮你避免把线上文件打坏。
6.2 补丁应用失败的排查思路
补丁应用失败不是罕见事。最常见的两个原因是行号偏移和路径前缀不符。
行号偏移,是因为补丁文件中记录的行号是“生成时的行号”,如果目标文件在你生成补丁之后又被别人改过,行号对不上,patch 会尝试根据上下文去寻找匹配位置。unified 格式因为有上下文行,所以兼容性好一些;行号对不上时,patch 还会提示“offset N lines”,意思是它自动做了偏移补偿。这时只要确认偏移量在合理范围(比如几行),通常可以放心应用。
如果遇到“Hunk #1 FAILED”这种提示,问题通常比较严重。我的排查步骤固定如下:
- 查看失败位置的上下文差异,确认目标文件是否被动过。
- 如果只是个别文件对不上,可以单独手动修改该文件,再重新生成补丁。
- 如果补丁整体失败,检查路径前缀是否正确,
-p0和-p1很可能搞混了。
补丁和 diff 是一对搭档,但这个场景里有个容易忽略的坑:补丁的编码。如果旧文件、新文件、补丁文件三者之间的编码或行尾符不一致,应用时可能会报错或产生乱码。确保三者的编码一致,能省去很多麻烦。
6.3 回滚:补丁的另一面用法
补丁不仅能应用在“从旧到新”的方向,也可以反过来实现回滚。
比如你已经用change.patch把配置从旧版本升级到了新版本,紧接着发现新版本有问题,想回退到旧版。可以用-R参数反向应用:
patch -R < change.patch这个操作会把补丁里的所有“+”变成“-”,所有“-”变成“+”,也就是做逆向变更。在我管理的几十台服务器上,这种“推补丁——验证——不行就反向回滚”的流程,比逐台手动改文件要快一个数量级。
不过必须强调:反向应用补丁的前提是当前文件内容和补丁生成时的“新文件”完全一致。如果中间又有人动过,反打补丁大概率会失败。所以在回滚前,我习惯先跑一次patch -R --dry-run验证可行性。
7. 踩坑记录:这些情况我劝你换工具
7.1 二进制文件:diff 的空白输出与乱码
diff 默认按文本模式逐行比较。遇到二进制文件时,它只是输出一句:
Binary files a/app.bin and b/app.bin differ仅此而已,不会告诉你哪里不同,更不会展示十六进制级别的差异。如果你试图用--text(-a)强制按文本比较,屏幕上会出现一串乱码,毫无参考价值。
二进制比较的正确工具是cmp或sha256sum。cmp可以输出第一个不同的字节偏移量:
cmp -l old.bin new.binsha256sum则能快速判断两个文件是否完全一致:
sha256sum old.bin new.bin如果哈希值相同,文件基本一致,不需要再纠结字节差异。
这个坑的核心教训是:明确 diff 的工具边界。它服务于文本比较,不是二进制差异分析器。遇到二进制文件,换工具比硬调参数理智得多。
7.2 行尾符与编码问题:diff 为“看不见”的差异报错
在 Linux 上编辑的文件,行尾符通常是 LF(\n)。但从 Windows 传过来的文件,行尾符可能是 CRLF(\r\n)。diff 会把\r当成一个普通字符,所以即使两行内容看起来一模一样,也会被判为不同。
这种差异用肉眼很难发现。排查时我有两个固定动作:
- 用
cat -A file查看不可见字符。CRLF 的行尾会显示为^M$,LF 的行尾只显示$。 - 用
file file查看文件的换行符和编码信息。
如果确认是 CRLF 和 LF 的差异,可以用dos2unix统一行尾:
dos2unix file.txt或者用sed快速去除\r:
sed -i 's/\r$//' file.txt还有一个编码层面的坑是 BOM。UTF-8 BOM 文件开头会有一个不可见字符,diff 会把整个文件的第一行判为不同,因为第一行在 BOM 文件里是从 BOM 字节开始的。遇到这种问题,用sed -i '1s/^\xEF\xBB\xBF//' file.txt去掉 BOM 即可。
7.3 diff 不输出内容但退出码却是 1
这种情况非常迷惑:两条命令看着都返回了,屏幕上一片空白,退出码却是 1。最常见的元凶就是“不可见字符差异”——比如文件的最后一行没有换行符、某行尾部多了个空格,等等。
我用一个具体例子解释。文件 A 内容为:
hello文件 B 内容为:
hello肉眼完全一样,但如果 A 的hello后面没有换行符,而 B 的hello后面有换行符,diff 会认为 A 的输出“hello”和 B 的输出“hello\n”不同,于是退出码为 1,但输出却不显示任何可见的差异行。
用xxd或od查看文件尾部字节,能立刻看出问题:
xxd file_a.txt | tail -n 2 xxd file_b.txt | tail -n 2如果 A 的最后一行没有0a(换行符),B 有,差异就找到了。给文件末尾补一个换行符,问题就解决了。
这个坑在脚本里尤其危险。如果你用 diff 的退出码作为“文件是否一致”的判断条件,却没注意那些看不见的差异,就会得到完全错误的结论。所以在写自动化脚本前,最好先确认要比较的文件在行尾符、编码、末尾换行这几点上都是可控的。
7.4 与 git diff 的关系:什么时候不用系统 diff
git diff 虽然也叫 diff,但它和系统 diff 并不完全相同。底层算法同源(都是 Myers 风格的差分算法),输出格式也是 unified 风格。但 git diff 额外知道“索引”状态,它能比较工作区、暂存区、HEAD 之间的差异,而系统 diff 只能比较两个显式给出的文件。
日常开发中,如果你想看某个文件相对上次提交改了什么,用git diff file显然更方便。那系统 diff 什么时候仍然不可替代?我认为是这两种情况:
- 在没有 git 仓库、甚至没有 git 命令的环境里(比如刚登录一台纯净的服务器);
- 需要比较两个任意文件、任意目录,且和 git 历史无关的场景。
所以我的建议不是“二选一”,而是“各自干各自擅长的活”。系统 diff 是通用工具,git diff 是结合版本控制上下文的专用工具。理解了这一点,你在不同场景下就能更自然地做出选择。
回到开头那句话:diff 之所以让我“离不开”,不是因为它有多复杂,而是因为它足够基础、足够通用。在排查问题时,它是切入现场的第一把刀;在日常维护中,它是生成补丁、验证变更的可靠工具。掌握它,不需要背太多参数,关键是理解输出格式、退出码和它的使用边界。先把这些基础打牢,后续无论面对配置文件漂移、目录对比还是补丁流转,你都能游刃有余。
最后分享一个小技巧:把 diff 和less配合使用,输出长文件时用-S参数避免折行,你可以在终端里横向滚动查看差异。这个组合是我在查看几百行配置文件差异时的默认姿势,效率比纯看滚屏高很多。