comm入門|2つのファイルの共通行と差分を見分ける基本

comm入門|2つのファイルの共通行と差分を見分ける基本

「このリストにしか無い行は、どれだろう」

2つの一覧を突き合わせたいとき、そんな確認をしたくなる場面があります。

とりあえず diff を叩いてみると、返ってくるのは「どこをどう書き換えれば一致するか」という編集手順。

私が知りたかったのは、もっと素朴な仕分けでした。

両方にある行と、片方だけにある行。

この用途にまっすぐ効くのが comm コマンドです。

commは行を「集合」として仕分ける

comm はソート済みの2つのファイルを先頭から突き合わせ、それぞれの行を3つのグループへ振り分けてくれます。

出力は3列に分かれる

まず、比較用の小さなファイルを2つ用意しました。

$ cat a.txt
apple
banana
cherry
date

$ cat b.txt
banana
cherry
fig
grape

オプションを付けずに実行すると、出力はこんな見た目になります。

$ comm a.txt b.txt
apple
		banana
		cherry
date
	fig
	grape

左端に寄っているのが a.txt だけにある行。

タブ1つぶん右へずれた行は、b.txt だけにあるものです。

タブ2つぶん下がった行が両方に共通する行

つまり第1列=左だけ/第2列=右だけ/第3列=両方という並びで固定されています。

diffとは見ている軸が違う

同じ「2ファイルを比べる」でも、diff と comm が返す答えの形は別物です。

diff が返すのは、片方をもう片方へ変形するための編集手順。

comm が返すのは、その行がどちらの集合に属するかという所属情報です。

だから、行の並び順が揃っているかどうかを気にする必要がありません。

インストール済みパッケージの一覧、許可リスト、ファイル名の棚卸し。

「順番はどうでもよくて、有無だけ知りたい」場面では、comm のほうが素直に読めます。

要らない列を -1 -2 -3 で落とす

3列そのままだと目で追いにくいので、普段は列を絞って使うのが実用的です。

数字が指すのは残す列ではなく、消したい列の番号という点に注意してください。

共通行だけ見たいとき

第1列と第2列を捨てれば、両方にある行だけが残ります。

$ comm -12 a.txt b.txt
banana
cherry

「どちらにも登録されているのはどれか」を知りたいときの定番。

片方にしか無い行を見たいとき

a.txt 側だけを知りたいなら、第2列と第3列を落とします。

$ comm -23 a.txt b.txt
apple
date

逆に b.txt 側だけを取り出すなら、-13 の組み合わせ。

$ comm -13 a.txt b.txt
fig
grape

「消えた行」と「増えた行」を別々に取り出せるので、そのまま次の処理へパイプで流し込めます

共通行を除いた差分だけ見たいとき

-3 だけを付けると、食い違っている行が左右に振り分けられて出てきました。

$ comm -3 a.txt b.txt
apple
date
	fig
	grape

タブの有無で、どちら側の行なのかが判別できる形になっています。

ソート済みが前提という落とし穴

comm でいちばんつまずくのは、入力が同じ順序に並んでいることを前提にしている点です。

並んでいないと結果が壊れる

試しに、わざと順不同のファイルを食わせてみます。

$ cat c.txt
cherry
apple
banana

$ comm c.txt b.txt
	banana
		cherry
comm: file 1 is not in sorted order
apple
banana
	fig
	grape

警告は出るものの、途中まで平然と間違った結果を吐いているところが厄介でした。

終了ステータスは 1 になるので、スクリプトの中なら戻り値で気づけます。

対策はシンプルで、比較の直前に sort を通すだけ。

$ comm <(sort c.txt) <(sort b.txt)
apple
		banana
		cherry
	fig
	grape

ロケールで照合順が変わる

もう一つの罠が、LC_COLLATE による照合順の違いです。

sort と comm が別々の基準で並びを判断すると、ソート済みのはずのファイルで不一致が起きてしまいます。

スクリプトに書くなら、両方の頭に LC_ALL=C を付けて基準をそろえておくと安全。

なお comm には、大文字と小文字を無視するオプションが用意されていません。

ケースをそろえたいときは、tr などで事前に変換してから渡す形になります。

コマンドの出力どうしを直接比べる

ファイルに落とさなくても、プロセス置換を使えばコマンドの出力を直接渡せるのです。

ディレクトリの中身を突き合わせる

$ comm -3 <(ls old) <(ls new)
legacy.js
	router.js

old にしか無いファイルと、new にしか無いファイルが1行ずつ並びました。

移行作業で「消し忘れ」と「追加漏れ」を同時に洗い出せるので、私はこの形をよく使います。

件数だけ知りたいとき

–total を付けると、末尾に3列ぶんの合計が1行だけ加わりました。

$ comm --total a.txt b.txt | tail -1
2	2	2	total

左だけ2件、右だけ2件、共通2件。

中身を読まずに規模だけ把握したい場面では、この–total オプションが効きます。

最後に

comm は、2つのリストを集合として仕分けるためのコマンドでした。

並び順まで含めて比べたいなら diff、有無だけ知りたいなら comm。

diff入門|2つのファイルの違いを行単位で見つける基本

この住み分けを覚えておくだけで、シェルでの棚卸し作業がずいぶん軽くなります

ソートを通してから渡す、という一手間さえ守れば怖くないのが、このコマンドの良いところ。

細かい仕様はGNU coreutils マニュアルの comm invocationに載っています。

NO IMAGE

なお本記事の出力例は、GNU coreutils 8.32 と GNU bash 5.2.37 の環境で確かめたものです。

以上です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA