こんにちは マネックスラボのMです。
今日はawkについて語っていきたいと思います。
マネックスラボではMONEX VIEW、MONEX VISON、MONEX 投資力診断などのサービスを開発していますが
開発の過程でデータ確認を行うことがよくあります。そして私はデータ確認にLinuxコマンドを多用します。
Linuxコマンドの中で最も優秀なのってなんでしょうね。
というソシャゲの最強キャラを言い争うような発言はしたくありませんが、
私はawkが好きです。優秀かどうかというより好きって表現が似合いますねawkには。
実は別にawkに詳しいわけでもありませんので、語るというほどの事もなく、
今日はこの使い方便利だよねっていう例をいくつか紹介させて頂きます。
区切り文字指定の-F、指定文字に該当する行抽出、該当行と出力項目を指定して出力
最も基本的な使い方になるでしょうか。data1のようなデータが並んでいるとして
「区切り文字(デフォルトはスペースかタブ)をカンマに指定」して 、「orangeを含んでいる行の」「1カラム目と3カラム目を抜き出す」だけです。
ついでに「行数をNRで出力」しています。
apple,shop1,100 orange,shop2,90 grape,shop3,200
$ awk -F, '/orange/ {print NR,$1","$3}' data1 2 orange,90
行数を指定して出力
ファイルのフォーマットによっては「特定の行だけ抜き出し」たい。なんてこともよくあります。
FNRを使用して行数を指定しています。
ついでに「printfで出力フォーマットの指定」や「ARGVによる引数の表示」をしています。
%sは文字列、%02dは2桁の整数をゼロ埋めしてください。という意味になっています。
特定の行を出力
apple,shop1,100 orange,shop2,90 grape,shop3,200
potato,shop1,50 carrot,shop2,40 onion,shop3,60
$ awk 'FNR==2{++i; printf "%02d: %s %s\n", i, ARGV[i], $0}' data2-{1,2} 01: data2-1 orange,shop2,90 02: data2-2 carrot,shop2,40
特定の行から特定の行までを出力
「この行からこの行まで抜き出し」たい。なんてこともよくありますよね。
111 222 333 444 555 666 777 888 999 000
$ awk 'FNR>4&&9>FNR{print NR, $0}' data2-3 5 555 6 666 7 777 8 888
特定のカラムを足し算
「特定のカラム(金額など)を合計したい」場合はこうです。私はよく使います。
BEGINは前処理、ENDは後処理を表しています。
apple,shop1,100 orange,shop2,90 grape,shop3,200
$ awk -F, 'BEGIN {a=0} {a+=$3} END {print a}' data1 390
特定の条件の行だけで足し算
発展型で、「特定のカラムが一致するものだけで合計したい」なんていう時もあります。
例えばこんな風に2カラム目がいくつかのパターンに分かれていて、同じパターン(shop1だけ等)のやつだけで足したい場合とかですね。
apple,shop1,100 orange,shop2,90 grape,shop3,200 banana,shop3,70 peach,shop1,300 melon,shop1,1000
ifで分岐させる
泥臭いですが分かりやすいです。
$ awk -F, 'BEGIN {{s1=0, s2=0, s3=0}} {if($2=="shop1"){s1+=$3} else if($2=="shop2"){s2+=$3} else{s3+=$3}} END {print "shop1="s1", shop2="s2", shop3="s3}' data3 shop1=1400, shop2=90, shop3=270
配列をパターン名で作成する
こうするとよりオシャレかもしれません。
$ awk -F, '{a[$2] += $3} END { for (i in a) print i, a[i] }' data3 shop1 1400 shop2 90 shop3 270
スラッシュ区切りの日付をスラッシュ外して数値としてソート
フォーマットの異なる困ったデータを扱わないといけない時もあります。
例えば、同じ日付でもゼロ埋めされていたりされていなかったりするこんなデータ。
2021/1/1 2021/10/2 2021/03/30
$ sort data4 2021/03/30 2021/1/1 2021/10/2
$ sort -n data4 2021/03/30 2021/1/1 2021/10/2でもawkなら大丈夫。
$ $ awk -F/ '{printf $1} {printf "%02d", $2} {printf "%02d\n", $3}' data4 | sort -n 20210101 20210330 20211002
特定のカラムが現在日時と一致するか判定
「現在日付等を処理」することも出来ます。
例えば、今日が2021/09/13だとしてその日付が含まれる行だけを処理したい場合は
strftimeで日付を取得して、matchをかけるとRSTARTが一致箇所になります。
0でない=一致箇所があるということになるのを利用して判定しています。
2021/09/13 00:00:00 aaa 2021/09/14 00:00:00 bbb 2021/09/15 00:00:00 ccc
$ awk -F" " '{{match($1, strftime("%Y/%m/%d",systime()))} {if(RSTART!=0){print "OK "$0} else{print "NG "$0}}}' data5 OK 2021/09/13 00:00:00 aaa NG 2021/09/14 00:00:00 bbb NG 2021/09/15 00:00:00 ccc
長い1行の中から目的文字列に一致する部分だけ出力
1行の困った形式のファイルから目的の箇所だけを抜き出したい場合にも使えます。
「><部分でsplitしたデータでmatchさせてRSTARTで判定して表示」しています。
(もっと簡単に出来そうですが)
<product><name>apple</name><shop>shop1</shop><price>100</price><name>orange</name><shop>shop2</shop><price>90</price><name>grape</name><shop>shop3</shop><price>200</price></product>
$ awk '{num=split($0,p,/></)}{for(i=0;i<num;i++) {{match(p[i],"name>.*</name" )} {if(RSTART != 0) {print p[i]}} }}' data6 name>apple</name name>orange</name name>grape</name
おわりに
まだまだawkには色々な事が出来ると思いますが、
日常的によく使いそうなものを紹介させて頂きました。