cmp 命令详解与参数实测(Ubuntu 22.04)
字节级文件比较工具 cmp:首个差异字节定位 char 13、-l 全量差异八进制、-b 八进制与字符表示、-s 静默脚本分支、-n 前 N 字节、二进制对比与 EOF 提示及与 diff 的字节级行级分工
cmp 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 GNU cmp 的字节级比较能力——默认模式定位首个差异字节(
char 13, line 2)、相同文件静默退出码 0、-l列出全部差异字节的八进制值、-b打印差异字节的八进制与字符表示、-s静默模式配合退出码做脚本分支、-n限定比较前 N 字节、二进制文件对比与 EOF 提示解读,并对照 diff 说明两者「字节级 vs 行级」的定位差异,全部命令与回显已在 Ubuntu 22.04(GNU diffutils 3.8)实测通过。
一、环境信息
本节列出本文所有实测所依赖的环境与测试数据,便于读者按同规格复现。
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- cmp 版本:GNU diffutils 3.8(
cmp --version实测) - cmp 属于 diffutils 软件包,Ubuntu 22.04 默认预装,无需额外安装;cmp 与 diff、diff3 同属 diffutils
- 演示目录:
/root/demo/cmp/ - 测试数据:
a.txt(32 字节)与b.txt(32 字节)组成「第 2 行大小写差异」的文本对;same.txt与 a.txt 内容完全相同;bin1.bin、bin2.bin(各 6 字节)为仅第 5 字节不同的二进制对;short.txt(12 字节)用于 EOF 提示实测
二、命令概览
2.1 语法与测试数据准备
cmp(compare)的基本语法是 cmp [OPTION]... FILE1 [FILE2 [SKIP1 [SKIP2]]]:逐字节比较两个文件,在发现第一处不同字节时报告其位置(默认随即停止);FILE 为 - 或缺失时读标准输入。cmp 回答的是「这两个文件从哪个字节开始不一样」,而不是「内容差在哪」,因此它天然适合二进制文件与完整性校验。
💡 技术拓展:文件比较工具有字节级与行级两大流派,横向对比——
工具 比较粒度 输出内容 适用场景 cmp 字节 首个差异字节的位置(可选全部) 二进制文件、完整性校验、脚本判断 diff 行 行级差异与编辑指令 文本、代码、配置比对与打补丁 comm 行(集合) 三列:独有/独有/公共 两个已排序文件的集合运算 md5sum/sha256sum 整体摘要 固定长度哈希值 不关心差异位置、只关心是否一致 cmp 与 md5sum 都能回答「是否一致」,区别在于 cmp 给出首个差异位置、无需整体读完即可短路返回;本文实测以 cmp 的字节级定位为主线。
先准备演示数据。printf 的转义序列直接产出二进制字节,两个 bin 文件只有第 5 字节不同(\002 与 \003):
rm -rf /root/demo/cmp && mkdir -p /root/demo/cmp
printf 'hello world\nline two\nline three\n' > /root/demo/cmp/a.txt
printf 'hello world\nLINE two\nline three\n' > /root/demo/cmp/b.txt
printf 'hello world\nline two\nline three\n' > /root/demo/cmp/same.txt
printf 'A\x00B\x01\x02C' > /root/demo/cmp/bin1.bin
printf 'A\x00B\x01\x03C' > /root/demo/cmp/bin2.bin
验证文件清单与大小:
$ ls -l /root/demo/cmp
total 20
-rw-r--r-- 1 root root 32 Oct 15 07:35 a.txt
-rw-r--r-- 1 root root 32 Oct 15 07:35 b.txt
-rw-r--r-- 1 root root 6 Oct 15 07:35 bin1.bin
-rw-r--r-- 1 root root 6 Oct 15 07:35 bin2.bin
-rw-r--r-- 1 root root 32 Oct 15 07:35 same.txt
用 xxd 把两个二进制文件的原始字节展开,后续实测可对照(第 5 字节 02 与 03 即差异点):
$ xxd /root/demo/cmp/bin1.bin
00000000: 4100 4201 0243 A.B..C
$ xxd /root/demo/cmp/bin2.bin
00000000: 4100 4201 0343 A.B..C
2.2 --help 全量回显
cmp --help 输出全部参数清单(实测原样保留,可作为参数速查表):
Usage: cmp [OPTION]... FILE1 [FILE2 [SKIP1 [SKIP2]]]
Compare two files byte by byte.
The optional SKIP1 and SKIP2 specify the number of bytes to skip
at the beginning of each file (zero by default).
Mandatory arguments to long options are mandatory for short options too.
-b, --print-bytes print differing bytes
-i, --ignore-initial=SKIP skip first SKIP bytes of both inputs
-i, --ignore-initial=SKIP1:SKIP2 skip first SKIP1 bytes of FILE1 and
first SKIP2 bytes of FILE2
-l, --verbose output byte numbers and differing byte values
-n, --bytes=LIMIT compare at most LIMIT bytes
-s, --quiet, --silent suppress all normal output
--help display this help and exit
-v, --version output version information and exit
SKIP values may be followed by the following multiplicative suffixes:
kB 1000, K 1024, MB 1,000,000, M 1,048,576,
GB 1,000,000,000, G 1,073,741,824, and so on for T, P, E, Z, Y.
If a FILE is '-' or missing, read standard input.
Exit status is 0 if inputs are the same, 1 if different, 2 if trouble.
Report bugs to: bug-diffutils@gnu.org
GNU diffutils home page: <https://www.gnu.org/software/diffutils/>
General help using GNU software: <https://www.gnu.org/gethelp/>
帮助末尾与 diff 相同的退出码约定(0 if inputs are the same, 1 if different, 2 if trouble)是 3.5、3.6 节实测的理论依据;SKIP 支持的 K/M/G 后缀说明 -i/SKIP 参数可以写 -i 1K 这类带单位的形式。
2.3 版本确认
确认实测环境的 cmp 版本(与 diff 同包同版本号):
$ cmp --version | head -1
cmp (GNU diffutils) 3.8
三、参数逐项实测
3.1 默认模式:定位首个差异字节
不加参数时,cmp 从字节 0 开始逐字节比较,遇到第一个不同的字节立即报告位置并停止。实测只有第 2 行大小写不同的 a.txt 与 b.txt:
$ cmp /root/demo/cmp/a.txt /root/demo/cmp/b.txt
/root/demo/cmp/a.txt /root/demo/cmp/b.txt differ: char 13, line 2
解读:a.txt 的第 1 行是 hello world(12 字节,含行尾换行),第 13 字节恰好是第 2 行的第一个字符 l,而 b.txt 同位置是大写 L——与输出 differ: char 13, line 2 完全吻合。cmp 的报告总是「首个差异」,其后内容是否相同一概不关心。
3.2 相同文件:静默与退出码 0
两个内容完全相同的文件比较,cmp 不输出任何信息、退出码 0:
$ cmp /root/demo/cmp/a.txt /root/demo/cmp/same.txt; echo 退出码=$?
退出码=0
这种「相同则静默、不同则一行报告」的行为,加上退出码语义,构成脚本里最朴素的两文件一致性判断。
3.3 -l:列出全部差异字节
-l(--verbose)不停在首个差异,而是扫完全程,把所有差异字节逐行列出。文本对实测:
$ cmp -l /root/demo/cmp/a.txt /root/demo/cmp/b.txt
13 154 114
14 151 111
15 156 116
16 145 105
解读:三列依次是「字节序号(十进制,从 1 起)」「文件1中该字节的值(八进制)」「文件2中该字节的值(八进制)」。154 八进制即十进制 108,也就是字符 l;114 八进制即十进制 76,也就是 L。四行差异对应 line 与 LINE 四个字母(l/L、i/I、n/N、e/E)。二进制对实测只有一处差异:
$ cmp -l /root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin
5 2 3
第 5 字节,文件1为八进制 2、文件2为八进制 3,与 2.1 节 xxd 展开的 02/03 一致。
3.4 -b:打印差异字节的八进制与字符表示
-b(--print-bytes)在默认报告的基础上追加两个差异字节的八进制值与可打印字符形式,仍只报告首个差异。文本对实测:
$ cmp -b /root/demo/cmp/a.txt /root/demo/cmp/b.txt
/root/demo/cmp/a.txt /root/demo/cmp/b.txt differ: byte 13, line 2 is 154 l 114 L
解读:与 3.1 相比有两点变化——报告词从 char 变成 byte(强调给出的是字节值),句尾追加 is 154 l 114 L:154 是文件1字节的八进制值、l 是其字符形式,114 与 L 对应文件2。二进制对实测,不可打印字节以 ^B/^C 控制符记法显示:
$ cmp -b /root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin
/root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin differ: byte 5, line 1 is 2 ^B 3 ^C
八进制 2 的控制字符写作 ^B(Ctrl-B 记法),八进制 3 写作 ^C。要「报首个差异 + 看字节值」用 -b,要「列出全部差异」用 -l,两者可叠加为 -lb 同时生效。
3.5 -s:静默模式与退出码分支
-s(--quiet/--silent)压制全部输出,只靠退出码说话:不同为 1、相同为 0。先实测不同与相同两种输入的退出码:
$ cmp -s /root/demo/cmp/a.txt /root/demo/cmp/b.txt; echo 退出码=$?
退出码=1
$ cmp -s /root/demo/cmp/a.txt /root/demo/cmp/same.txt; echo 退出码=$?
退出码=0
配合 shell 的 &&/|| 直接写成一行分支判断,这是 cmp 在脚本里的标准用法:
$ cmp -s /root/demo/cmp/a.txt /root/demo/cmp/same.txt && echo 文件相同 || echo 文件不同
文件相同
💡 提示:
cmp -s的退出码分支(0 相同 / 1 不同)是脚本里判断两文件是否一致的最低开销写法——只比到第一个差异字节即停,比diff生成补丁、比md5sum全量哈希都快。
3.6 -n:只比较前 N 字节
-n N(--bytes=N)把比较范围限定在前 N 字节,超出部分不看。实测两个场景:N=12 时恰好覆盖相同的第 1 行(hello world 共 12 字节含换行),范围内一致,静默退出码 0:
$ cmp -n 12 /root/demo/cmp/a.txt /root/demo/cmp/b.txt; echo 退出码=$?
退出码=0
N=20 时范围覆盖到第 13 字节的差异点,报告与全量比较一致:
$ cmp -n 20 /root/demo/cmp/a.txt /root/demo/cmp/b.txt; echo 退出码=$?
/root/demo/cmp/a.txt /root/demo/cmp/b.txt differ: char 13, line 2
退出码=1
典型用途:文件格式校验只需比对头部魔数(如 ELF 文件前 4 字节、tar 包的 262 字节头),无需通读整个文件。
3.7 二进制文件对比与 EOF 提示
对 2.1 节的二进制对,cmp 默认模式直接给出差异字节位置:
$ cmp /root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin
/root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin differ: char 5, line 1
对照同样的二进制对用 diff 比较,只能得到「是二进制、不同」的粗粒度结论:
$ diff /root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin; echo 退出码=$?
Binary files /root/demo/cmp/bin1.bin and /root/demo/cmp/bin2.bin differ
退出码=1
这组对照正是「字节级 vs 行级」定位差异的直接体现:二进制文件交给 cmp,文本文件交给 diff。
两文件长度不同、且短文件的全部内容与长文件前缀一致时,cmp 报告 EOF 而非差异字节。实测 32 字节的 a.txt 与 12 字节的 short.txt(后者内容恰为 a.txt 的前 12 字节):
$ printf 'hello world\n' > /root/demo/cmp/short.txt
$ cmp /root/demo/cmp/a.txt /root/demo/cmp/short.txt
cmp: EOF on /root/demo/cmp/short.txt after byte 12, line 1
解读:EOF on <文件名> after byte N 表示「读完短文件(到第 N 字节为止内容都与长文件一致),长文件还有剩余」。这类提示在排查截断传输(下载中断、日志切割)时非常直观。EOF 情况的退出码同样是 1。
四、实战示例
4.1 脚本中做零输出一致性判断
-s 与退出码组合,是发布脚本、备份脚本里判断「产物是否变化」的最小代价写法,实测一行分支:
$ cmp -s /root/demo/cmp/a.txt /root/demo/cmp/same.txt && echo 文件相同 || echo 文件不同
文件相同
把 echo 换成实际动作(复制、告警、触发构建)即是完整逻辑;需要区分「不同」与「出错」两种失败时,分别检查退出码 1 与 2。
4.2 二进制差异三步定位法
对二进制文件的差异,按「cmp 定位 → -b 看值 → xxd 验证」三步走。第一步定位(第 5 字节):
$ cmp /root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin
/root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin differ: char 5, line 1
第二步用 -b 读出差异字节的八进制值与字符形式(2 与 3,控制符 ^B 与 ^C):
$ cmp -b /root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin
/root/demo/cmp/bin1.bin /root/demo/cmp/bin2.bin differ: byte 5, line 1 is 2 ^B 3 ^C
第三步用 xxd 展开原始字节交叉验证(第 5 字节 02/03,偏移 00000004):
$ xxd /root/demo/cmp/bin1.bin
00000000: 4100 4201 0243 A.B..C
$ xxd /root/demo/cmp/bin2.bin
00000000: 4100 4201 0343 A.B..C
三步结论互相印证:差异在第 5 字节,八进制 2 变为 3。
五、注意事项与常见问题
Q1:cmp 和 diff 到底怎么分工? 看数据类型与要回答的问题:文本文件想「看哪里不一样、怎么改」用 diff(行级、人可读);二进制文件、或只问「是否一致、从哪个字节开始不一致」用 cmp(字节级、可短路)。cmp 比较文本文件也完全合法(3.1 节实测),只是输出定位到字节而非行,可读性不如 diff。
Q2:为什么默认输出写 char 13 而 -b 输出写 byte 13?
实测口径:默认报告只定位「第几个字符位置」,用词是 char;-b 追加字节值后用词是 byte(differ: byte 13, line 2 is 154 l 114 L);-l 则以十进制字节序号加两列八进制值输出。三种说法指向同一个位置,理解即可,不必纠结用词。
Q3:cmp: EOF on /root/demo/cmp/short.txt after byte 12 这类提示是错误吗?
不是错误,是「长度不一致且短文件是长文件的前缀」这一事实的报告(3.7 节实测);若两文件既有字节差异又长度不同,cmp 会先报首个差异字节。EOF 情况退出码为 1,脚本中与「内容不同」走同一分支,需要区分时看输出是否含 EOF 字样。
Q4:-n 12 为什么退出码 0?两个文件明明不同?
-n 限定了比较范围:前 12 字节内两个文件完全一致,范围内「相同」即退出码 0(3.6 节实测)。它回答的是「前 N 字节是否一致」,不是「文件是否一致」,用于魔数/头部校验时语义刚好正确。
Q5:cmp 能比较命令的输出流吗?
可以。帮助原文明确 If a FILE is '-' or missing, read standard input,把 - 作为文件名即从 stdin 读入,例如管道进来的数据与磁盘文件比对;两个输入流可用 Bash 进程替换 <(命令) 传入。SKIP 参数还支持跳过两文件各自的头部字节(SKIP1/SKIP2,可带 K/M/G 后缀)后开始比较。
六、参考链接
- GNU diffutils 官方手册(cmp 章节):https://www.gnu.org/software/diffutils/manual/html_node/cmp-Invocation.html
- GNU diffutils 首页:https://www.gnu.org/software/diffutils/
- 本机帮助:
cmp --help、man cmp(Ubuntu 22.04 内置)