awkスクリプトのデバック方法

ここひと月ぐらい割とawkを使うようになり、自分なりにmanを読んだり、試行錯誤したawkスクリプトのデバック方法のメモ。

環境

基本的には以下のバージョンのgawkで試している。

[mk55@localhost ~]$ awk --version | head -n 1
GNU Awk 4.0.2

gawkのデバックオプションを使う

gawk-dオプション(もしくは--dump-variables)を使うと組み込み変数を含むawkの変数の最終的な値をファイルに出力できる。

以下のようなサンプルスクリプト1_debug.awkがあったとすると

{
        baz="baz" ;
        print $1, baz ;
 }

デバックオプション-dをつけると以下のように変数の内容をダンプしたawkvars.outがカレントディレクトリに生成される

[mk55@localhost ~]$ echo "foo bar" | gawk -d -f 1_debug.awk 
foo baz

#変数のダンプファイル。
#組み込み変数とともに、最終行に定義した変数bazの値が出力されている。
[mk55@localhost ~]$ cat awkvars.out 
ARGC: 1
ARGIND: 0
ARGV: array, 1 elements
BINMODE: 0
CONVFMT: "%.6g"
ERRNO: ""
FIELDWIDTHS: ""
FILENAME: "-"
FNR: 1
FPAT: "[^[:space:]]+"
FS: " "
IGNORECASE: 0
LINT: 0
NF: 2
NR: 1
OFMT: "%.6g"
OFS: " "
ORS: "\n"
RLENGTH: 0
RS: "\n"
RSTART: 0
RT: "\n"
SUBSEP: "\034"
TEXTDOMAIN: "messages"
baz: "baz"

manにも書いてある通り、typoで変な変数を定義していたり、ループの添え字(i, j)が意図しない値になっていたりすることを検出できることがある。

なお、以下のようにファイルを指定して出力することもできる。

[mk55@localhost ~]$ echo "foo bar" | gawk -d/tmp/debug.txt -f 1_debug.awk 
foo baz
[mk55@localhost ~]$ ls /tmp/debug.txt 
/tmp/debug.txt
[mk55@localhost ~]$ head /tmp/debug.txt 
ARGC: 1
ARGIND: 0
ARGV: array, 1 elements
BINMODE: 0
CONVFMT: "%.6g"
ERRNO: ""
FIELDWIDTHS: ""
FILENAME: "-"
FNR: 1
FPAT: "[^[:space:]]+"
[mk55@localhost ~]$ 

printデバックする

AWK Users JP :: awk 基礎文法最速マスターにある通り、awkの実装によらずデバックできるのでprintデバック(もしくはprintfデバック)はよく使うと思う。 ただ、単純にprintを仕込むとデバック用途なのか、通常の用途なのかわからないので一工夫する。

以下のようなサンプルスクリプト2_debug.awkがあったとする。

{
        baz="baz" ;
        if(debug)  print "[debug] baz is :" baz  > "/dev/stderr" ;
        print $1, baz ;
 }

上のスクリプトは通常時は1_debug.awkと同じ処理をするが、変数debugが0以外で定義されている場合は標準エラー出力に対してprint文で文字列を出力する*1。 これを利用して、変数を定義できる-v(もしくは--assign)オプションを疑似的なdebug print用のオプションのように使う*2*3

#変数debugがないので通常の出力のみ
[mk55@localhost ~]$ echo "foo bar" | gawk -f  2_debug.awk 
foo baz

#変数debugが1なのでデバック出力あり
[mk55@localhost ~]$ echo "foo bar" | gawk -v debug=1 -f 2_debug.awk 
[debug] baz is :baz
foo baz

上記では変数の出力だけなのでありがたみがないが、例えばBEGINFILEENDFILE等に記載すれば、どのファイルの処理を開始/終了したか出力したりもできる。

なお、本番向けスクリプトにdebug用の記述が入るのが不快ならsedで一括置換してしまえばよい*4

[mk55@localhost ~]$ sed -i 's/if(debug)/#if(debug)/g' 2_debug.awk 
[mk55@localhost ~]$ cat 2_debug.awk 
{
        baz="baz" ;
        #if(debug)  print "[debug] baz is :" baz  > "/dev/stderr" ;
        print $1, baz ;
 }
[mk55@localhost ~]$ echo "foo bar" | gawk -v debug=1 -f 2_debug.awk 
foo baz

もし何度も上記を呼ぶような大きなスクリプトを書くなら、いっそ関数化する手もありそうではある*5

{
        baz="baz" ;
        printDebugMsg( "baz is : " baz )
        print $1, baz ;
}
function printDebugMsg(msg)
{
        if(debug) print "[debug]" msg > "/dev/stderr"
}

gawkの互換性チェックのオプションを使う

あまりないとは思うが、awkの処理を処理系の異なるawkに移植する際は、事前に--lintオプションを使って動かしておくと他の処理系で動作しなさそうな箇所を警告してくれる。 以下の例はgawkにはあり、nawkにはない関数systime()strftime()を使った処理で--lintを使ってみた場合。

[mk55@localhost ~]$ echo "now is" | gawk '{print $0 " " strftime("%Y/%m/%d", systime()) }'
now is 2017/05/28

[mk55@localhost ~]$ echo "now is" | gawk --lint '{print $0 " " strftime("%Y/%m/%d", systime()) }'
gawk: cmd. line:1: warning: `strftime' is a gawk extension
gawk: cmd. line:1: warning: `systime' is a gawk extension
now is 2017/05/28

gawk付属のプロファイラを使う

gawkにはPAWKというプロファイラがついているので、これを利用して解析することもできる。 pawkコマンドを使うか--profileもしくは-pオプションを利用すると使用できる

例えば以下のようなことができる。

  • BIGINやEND、funcitionなどを種類ごとにまとめて表示*6
  • ループ処理が何回呼ばれたか表示
  • パターンやif-else分岐が何度呼ばれたか表示

細かい利用方法は以下を読めばだいたいわかる。 The GNU Awk User’s Guide: Profiling

gawk付属のデバッガを使う

gawkにはDAWKというデバッガがついているのでこれを使うという手もある。

The GNU Awk User’s Guide: Debugging

対話的にコマンド入力したりしてブレークポイントを設定したりなどいろいろできるようだ。 ただ、私自身はデバッガが必要なほど複雑なスクリプトを作ったことはないので、いままでお世話になったことはない。 なので、正直なところ良く分からない。

日本語だとかろうじてこのページでちょっと触れられているぐらいではAWK Users JP :: 【緊急特集】最新の gawk 4.0.0 を追え!

参考

The GNU Awk User’s Guide: Indexgawkのマニュアルページ

AWK 処理系の比較 | YOSBITS ⇒nawk, gawk, mawkの比較。mawkというものがあるのは初めて知った。

Man page of STRFTIMEC言語strftime()のman。awkでも同じような動きをしているようだ。

*1:標準エラー出力に出力するのは、標準出力と区別することでawkの結果と別に出力できるようにするため。例えば、awkの結果をリダイレクトでファイルに出す場合などでも、debug printはコンソール上に表示したり、別のファイルに出力できたりする

*2:Powershellの-DebugとWrite-Debugのようなイメージ

*3:nawkでも-vオプションは利用可能

*4:個人的にはそこまで性能的に問題になることは少ないと思うので、2重に管理するぐらいなら残した方が良いと思うが

*5:awkの関数ってキャメルケースで良いのだろうか?print_debug_msgとかのほうが適切な気もするが…適切な既存の規約を見つけられなかった

*6:たとえば2つのBIGINが書かれてしまっているときの解析に便利