join入門|2つのファイルをキー列で突き合わせる基本

join入門|2つのファイルをキー列で突き合わせる基本

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 より前に来るからでした。

並べ替えと突き合わせで、同じ物差しを使う。

照合順序が環境で揺れるのを避けたいなら、sortjoin の両方に 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 の記事で扱いました。

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 のマニュアルにまとまっています。

NO IMAGE

最後に

joinは、両方のファイルを同じ物差しでソートしてから渡す、この一点さえ守れば怖くありません。

あとは -a で残し、-v で差分を取り、-o で列を整えるだけ。

突き合わせを表計算ソフトに貼らずターミナルで完結できるようになると、確認の手数がぐっと減りました。

次に2つのファイルを見比べたくなったら、コピペの前にjoinを思い出してみてください。

以上です。

コメントを残す

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

CAPTCHA