+===========+ |P/ECE研究室| +===========+ P/ECE研究記録 2021年 ==================== * Thu Sep 09 00:00:00 JST 2021 Naoyuki Sawa - GNU Awk 3.1.7の不具合を回避する 僕はAwkが好きです。 P/ECEのアプリ開発でデータを作る時も、仕事で簡単なテキスト処理をする時も、よくAwkを使っています。 もっとモダンなスクリプト言語も勉強したのですけれど、しばらく使わないとすぐに忘れてしまって、ついAwkとSedに戻ってしまいます…(^^; 僕が使っているAwkは、「GNU Awk 3.1.7(windows special Nov 24 2009)」(gawk-mbcs-win32-20091124.zip)です。 古いバージョンなのですけれど、もっぱらシフトJISのテキストだけを扱う上では使い勝手が良いので、重宝しています。 しかし残念な事に、ダウンロードページが公開停止中です。 (参照:「corbieのブログ」(http://blog.livedoor.jp/corbie/)さんの「Windows版gawk選び」(http://blog.livedoor.jp/corbie/archives/3924154.html)の記事) もうダウンロードできなさそうなので、大事に使って行こうと思います。 長らく使ってきて大きな問題は起きていなかったのですけれど、最近、ローカル配列を使うと不具合が発生する事に気付きました。 Awkでローカル配列を使うには、以下のようにします。 ■テスト� \犠� │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test(a) { │ a[1] = 200 │ print "呼び出し中: " a[1] │} □結果 │呼び出し前: 100 │呼び出し中: 200 │呼び出し後: 100 また、配列の末尾に値を追加して行くには、以下のようにする事が多いです。 ■テスト�◆\犠� │BEGIN { │ delete a #//確実に空の配列にする。 │ a[length(a)+1] = 200 │ a[length(a)+1] = 300 │ a[length(a)+1] = 400 │ for(i = 1; i <= length(a); i++) { │ print i ": " a[i] │ } │} □結果 │1: 200 │2: 300 │3: 400 �,皚△眄気靴�動作するのですが、�,鉢△鯀箸濆腓錣擦董▲蹇璽�ル配列の末尾に値を追加して行こうとすると、不具合が発生しました。 ■テスト�� 異常 │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test(a,i) { │ delete a #//確実に空の配列にする。 │ a[length(a)+1] = 200 │ a[length(a)+1] = 300 │ a[length(a)+1] = 400 │ for(i = 1; i <= length(a); i++) { │ print i ": " a[i] │ } │} □結果 │呼び出し前: 100 │1: │2: 300 │3: 400 │4: │5: │6: │7: │8: │9: │10: │…終わらない… 調査した所、どうやら、ローカル配列に値を格納する前にdeleteすると、配列の内部構造が壊れて、length()が不定な値を返すようです。 ■テスト�ぁ^枉� │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test(a) { │ delete a │ print "呼び出し中: " length(a) │} □結果 │呼び出し前: 100 │呼び出し中: 1804120 │呼び出し後: 100 ちなみに、グローバル配列に値を格納する前にdeleteしても大丈夫なので、上記の不具合はローカル配列の場合だけに発生するようです。 ■テスト�ァ\犠� │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test() { │ delete a │ print "呼び出し中: " length(a) │} □結果 │呼び出し前: 100 │呼び出し中: 0 │呼び出し後: 僕はこれまで、ローカル配列をあまり使わずに、グローバル配列ばかり使っていたので、たまたま不具合に遭遇していなかったようです。 ためしに、MinGW版のGNU Awk 3.1.7(gawk-3.1.7-1-msys-1.0.11)でもテストしてみた所、同じ不具合が発生しました。 どうやら、「GNU Awk 3.1.7(windows special Nov 24 2009)」に特有の不具合ではなく、元のGNU Awk 3.1.7に起因する不具合のようです。 ちなみに、「GNU Awk 4.1.4, API: 1.1」では不具合は発生しませんでした。 GNU Awk 3.1.8以降のどれかのバージョンで修正されたのだと思います。 (GNU Awk 3.1.8のChangeLogを読んでみたのですけれど、わかりませんでした…) 不具合を回避するためには、本来は、普段使いのAwkをもっと新しいバージョンに変えるべきなのです。 でも、「GNU Awk 3.1.7(windows special Nov 24 2009)」に慣れ過ぎていて、微妙な挙動が変わるのが怖くて、変えたくありません… そこで、「GNU Awk 3.1.7(windows special Nov 24 2009)」のままで、不具合を回避する方法を考えました。 ローカル配列に値を格納する前にdeleteすると不具合が発生するのですから、deleteする前にローカル配列に値を格納すれば良いのです。 ■テスト�Α\犠� │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test(a) { │ a[1] = 1 #//ダミー │ delete a │ print "呼び出し中: " length(a) │} □結果 │呼び出し前: 100 │呼び出し中: 0 │呼び出し後: 100 ■テスト�А\犠� │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test(a,i) { │ a[1] = 1 #//ダミー │ delete a #//確実に空の配列にする。 │ a[length(a)+1] = 200 │ a[length(a)+1] = 300 │ a[length(a)+1] = 400 │ for(i = 1; i <= length(a); i++) { │ print i ": " a[i] │ } │} □結果 │呼び出し前: 100 │1: 200 │2: 300 │3: 400 │呼び出し後: 100 正しい結果になりました。 忘れないようにするためには、常に関数の先頭でローカル変数を初期化するのを、定型とすれば良いと思います。 ■定型 │BEGIN { │ test(1,2,3) │} │function test(x,y,z,t1,t2,t3,a1,a2,a3) { #//x,y,zは引数。t1,t2,t3はローカル変数。a1,a2,a3はローカル配列。 │ a1[1] = 1 #//ダミー │ delete a1 #//確実に空の配列にする。 │ a2[1] = 1 #//ダミー │ delete a2 #//確実に空の配列にする。 │ a3[1] = 1 #//ダミー │ delete a3 #//確実に空の配列にする。 │ … │} 以下、補足です。 ローカル配列を使用する前のdelete自体が不要では?と思われるかも知れませんが、必要です。 ローカル配列を使用する前にdeleteしておかないと、以下のようなコードがエラーになります。 ■テスト�─.┘蕁� │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test(a,n,i) { │ n = length(a) │ a[n+1] = 200 │ n = length(a) │ a[n+1] = 300 │ n = length(a) │ a[n+1] = 400 │ for(i = 1; i <= length(a); i++) { │ print i ": " a[i] │ } │} □結果 │呼び出し前: 100 │gawk: test8.awk:9: 致命的: スカラーパラメータ `a' を配列として使用しています。 エラになる理由は、未使用の変数に対してlength()を使った時点で、その変数はスカラー変数になってしまうからです。 先にdeleteしておけば、その変数は配列変数になるので、上記のエラーは発生しません。 ■テスト�� 正常 │BEGIN { │ a[1] = 100 │ print "呼び出し前: " a[1] │ test() │ print "呼び出し後: " a[1] │} │function test(a,n,i) { │ a[1] = 1 #//ダミー │ delete a #//確実に空の配列にする。 │ n = length(a) │ a[n+1] = 200 │ n = length(a) │ a[n+1] = 300 │ n = length(a) │ a[n+1] = 400 │ for(i = 1; i <= length(a); i++) { │ print i ": " a[i] │ } │} □結果 │呼び出し前: 100 │1: 200 │2: 300 │3: 400 │呼び出し後: 100