これは何
タイトルのとおり。23 年もずっと使い続けているスクリプト言語は awk ぐらいしかないかも。
プログラムファイルの分割
コマンドマンドラインオプション -f
(--file
) は複数与えられる。gawk のどのバージョンからなのか、など調べていませんが、今日初めて知りました。
ということは、プログラムファイルを分割することができますね。
もちろんネームスペースとかスコープとかカプセル化とか、そういう概念とは無縁でしょうから、単に一本のプログラムファイルに書いていたものを管理しやすいように分割するだけです。
まあ、よく使う関数群を一つのプログラムファイルに書いておいて (そのファイルは function
定義がいっぱい書いてある) あちこちから使うというのがいちばんイメージしやすいですね。ときおり BEGIN
ブロックが巨大化するときがありますが、そういうときも別ファイルに括りだしておいた方がスクリプトの見通しはよくなりますね。
処理対象ファイルごとの分岐
awk -f test.awk test1.csv test2.csv
みたいに複数のファイルを処理対象にして、なおかつ、その処理対象ファイルごとに違う処理をしたい場合、プログラムファイル内で FILENAME
を基に分岐する処理を描くわけです。普通に書けば、
FILENAME == "test1.csv" {
...
}
とかそういうパターンを書くのでしょうが、FILENAME
がフルパスだったりすると比較する部分がもっと長くなるし、そもそも FILENAME
というのも長いし、もうちょと短く書けないかな、と考えて、さっきこういうのを思いつきました。
FNR == 1 {
fname = gensub(/^.*\/([^\/]+)\.csv$/, "\\1", 1, FILENAME);
delete m;
m[fname] = 1;
}
m["test1"] {
# test1.csv に関する処理
}
m["test2"] {
# test2.csv に関する処理
}
ミソは、m[***]
がどれか一つだけ真 (1
) になることを利用して、パターンが短く書ける、というところですね。
これだとプログラムファイルが散らかる可能性があるので、たとえば、test1.csv の処理に特化した部分と test2.csv の処理に特化した部分を、さきに書いたように分割すればいいのかな、と。
例
FNR == 1 {
fname = gensub(/^.*\/([^\/]+)\.csv$/, "\\1", 1, FILENAME);
delete m;
m[fname] = 1;
}
function stderr(msg) {
print msg > "/dev/stderr";
}
m["test1"] && FNR < 3 {
stderr(sprintf("[%s:%03d] %s", fname, FNR, $0));
}
m["test2"] && FNR % 2 == 0 {
stderr(sprintf("[%s:%03d] %s", fname, FNR, $0));
}
こんな感じにしといて、
[takeyuki@jupiter ~]$ `(echo -n "awk "; ls -1 test*.awk | while read f; do echo -n "-f $f "; done; echo /var/test/*.csv)`
と実行するとか (↑は -f testmain.awk -f testsub.awk ...
のようにすべての test○○.awk
に -f
を前置して awk
のコマンドラインオプションとして渡し、/var/test/
直下の CSV ファイルを全部処理させます。あ、testmain.awk は test1.awk なんかより先に読み込ませないと駄目だね)。
追記 2017-02-02
m["***"]
で分岐するって話、処理対象ファイルごとに分岐するときだけでなく、一つのファイルを処理するときに途中でモードが変わるケースにも使えますね。たとえば、なにがしかの方法でマークアップされたファイルがヘッダ、メイン、フッタに分かれるときに m["header"]
や m["footer"]
を定義する、みたいな。