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 能不能一行搞定。

那就开始尝试吧。

更多推荐

章节目录