[Linux]複数ファイルの片方にしか無いリストを作る

今日もLinuxです。

シチュエーション

  • 値が一列に記載されたテキスト(元リスト)がある。
  • 元リストから任意の値が抽出されたテキスト(リストA)がある。
  • 元リストからリストAではないリストを作りたい。

元リスト base_list

hoge
fuga
piyo
foo
bar

リストA list_a
※base_listからいくつか抜き出したリスト

hoge
fuga
bar

作りたいリスト list_b
※base_list – list_aのリスト

piyo
foo

※順不同

結論

commを使う

$ comm  -23 <(sort base_list.tsv) <(sort list_a.tsv)
foo
piyo

joinを使う

$ join -v 1 <(sort base_list.tsv) <(sort list_a.tsv)
foo
piyo

解説

comm

基本文法

$ comm file_1 file_2

file_1とfile_2を比較し、1列目にはfile_1のみ、2列目にfile_2のみ、3列目には両者に共通する項目を表示します。

option : -[数字]

-数字とすることで、任意の列の出力を抑制できます。
これを利用して、1列目にのみ存在する項目を-23というオプションで出力できます。

join

基本文法

$ join file_1 file_2

file_1とfile_2の共通する項目を出力するコマンドです。
オプションなしで実行したときの結果は以下になります。

$ join <(sort base_list.tsv) <(sort list_a.tsv)
bar
fuga
hoge

option : -v

-vオプションをつけることで、一致しない行を出力します。
なので、base_listとlist_aで一致しない、つまりbase_listにしかない行を出力します。

<(sort filename)

commもjoinも、両方のファイルが昇順ソートされている必要があります。
とはいえ毎回xxx_sort.tsvみたいなファイルを作るのも面倒です。

そこで。

$ some_cmd <(another_cmd)

の形式で、some_cmdの対象にanother_cmdの結果をもってくることができます。パイプと似たような使い方です。

ハマりポイント

commは、ファイル同士の改行コードが違うと正しく動作しません。(当たり前ですが)

VirtualBoxなどのVM環境で、ファイル共有をしてゴニョゴニョとやっているファイルを扱う場合は注意が必要です。
私はWindows <=> Linuxのコードの違いにハマって小一時間潰しました…

nkfなどのコマンドで改行コードを変換してからcommを実行しましょう。

参考