講座「文系のためのawkプログラミング入門」
今回は繰返しの文、ループ構造の続きです。for文を学びます。
前回の最初のwhile文を再掲しますと以下のようでした
# script 13-1 BEGIN{ i=1 while (i <= 10) { sum = sum + i i++ } print "1から10までの合計は " sum }
この文では、ループを継続するかどうかの判定は、変数 i の値によって行われます。そしてこの変数 i は最初に1という値で初期値を与えられ、条件式の判断に用いられ、while の実行文のなかで1ずつインクリメント(増加)されます。ループ構造は、一般的に、ある変数に着目して、初期化、判定、インクリメント(またはデクリメント、減少)というこの三つの作業をともなうものですが、これらをひとつの文の中に詰め込んだのがfor文です。以下のような形式になります。
for (変数の初期化 ;変数に関する条件式; 変数のインクリメント或はデクリメント) { 処理 }
上の13-1 のスクリプトのwhile文 をfor文を使って書きなおすと以下のようになります。
# script 14-1 BEGIN{ for (i = 1; i <= 10; i++) { sum = sum + i } print "1から10までの合計は " sum }
このスクリプトでは、まず変数 i に初期値1が代入され、次に条件式をみたすかどうか検査されます。条件式をみたしておれば、本体の実行文が実行され、最後に変数 i がインクリメントされます。インクリメントされた i が条件式を満たしていれば、実行文が実行され…と、以下条件式を満たさなくなるまで繰り返されます。実行文がひとつだけの場合は { } が省略されるのは while 文と同様です。また while 文と違うところは、処理の中では、i のインクリメントは行なわれていないということですね。また変数に関する条件式は、上では、<= 大または等しい が使われていますが、不等号だけの場合もあります。
もしインクリメントを2ずつ行いたいというときは、for文は以下のようになります。
# script 14-2 BEGIN{ for(i = 1; i <= 10; i = i+2) print "i = " i }
さらに、インクリメントではなくデクリメント(減少)させる例を示しましょう。
# script 14-3 BEGIN{ for(i = 10; i >= 1; i--) print "i = " i }
それぞれ、Input欄 Null Output欄 Screen として実行し結果を確認してください。
for文は繰返し条件が数値だけなので、while 文の特殊なものと考えることができます。while 文の場合は、繰返しの条件は数値だけでなく文字列の比較も使えるので、応用範囲がはるかに広いということができます。
while 文にしても for 文にしても、条件式が満たされなくなるまでループを実行しますが、繰返しの途中で抜け出したくなるときもあります。またある場合にはループの処理の一部を省略しないといけないときもあります。そんなときに使われる命令が、break と continue です。
breakは現在実行中の while や for のループのすぐ外側にでます。continueは、while 文の場合はcontinue文が置かれた場所より後ろの実行文を省略し、ただちに条件判定にもどります。for 文の場合も、continue文が置かれた場所より後ろにある実行文を省略し、インクリメント(あるいはデクリメント)を実行し、条件判定にもどります。
たとえば、ファイルの先頭から10行目まで、または、行頭が「■■」ではじまる行まで読み飛ばして残りを出力したいときのスクリプトです。データは次のような、data14.txtです。
不要な部分 不要な部分 不要な部分 不要な部分 不要な部分 ■■ これも不要 本文1 本文2 本文3 本文4 本文5
スクリプトは次のようになります。
# script 14-4 FNR==1{ for(i = 1;i < 10; i++) { if ($0~/^■■/) { getline #冒頭に■■が来たので次の行を読み break #ループを抜ける } getline } } {print}
データを編集して、■■の行を前後にずらして、うまく実行されているかどうか確認してください。
上のスクリプトで FNRは、処理している入力行が、処理中のファイルの何行目にあたるのかをあらわす組み込み変数でしたね。FNR==1 とパターン部で指定すれば、ファイルの一行目を読んだときに、上の処理が実行されます。そして、getline は次の入力行を読み込めといういう命令です。これでさらに次の行を繰返し読んでいきますが、「■■」で始まる行を読んだとき、もう一行読んで、このforのループを抜けます。この後は、最後に読んだ行から、各行毎に、つぎのアクション{print}が実行されます。
次のような、文字列と数が入り混じったテキストdata14-2.txtがあります。ここから純粋な数、即ち数字の連続したもの以外を、引用符「"」で括り、コンマ区切りで出力します。
tomato トマト 100 300 りんご 林檎 57 林 たまご 909 糸 国会 350 189 雪 43 財布 自動車 567 098 学生 選挙 911 市場 apple 4000 最初 動物園
スクリプトは次のようになります。スクリプト中 NF は入力行をフィールドに分割したときのフィールドの数を表す組込み変数です。$NF で最後のフィールドを表すことになります。
# script 14-5 { s = "" i = 0 while (i < NF) { i++ if($i ~ /^[0-9]+$/) { continue } s = s "\"" $i "\"," } print substr(s,1,length(s)-1) # 最後のコンマを削除して出力 }
ここで $i は 各行の i 番目のフィールドを表します。$i が数字の連続であるかどうかを判定するのは
if($i ~ /^[0-9]+$/)
という if文です。復習しておくと「~」は正規表現にマッチするかどうかを問う演算子でした。「/」で囲まれた文字列が正規表現で、[0-9]は 0から9までの文字をあらわし、そのうしろの +記号は、前の文字がひとつ以上続くことを意味します。ここでは 0 から 9 までの数字がひとつ以上続くことを意味します。 ^ と $ はそれぞれ文字列の先頭、末尾を表します。先頭から末尾まで、数字ということです。
"\"" は引用符「"」を文字として扱う際の正規表現の記法でしたね。文字(列)はすべて引用符で囲みますが、引用符自体を文字として扱いたいときは「\」という記号を前につけなければいけません。従って「"\""」となります。「\」は正規表現で使われるメタキャラクタという特殊な文字に属します。
while と for を使った繰返し、ループの構造を見てきましたが、実はawkの本処理の部分自体がループになっています。一行ずつテキストを読み込むことを繰り返しているわけです。次回は、この本処理の部分のループを制御する方法を見ておきます。