第13回スクリプトの実行順序の制御(その4)… while 文

講座「文系のためのawkプログラミング入門」

今回は繰返しの構文、ループ構文について学びます。これには、while文と、for文の二種類ありますが、まずwhile文から説明します。

I while文

1) while文の基本

while文は、ある条件が成り立つ限り、繰返し、同じ処理を実行します。形式は

while(条件式) 
{ 
	処理
}

と、次のような、do を使うものの二つがあります。

do{
	処理
}while(条件式)

最初のものをwhile文、2番目をdo-while文と呼ぶことにします。いずれも条件式が真である限り、処理が繰返し実行されます。二つの違いが分かるでしょうか? while 文は処理の前に条件の判定を行うため、場合によっては一度も処理が実行されないことがあります。これに対して、do-while文は処理のあとに条件判定があるため、少なくとも一回は必ず実行されるという点です。処理の目的によって使い分けます。又二つとも、処理が実行文ひとつだけの場合は、{ }を省略できるのは if 文と同じです。
ここではwhile文の実例を挙げましょう。次のスクリプトは、1から10までの数を加算して合計をだします。

# script 13-1

BEGIN{ 
      i=1 
      while (i<=10) 
      { 
        sum = sum + i 
        i++ 
      } 
      print "1から10までの合計は " sum 
}

このスクリプトで、繰返しの条件は、i が 10 以下ということです。i が 10 以下の間はこの繰返しが行なわれます。処理の中でやっていることは分かりますね。sum に i を加えていきます。i は最初 1 から始まり、1回処理を行なうたびに 1 ずつ増加していきます。だから 10回繰り返した後で i は 11 になり、条件式は成り立たなくなりますから、繰返しは終了します。

このスクリプトで、もし i++ というインクリメントの式がなければどうなるでしょうか?awkは文句もいわずに実行しますが、i はいつまでたっても 1 のままなので、この繰返しは終わりません。無限ループに陥ります。そのようなスクリプトは明らかに誤りですが、ただ文法的な誤りではないため、awkは何も警告してくれません。無限ループに陥るとawkを強制終了せざるを得なくなります。繰返しの構文で恐いのは、このような無限ループで永遠に回りつづけるスクリプトを書いてしまうことです。awkの文法チェックにはひっかかりませんから要注意です。

もうひとつ、学校でのテストの平均点をだす例を考えてみましょう。5人の生徒が数学の練習問題をやります。ただし、欠席した場合はテストを受けていないので、全員のテストの回数にはぱらつきがあります。下の、data13.txt が成績表です。最初に名前があり、タブ区切りで点数が並んでいます。

data13.txt(ダウンロードできます)
A君	68	75	80	91	67	56
B君	56	45	67	80	40
C君	80	77	89	95	72	90
D君	40	35	50	65	54	
E君	82	56	90	35

もしこのデータで、全ての人のテストの回数が6回であれば、平均点をだすには

($2 + $3 + $4 + $5 + $6 + $7) / 6

というスクリプトで求められます。しかし、回数がそれぞれ異なるときには、困りますね。こんなときには、while文かdo-while文を使う必要があります。名前と平均点( 小数点以下は切り捨て )だけを出力するスクリプトを考えてみましょう。

# script 13-2
BEGIN{
	print "氏名\t平均点"   #表の見出しです
}
{
	sum = 0
	i = 2
	while(i <= NF)
	{
		sum = sum + $i
		i++
	}
	kaisu = NF - 1            # 試験の回数
	heikin = int(sum / kaisu) # 平均点の少数以下は切り捨て
	print $1 "\t" heikin
}

うまく実行されるか試してみましょう。data13.txt を ダウンロードし、入力ファイルリストにドラッグ&ドロップし、Input欄 File Output欄 Screen で実行してみてください。
スクリプトで、$i は i番目のフィールドを表します。NF はフィールドの数を表す、組込み変数ですね。名前のフィールドを除くと試験の回数は NF - 1 となります。int 関数は、少数以下を切り捨てる組込み関数ですね。ここでは、kaisuや heikin という変数を使用していますが、これらを使わないで print文を下のように書いてもいいですね。

print $1 "\t" int(sum / NF - 1)

2) 応用、getline と組み合わせて使う

while文や、次に見る for 文のループ構文のもうひとつの典型的な使い方は、getline と組み合わせて、入力をアクションの中で制御するものです。入力に関しては、スクリプトでなにも書かなくても awk が一行ずつ読み込んでくれますが、getline 命令を使うと、新たに一行を読み込ませることができます。そのときには、組込み変数 FNR(ファイル単位での読み込み済み行数)や NRawkを起動したときからの延べで計算した読み込み行数)もインクリメント(一つ増えることでしたね)されるし、$1,$2,..で表されるフィールドもセットされます。例えば、たくさんのファイルの冒頭の部分だけを削除したいとか、ファイル中の特定の部分だけを特別に処理する場合にはこういうテクニックを使います。

筆者は、この講座の原稿をまず普通のエディタで書いています。そして、それをawkを使って、様々なタグを埋め込みHTMLに変換しています。もちろん細かい修正はあとで手で行なう必要がありますが、ほとんどの部分はawkで処理できます。 例えば簡単な例を挙げましょう。HTMLでは、長い引用文など、地の文とは区別して表示したい段落や文章があるとき、

<BLOCKQUOTE> ---- </BLOCKQUOTE>

というタグを使います。こうすると、このタグで囲まれた部分は、通常の文章よりも全体が字下げして表示されます。原稿を書いているときにこういうタグを埋め込んでしまうこともできますが、それをすると、文章を推敲するときにきわめてわずらわしいことになります。そこで目立たない形でこのタグを埋め込む場所に印をつけます。実際には、行頭に次のような記号を埋めます。

=EB=
=EE=

awkで処理するときは、行頭に =EB= が来たら、<BLOCKQUOTE> というタグを出力し、そこから while文に入って、行頭に =EE= という文字列が現れるまで、getline で読み込み、そのまま print で出力します。=EE= という文字列が現れると、ループを抜けて、後ろのタグ </BLOCKQUOTE> を出力するわけです。実際には、このページのソースを見てもらえば分かるように単に<BLOCKQUOTE>タグだけではなく、同時に他のタグもたくさん埋め込んでいますが、原理は同じ事です。こういう処理をするスクリプトを書いてみます。 data13-2.txt が原稿だとします。

data13-2.txt(ダウンロードできます)
これは、タグ埋め込みテストです。
ここはまだ地の文です。
=EB=
ここから始めて、
いろいろ
あれこれ
もろもろ
ここまでを字下げします
=EE=
もとの地の文にもどりました。
地の文が続いています。
地の文の終りです。

これを処理して、HTMLに変換するスクリプトは次のようになります。

{
	if($0 ~ /^=EB=/)            # 行頭に =EB= が来れば
	{
		print "<BLOCKQUOTE>"    # "<BLOCKQUOTE>"を出力
        
		getline                 # 次の行を読み込む
        
		do{
			print $0 "<BR>"     # HTML では改行するとき<BR>タグが必要
            
			getline
            
		}while($0 !~ /^=EE/)    # =EE= が現れるまでループ
        
		print "</BLOCKQUOTE>"
	}
	else  		 # 行頭に =EB= が来なければ、そのまま <BR>タグをつけて出力
	{
		print $0 "<BR>"
	}
}

これで必要なタグをつけることができますが、HTML文書は、冒頭と末尾に <HTML> </HTML> また本文の初めとおわりに<BODY> </BODY> タグが必要なので、それらをBEGIN部とEND部で追加します。スクリプトは全体で次のように成ります。


# script 13-3

BEGIN{
	print "<HTML>"                      #HTML文書の始まりを示す
	print "<TITLE> テスト </TITLE>"     # ウィンドウタイトルもつけましょう。
	print "<BODY>"			    # 表示される文章の始まり
}
{
	if($0 ~ /^=EB=/)                    # 行頭に =EB= が来れば
	{
		print "<BLOCKQUOTE>"
		getline                     # 次の行を読み込む
		do{
			print $0 "<BR>"     # HTML では改行するとき<BR>タグが必要
			getline
		}while($0 !~ /^=EE/)
		print "</BLOCKQUOTE>"
	}
	else  			# =EB= でなければ、そのまま <BR>タグをつけて出力
	{
		print $0 "<BR>"
	}
}
END{
	print "</BODY>" 		    # 本文の終りを示すタグ
	print "</HTML>"			    # HTML文書の終りを示すタグ
}

実際にうまくいくか実行してみましょう。おーくの友だちの設定は、Input欄 File Output欄 File( 単一上書 ) として、新規ボタンで出力ファイル名を「13-3.html」などと指定します。出力されたら、それをブラウザで開いて見てください。
この講座のような、同じ形式でたくさんのHTML文書を作成するような場合、awkは大きな威力を発揮します。色んなHTMLエディタがありますが、工夫次第、スクリプト次第でawkは見事なHTML変換器になります。
次回は while 文と同じく、やはり繰返しをあらわす for 文を検討しましょう。


前回 次回 目次 Home