Comm 命令快速找出两个文件的异同
处理两份文本清单,想知道哪些行是共有的、哪些是各自独有的,我第一反应不是写脚本,而是 comm。它要求文件先排好序,然后按三列把结果摊开给你看。这篇讲清 comm 的用法、三列含义,以及跟 diff 的取舍。
手头有两份用户名单,一份是上周的,一份是这周的。我想知道谁是两周都在的,谁是这周新来的,谁上周还在这周走了。
这种“两个清单求交集、求差集”的活儿,写脚本当然能干,但太重了。
Linux 里有个小命令专门干这个,叫 comm。
名字取自 common,就是“共同”的意思。它安静、简单,很多人装了一辈子系统都没用过它。
comm 到底在干嘛
comm 接收两个已经排好序的文件,逐行比较,然后把结果分成三列输出给你。
- 第一列:只在第一个文件里出现的行
- 第二列:只在第二个文件里出现的行
- 第三列:两个文件都有的行(共同行)
就这么简单。三列,对应三种归属。
我们先造两个文件试试。老王维护的这周名单和上周名单:
# 上周在岗的人(week1.txt)
cat > week1.txt <<'EOF'
laowang
laozhang
wangayi
niubi
EOF
# 这周在岗的人(week2.txt)
cat > week2.txt <<'EOF'
laowang
laozhang
mebugs
niubi
EOF
直接跑 comm:
comm week1.txt week2.txt
输出大概长这样:
laowang
laozhang
wangayi
mebugs
niubi
看着有点懵?别急。这里的缩进就是“列”。
- 顶格没缩进的
wangayi在第一列,说明只有上周有他,这周走了 - 缩进两个 tab 的
mebugs在第二列,说明这周才来的新人 - 缩进一个 tab 的
laowang、laozhang、niubi在第三列,两周都在
一眼就把“走的、来的、留的”分清楚了。
为什么必须先排序
comm 有个硬前提:两个文件都得是排好序的。
这不是它偷懒,而是它的工作方式决定的。comm 是一行一行往下扫,两边同时推进指针,靠“当前行谁大谁小”来判断归属。这套逻辑只有在有序的前提下才成立。
如果文件没排序,comm 不会报错,但结果是错的,而且错得很隐蔽。
我踩过这个坑,名单没排序,comm 给我一堆莫名其妙的“独有行”,查半天才反应过来是顺序的问题。
所以稳妥的做法是先 sort 再 comm:
# 用进程替换,不落地临时文件,直接把排好序的结果喂给 comm
comm <(sort week1.txt) <(sort week2.txt)
<(...) 是 bash 的进程替换,把命令的输出当成一个临时文件传进去。省得先 sort 生成两个中间文件再删。
个人习惯是能不落临时文件就不落,干净。
用列开关精确取想要的
三列一起看有时候太杂,我其实只想要其中一两列。
comm 提供了 -1、-2、-3 三个开关,含义是抑制(不显示)对应的列。
注意,是“抑制”不是“显示”,这点特别容易记反。
-1:不显示第一列(第一个文件独有的)-2:不显示第二列(第二个文件独有的)-3:不显示第三列(共同行)
反过来理解就顺了:想留哪几列,就把不想要的那几列关掉。
比如我只想要两个文件的共同行(交集),那就把第一列和第二列都关掉,只留第三列:
# 只留第三列 = 求交集
comm -12 <(sort week1.txt) <(sort week2.txt)
输出:
laowang
laozhang
niubi
干净利落,就是两周都在的人。
我想要“这周新来的”,也就是只在第二个文件里的行,那就关掉第一列和第三列:
# 只留第二列 = week2 独有
comm -13 <(sort week1.txt) <(sort week2.txt)
输出:
mebugs
想要“上周走的”,同理,关掉第二列和第三列:
# 只留第一列 = week1 独有
comm -23 <(sort week1.txt) <(sort week2.txt)
输出:
wangayi
是不是有种在玩开关的感觉?三个开关随意组合,交集、差集、对称差,全都能拼出来。
几个实用的小场景
光看名单没意思,说几个我真实用到过的。
对比两台机器的软件包
运维经常要对比两台机器装的软件包列表,看看哪台多装了什么。
# 分别导出两台机器的包列表并排序
ssh laowang@host-a "rpm -qa | sort" > pkg_a.txt
ssh laowang@host-b "rpm -qa | sort" > pkg_b.txt
# 只看 A 有而 B 没有的
comm -23 pkg_a.txt pkg_b.txt
一行命令,A 比 B 多装的包全列出来了。
配合 wc 数个数
comm 的输出可以接着往下管道。想知道两份清单到底有多少重合,直接数第三列:
# 数一下有多少共同行
comm -12 <(sort week1.txt) <(sort week2.txt) | wc -l
输出 3,三个人两周都在。
检查子集关系
如果 A 文件的所有行都在 B 里,那“A 独有”这一列应该是空的。
# 如果没有任何输出,说明 A 完全被 B 包含
comm -23 <(sort a.txt) <(sort b.txt)
没输出就是子集,有输出就把多出来的行摆给你看。这个判断比写循环挨个 grep 舒服多了。
comm 和 diff 怎么选
有人会问,对比文件不是有 diff 吗,要 comm 干嘛。
这俩关注的东西不一样。
- diff 关心的是怎么把文件 A 改成文件 B,输出的是“增删改”的编辑动作,在乎行的顺序和上下文。
- comm 关心的是集合关系,谁有谁没有,不在乎行原本的顺序(反正都要求你先排序)。
打个比方,diff 像是给你一份修改批注,告诉你第几行改成什么。comm 更像是把两堆东西倒一块儿,帮你分好“你的、我的、共同的”三摞。
所以:
- 对比代码改动、看版本差异 → 用 diff
- 求两个清单的交集差集、判断包含关系 → 用 comm
场景对上了,用起来就顺手。硬拿 diff 去求交集,或者拿 comm 去看代码 diff,都是别扭。
小结
comm 就是个专注做“集合运算”的小工具,核心记住三点就够用了。
- 三列分别是:文件一独有、文件二独有、两者共有
- 用前必须排序,配
<(sort ...)最省事 -1 -2 -3是“抑制”对应列,想留哪列就关掉其他列
它不花哨,但在处理清单类文本时特别对口。下次再遇到“这两份名单有啥不一样”,别急着写脚本了,先想想 comm 能不能一行搞定。
那就开始尝试吧。
