2つのファイルに散らばった情報を、共通のIDでひとつにまとめたい。
表計算ソフトなら VLOOKUP で片づく話が、ターミナルの中では急に面倒に感じられませんか。
そんなときに出番なのが join です。
joinは、キーの並んだ2つのファイルを「同じキーの行どうし」で1行につなぐコマンドで、SQL でいう内部結合にあたる処理をファイル相手にやってくれます。
この記事の出力はすべて手元で実行して確かめたもので、環境は GNU coreutils 8.32 と GNU bash 5.2.37(MSYS2 / Git Bash)です。
joinは「同じキーの行」を1行につなぐ
まずは、いちばん素朴な使い方から。
会員IDと名前を並べたファイルと、会員IDと契約プランを並べたファイルを用意してみました。
$ cat users.txt
1001 alice
1002 bob
1003 carol
1005 erin
$ cat plans.txt
1001 free
1002 pro
1003 pro
1004 trial
この2つをそのまま渡すと、こうなります。
$ join users.txt plans.txt
1001 alice free
1002 bob pro
1003 carol pro
キーの指定を省いたときは、両方のファイルとも1列目がキーという扱いになりました。
出力は「キー・1つ目の残り列・2つ目の残り列」の順に並ぶ決まり。
片方にしか無い 1004 と 1005 は、既定では黙って捨てられます。
ここまでは、名前のとおり素直な挙動ですよね。
最初の関門は「ソート済みかどうか」
joinでつまずくポイントは、ほぼここに集約されます。
並んでいないと、行が静かに落ちる
joinは2つのファイルを先頭から1行ずつ読み進める作りで、中身を丸ごとメモリに載せません。
そのためキーが昇順に並んでいることが前提で、崩れていると照合を取りこぼします。
$ cat unsorted.txt
1003 carol
1001 alice
1002 bob
$ join unsorted.txt plans.txt
join: unsorted.txt:2: is not sorted: 1001 alice
1003 carol pro
join: input is not in sorted order
$ echo $?
1
本来3行そろうはずの結果が、1行だけになりました。
幸いこのときは警告と終了ステータス 1 が返るので、気づく余地があります。
怖いのは、警告がうるさいからと --nocheck-order を付けてしまったとき。
$ join --nocheck-order unsorted.txt plans.txt
1003 carol pro
$ echo $?
0
警告が消え、終了ステータスまで 0 になりました。
2行ぶんのデータが失われたまま、処理は成功したことになります。
このオプションは、順序を自分で保証できるときだけ使うもの。
sortを通してからjoinへ渡す
対処はシンプルで、両方のファイルをキー列で sort してから渡すだけです。
bash のプロセス置換を使えば、中間ファイルを作らずに書けました。
$ join <(sort unsorted.txt) plans.txt
1001 alice free
1002 bob pro
1003 carol pro
ここで見落としやすいのが、joinはキーを数値ではなく文字列として比べるという点。
片方を sort -n、もう片方を素の sort で並べると、両者の順序観がずれて事故になります。
$ join <(sort -n m1.txt) <(sort m2.txt)
2 c x
join: /dev/fd/63:3: is not sorted: 10 b
9 a y
join: input is not in sorted order
$ join <(sort m1.txt) <(sort m2.txt)
10 b z
2 c x
9 a y
キー 10 の行が落ちたのは、辞書順では 10 が 2 より前に来るからでした。
並べ替えと突き合わせで、同じ物差しを使う。
照合順序が環境で揺れるのを避けたいなら、sort と join の両方に LC_ALL=C を付けておくと安心できます。
片側にしかない行を落とさない
既定のjoinは、両方にキーがある行しか出しません。
けれど実務では「片方にしか無い行こそ知りたい」場面のほうが多いのではないでしょうか。
-aで残し、-oと-eで穴を埋める
-a1 と -a2 は、それぞれ1つ目・2つ目のファイルで相手が見つからなかった行も出力する指定です。
$ join -a1 -a2 users.txt plans.txt
1001 alice free
1002 bob pro
1003 carol pro
1004 trial
1005 erin
1004 と 1005 が残りました。
ただ、このままでは欠けた列が詰まってしまい、何列目が何なのか読み取れません。
そこで -o で出したい列を並べ、-e に空欄の埋め文字を渡すのです。
$ join -a1 -a2 -e '(なし)' -o '0,1.2,2.2' users.txt plans.txt
1001 alice free
1002 bob pro
1003 carol pro
1004 (なし) trial
1005 erin (なし)
0 がキー列、1.2 は「1つ目のファイルの2列目」という意味。
列の位置が固定されるので、後続の awk や cut にそのまま流し込めます。
列を切り出す・貼り合わせるほうの道具は、cut と paste の記事で扱いました。
ひとつ注意したいのは、-e を単独で付けても何も起きないという点です。
手元で -o を外して試したところ、埋め文字は無視されて欠けた列は詰まったまま出力されました。
-e は -o とセットで初めて効くオプションと覚えておくと、無駄に悩まずに済みます。
-vで「合わなかった行だけ」を取り出す
差分そのものが欲しいときは -v の出番。
$ join -v1 users.txt plans.txt
1005 erin
$ join -v2 users.txt plans.txt
1004 trial
マスタと実データを突き合わせて、片方にしか存在しないIDを洗い出す用途にそのまま使えます。
行そのものを比べる comm に対して、joinが比べるのはキー列だけ。
言い換えると、キー以外の列がどれだけ違っていても、joinは同じキーなら同じ行として扱ってくれるわけです。
ID は同じなのに名前の表記だけ揺れている、といったデータでも取りこぼしません。
比較の単位が違うと捉えると、2つのコマンドの使い分けが見えてきますね。
区切り文字とキー列を指定する
実際のデータが、いつも空白区切りとはかぎりません。
カンマ区切りを扱うときは -t で区切り文字を渡す決まり。
$ join -t, <(sort -t, -k1,1 users.csv) <(sort -t, -k1,1 plans.csv)
1001,alice,sendai,free,2026-01-15
1002,bob,tokyo,pro,2026-04-01
1003,carol,osaka,pro,2025-11-30
sort 側にも同じ区切り文字とキー列を伝えるのを忘れないでください。
キーが1列目に無いときは、-1 と -2 でそれぞれのキー列番号を指定できます。
$ cat l2.txt
sendai 1002
tokyo 1001
$ join -1 2 -2 1 <(sort -k2,2 l2.txt) <(sort -k1,1 r2.txt)
1001 tokyo free
1002 sendai pro
左のファイルは2列目、右のファイルは1列目をキーにして連結されました。
出力の先頭には必ずキー列が来るので、元の列順とは変わる点だけ頭に入れておくと混乱しません。
細かなオプションの一覧は、GNU Coreutils の join のマニュアルにまとまっています。
最後に
joinは、両方のファイルを同じ物差しでソートしてから渡す、この一点さえ守れば怖くありません。
あとは -a で残し、-v で差分を取り、-o で列を整えるだけ。
突き合わせを表計算ソフトに貼らずターミナルで完結できるようになると、確認の手数がぐっと減りました。
次に2つのファイルを見比べたくなったら、コピペの前にjoinを思い出してみてください。
以上です。











コメントを残す