🦉
awkを使って2つのcsvファイルを比較する方法
はじめに
実はawkコマンドは複数のファイルを引数に取れます。(↓man awk
より抜粋)
SYNOPSIS
awk [ -F fs ] [ -v var=value ] [ 'prog' | -f progfile ] [ file ... ]
こんな2つのファイルを用意。
first.txt
111
222
333
second.txt
444
555
666
awkに2つのファイルを引数に渡すと2つのファイルが連結されたように処理されます。
> awk '{print $0}' first.txt second.txt
111
222
333
444
555
666
awkにはいくつかビルトイン変数があって、ファイルを比較するときには NR
と FNR
を使います。
NRにはファイルが連結された状態で連番が、FNRは各ファイルごとに連番が振られます。
> awk '{print $0}' first.txt second.txt
111 // NR = 1 FNR = 1
222 // NR = 2 FNR = 2
333 // NR = 3 FNR = 3
444 // NR = 4 FNR = 1
555 // NR = 5 FNR = 2
666 // NR = 6 FNR = 3
従って、NR==FNR
を条件にすれば1ファイル目の行、NR!=FNR
を条件にすれば2ファイル目の行だけを処理することが出来ます。
実際に比較してみる
例えばこんなcsvがあるとします。
before.csv
user_id,name,birthday,gender
1,aaa,20000101,male
2,bbb,19501201,female
3,ccc,19990707,female
4,ddd,20101010,female
このcsvのuser_idを+100したafter.csv
を作成して、本当にafter.csv
のuser_idがbefore.csv
の+100になっているのかを最後に確認したい場合、diffコマンドだとuser_idが異なるので全行出力されてしまいます。
この例では数行程度なので大した手間ではないですが、何万行もあるファイルだったらawkで処理できると楽できます。
今回はあえてミスしたafter.csv
を作成して、エラーを検知できるか検証します。
after.csv
user_id,name,birthday,gender
101,aaa,20000101,male
103,bbb,19501201,female // 2+100 -> 103のミス
103,ccc,19990707,female
104,ddd,20101010,female
このような処理を記述したファイルを作成しておきます。
compare.awk
NR!=1 && NR==FNR { mem[NR] = $1; next }
FNR!=1 && NR!=FNR && mem[FNR] != ($1 - 100) { print "error. Before user_id:",mem[FNR],",after user_id",$1,",line:",FNR}
awkは事前に処理をファイルに記述してそれを-f
オプションで渡すことが出来ます。
実際に実行してみると、ちゃんとミスした行を出力してくれました。
> awk -F, -f compare.awk before.csv after.csv
error. Before user_id: 2 ,after user_id 103 ,line: 3
処理の詳細
一行目
NR!=1 && NR==FNR { mem[NR] = $1; next }
-
NR!=1
:1行目(=ヘッダ)を除く -
NR==FNR
:前述の通り、1ファイル目 -
mem[NR] = $1
:配列mem
の{NR}
番目の要素に、1列目のデータ(=user_id)を格納
FNR!=1 && NR!=FNR && mem[FNR] != ($1 - 100) { print "error. Before user_id:",mem[FNR],",after user_id",$1,",line:",FNR}
-
FNR!=1
:1行目(=ヘッダ)を除く -
NR!=FNR
:前述の通り、2ファイル目 -
mem[FNR] != ($1 - 100)
:1ファイル目のuser_idの配列の{FNR}
番目の要素の値が、処理行の値から100引いた値と等しいか。つまり、1ファイル目と2ファイル目の同じ行目のuser_idの差分が100でない場合。
追記
awkについてはとほほさんのawk入門がすごく参考になります。
Discussion