先日のシェル芸勉強会のテーマがjoinコマンドだったのだが、普段あまりjoinコマンドを使ってなかった事もあって、あまりjoinコマンドで解答をできなかったので、使い方について調べなおしてまとめてみることにした。

1. 基本的な使い方

joinコマンドは、2つのファイルをSQLのjoinのように結合して表示してくれるというコマンドになる。 以下のように、1列目の形式が同じで順番も揃っている場合、オプションなしで結合をしてくれる。

join file1 file2
blacknon@BS-PUB-DEVELOP:~$ cat test1.a
1 a
2 a
3 a
4 a
5 a
6 a
7 a
8 a
9 a
10 a
blacknon@BS-PUB-DEVELOP:~$ cat test1.b
1 b
2 b
3 b
4 b
5 b
6 b
7 b
8 b
9 b
10 b
blacknon@BS-PUB-DEVELOP:~$ join test1.{a,b}
1 a b
2 a b
3 a b
4 a b
5 a b
6 a b
7 a b
8 a b
9 a b
10 a b

この時、joinに使用する列がsortされていないと結合ができない。 このため、結合時はsortと組み合わせるのが基本となるだろう。 いちいちエラーメッセージが出てくるのが邪魔であれば「--nocheck-order」オプションを付与するといいだろう。

sort <(sort file1) <(sort file2)
blacknon@BS-PUB-DEVELOP:~$ cat test1.as
3 a
5 a
1 a
10 a
9 a
8 a
2 a
6 a
4 a
7 a
blacknon@BS-PUB-DEVELOP:~$ cat test1.b
1 b
2 b
3 b
4 b
5 b
6 b
7 b
8 b
9 b
10 b
blacknon@BS-PUB-DEVELOP:~$ join test1.{as,b}
3 a b
join: test1.as:3: is not sorted: 1 a
5 a b
join: test1.b:10: is not sorted: 10 b
9 a b
blacknon@BS-PUB-DEVELOP:~$ join <(sort -n test1.as) <(sort -n test1.b)
1 a b
2 a b
3 a b
4 a b
5 a b
6 a b
7 a b
8 a b
9 a b
10 a b

ちなみに、オプションなしだと表示されるのは「結合ができた行」のみになる。

blacknon@BS-PUB-DEVELOP:~$ cat test1.a
1 a
2 a
3 a
4 a
5 a
6 a
7 a
8 a
9 a
10 a
blacknon@BS-PUB-DEVELOP:~$ cat test1.bs
3 b
4 b
5 b
6 b
blacknon@BS-PUB-DEVELOP:~$ join --nocheck-order test1.{a,bs}
3 a b
4 a b
5 a b
6 a b4

2. 区切り文字を切り替える

joinコマンドのデフォルトの区切り文字はスペースとなっているのだが、これをカンマなど任意の文字に切り替わる場合、「-t」オプションで指定できる。

join -t 区切り文字 file1 file 2
blacknon@BS-PUB-DEVELOP:~$ cat test1.a_c
1,a
2,a
3,a
4,a
5,a
6,a
7,a
8,a
9,a
10,a
blacknon@BS-PUB-DEVELOP:~$ cat test1.b_c
1,b
2,b
3,b
4,b
5,b
6,b
7,b
8,b
9,b
10,b
blacknon@BS-PUB-DEVELOP:~$ join -t, test1.{a,b}_c
1,a,b
2,a,b
3,a,b
4,a,b
5,a,b
6,a,b
7,a,b
8,a,b
9,a,b
10,a,be

3. 結合に使用する列を指定する

joinコマンドのデフォルトでは、各ファイルで結合に使用する列は1列目を使用するのだが、それを変更する場合は「-1 2」(1ファイル目の2列目)、「-2 3」(2ファイル目の3列目)といった形で結合に使用する列を指定することができる。

join -1 n -2 n file1 file2
blacknon@BS-PUB-DEVELOP:~$ cat test1.a_r
a 1
a 2
a 3
a 4
a 5
a 6
a 7
a 8
a 9
a 10
blacknon@BS-PUB-DEVELOP:~$ cat test1.b
1 b
2 b
3 b
4 b
5 b
6 b
7 b
8 b
9 b
10 b
blacknon@BS-PUB-DEVELOP:~$ join -1 2 -2 1 test1.{a_r,b}
1 a b
2 a b
3 a b
4 a b
5 a b
6 a b
7 a b
8 a b
9 a b
10 a b

4. 外部結合(LEFT OUTER JOIN)を行う

joinコマンドでいわゆる外部結合(片方、もしくは両方のファイルで結合できなかった行も表示させる)を行う場合は、「-a 1」(1個目のファイル側の行は全て表示させる)といったオプションを付与してやれば良い。

join -a n file 1 file 2
join -a 1 -a 2 file1 file 2 #両方のファイルで全行表示させる
blacknon@BS-PUB-DEVELOP:~$ cat test1.a
1 a
2 a
3 a
4 a
5 a
6 a
7 a
8 a
9 a
10 a
blacknon@BS-PUB-DEVELOP:~$ cat test1.bs
3 b
4 b
5 b
6 b
blacknon@BS-PUB-DEVELOP:~$ join --nocheck-order -a 1 test1.{a,bs}
1 a
2 a
3 a b
4 a b
5 a b
6 a b
7 a
8 a
9 a
10 a

5. 出力する列を指定する

出力する列の順番やどの列を出力するのかを指定する場合、「-o」オプションで指定することができる。 指定方法は、「-o 1.1 1.2 2.3」(1ファイル目の1列目(1.1)、2列目(1.2)と2ファイル目の3列目(2.3))といったように、ドットを使って表現する。

blacknon@BS-PUB-DEVELOP:~$ cat test1.a_n
1 a 1_a
2 a 2_a
3 a 3_a
4 a 4_a
5 a 5_a
6 a 6_a
7 a 7_a
8 a 8_a
9 a 9_a
10 a 10_a
blacknon@BS-PUB-DEVELOP:~$ cat test1.b
1 b
2 b
3 b
4 b
5 b
6 b
7 b
8 b
9 b
10 b
blacknon@BS-PUB-DEVELOP:~$ # 1ファイル目の3列目、2ファイル目の2列目を出力する
blacknon@BS-PUB-DEVELOP:~$ join -o 1.3 2.2 test1.{a_n,b}
1_a b
2_a b
3_a b
4_a b
5_a b
6_a b
7_a b
8_a b
9_a b
10_a b

6. フィールドがない場合に変わりの文字列を入れてやる

結合ができなかったためフィールドの表示ができない場合、通常であれば何も表示されないのだが、「-e 文字列」としてオプションを付与することで、その文字列を変わりに表示してくれるようになる。 -oと組み合わせて使うパターンが多いだろう。

join -e String file1 file2
blacknon@BS-PUB-DEVELOP:~$ join --nocheck-order -a 1 test1.{a,bs}
1 a
2 a
3 a b
4 a b
5 a b
6 a b
7 a
8 a
9 a
10 a
blacknon@BS-PUB-DEVELOP:~$ join -e NULL --nocheck-order -a 1 test1.{a,bs}
1 a
2 a
3 a b
4 a b
5 a b
6 a b
7 a
8 a
9 a
10 a
blacknon@BS-PUB-DEVELOP:~$ join -e NULL --nocheck-order -a 1 -o 1.1 1.2 2.2 test1.{a,bs}
1 a NULL
2 a NULL
3 a b
4 a b
5 a b
6 a b
7 a NULL
8 a NULL
9 a NULL
10 a NULL

7.結合できなかった行のみを出力する

結合が出来なかった行のみを出力する場合、「-v 1」(1ファイル目で結合できなかった行を表示)といったオプションを付与すれば良い。

join -v n file1 file2
blacknon@BS-PUB-DEVELOP:~$ join --nocheck-order -a 1 test1.{a,bs}
1 a
2 a
3 a b
4 a b
5 a b
6 a b
7 a
8 a
9 a
10 a
blacknon@BS-PUB-DEVELOP:~$ join --nocheck-order -v 1 test1.{a,bs}
1 a
2 a
7 a
8 a
9 a
10 a

8. 結合時に大文字・小文字を区別させない

結合時に使用する列で大文字・小文字を区別させないようにする場合、「-i」オプションを指定する。

join -i file1 file2
blacknon@BS-PUB-DEVELOP:~$ cat test1.a_r
a_1 1
a_2 2
a_3 3
a_4 4
a_5 5
a_6 6
a_7 7
a_8 8
a_9 9
a_10 10
blacknon@BS-PUB-DEVELOP:~$ cat test1.a_r2
A_1 A
A_2 A
A_3 A
A_4 A
A_5 A
A_6 A
A_7 A
A_8 A
A_9 A
A_10 A
blacknon@BS-PUB-DEVELOP:~$ join -i test1.a_r{,2}
a_1 1 A
a_2 2 A
a_3 3 A
a_4 4 A
a_5 5 A
a_6 6 A
a_7 7 A
a_8 8 A
a_9 9 A
a_10 10 A