講座「文系のためのawkプログラミング入門」
今回は配列について学びます。配列は、同じ種類の数値や文字列を格納する、変数の集まりです。共通の変数名のあとに[ ]をつけ、そのなかに数(これを添え字という)を入れて区別するものです。たとえば、name という配列変数をつくるとすると次のように書きます。
name[1] name[2] name[3] ---
この配列変数に値を代入するのは、普通の変数と同じように、= を使い
name[1] = "太郎" name[2] = "花子" name[3] = "次郎"
とします。又配列変数の名前は、普通の変数と同じように半角英字またはアンダーバー "_"で始まり、英数字が続きます。
配列変数をつかうメリットはどこにあるかというと、ひとつには、変数を扱うコードが簡素化されるということです。たとえば、普通の変数のやり方で人名を以下のように代入すると、
name1 = "太郎" name2 = "花子" name3 = "次郎" --- nameN = "n太郎"
この変数の内容を出力しようとするとスクリプトは
print name1 print name2 print name3 ---- print nameN
と print 命令を変数の数だけ書かなければなりませんが、配列を使った場合は
for(i = 1; i <= n; i++) print name[i]
と一回で済みます。for文をいれても二行で、これは変数の数が増えても同じです。name1、name2、などの場合は、変数が増えれば、その分だけ、print文を増やさなければなりません。スクリプトの簡素化と読みやすさの向上に大いに役立ちます。
決定的に重要なのは、配列変数においては、添え字(つまり[ ]の中の数でしたね)は、スクリプトの実行中に増やすことができます。つまり変数の数を増やすことができます。しかし通常の変数は、実行前にスクリプトの中に書き込まなければならず、スクリプトを作成する時点で変数の数を決めなければなりません。従って、データの数が決まっていない名簿などを処理する場合、もし、そこに出てくる名前を変数に収めようとすれば、配列変数を使うしかありません
例えば次の様なスクリプトを考えてみましょう。
名前と試験の点数がタブ区切りで収められた次のようなデータがあるとします。
逢沢 3 赤松 69 池田 13 石井 49 大畠 17 岡本 55 岸本 51 北川 43 西川 43 福島 76
これを、0 - 49 を不可、50 以上 合格 にわけて、それぞれ別々に、名前を出力するようなスクリプトを書くときは配列を使うのが便利です。
# script 16-1 { name[FNR] = $1 # FNR は入力するたびに増えていく point[FNR] = $2 } END{ print "----- 合格 -----" for(i = 1; i <= FNR; i++) { if(point[i] >= 50) print name[i] "\t" point[i] } print "----- 不可 -----" for(i = 1; i <= FNR; i++) { if(point[i] < 50) print name[i] "\t" point[i] } }
本処理の 1行目に注目してください。
name[FNR] = $1
FNR はファイルの入力行の番号です。1行目は 1、2行目は 2 という風に入力ファイルを読み込むたびに増加していきます。$1 は、各行の第1フィールドですから、ここは名前ですね。従ってこの配列には、以下のように学生の名前が順番に収められていきます。
name[1] = "逢沢" name[2] = "赤松" name[3] = "池田" name[4] = "石井" ---
また点数の方は次のようになります。
point[1] = 3 point[2] = 69 point[3] = 13 point[4] = 49 ---
変数を使うときは、そこに何が代入されているのかを、よく把握しておくことが大事ですが、配列変数においても、ちょっと形式が複雑なだけに、いっそうこのことは当てはまります。
また、END部のループ部分にも注目してください。
for(i = 1; i <= FNR; i++) { if(point[i] >= 50) print name[i] "\t" point[i] }
ここで、FNR に注目してください。FNR はテキストを一行読むたびに増加していきます。そして、このスクリプトはEND部にありますから、この変数が現在保持している値は、このファイルから読み込んだ行の数です。つまり、成績表の人数と等しいということになります。従って、1 から FNR まで for 文でループすることによって、全ての人の成績を処理できるようになります。
それでは、Input欄 File Output欄 Screen で、data15.txtを入力ファイルリストに登録して実行してください。結果は以下のようになるでしょう。
----- 合格 ----- 赤松 69 岡本 55 岸本 51 福島 76 ----- 不可 ----- 逢沢 3 池田 13 石井 49 大畠 17 北川 43 西川 43
もうお気づきだと思いますが、おなじみの組込み変数、$1,$2,..も実は配列変数の一種です。この場合[ ] は使わないだけで、配列変数として扱うことができます。添え字は直接 $ に続けて書かれています。
配列と密接に関係している、awkの組み込み関数、split についてみておきましょう。これは split(str,array,sep) のように引数を3つ受け取ります。動作は、区切文字sep を用いて、strを 配列array に分割し、その配列の要素数を返します。 もしsepが省略されれば、現在のフィールド・セパレータFSが用いられます。
split( str, array, sep)
str :元の文字列
array :配列
sep :区切り文字
返値 :配列の要素数
どういう風に使うか、例をあげましょう。例えば、ディレクトリ名も含んだ長いファイル名を、ディレクトリ名と短いファイル名に分割できます。
# script 16-2 BEGIN{ longfname = "c:\\Program Files\\ForAwk\\ForAwk.exe" num = split(longfname,arr,"\\") for(i = 1; i <= num; i++) #配列を出力 { print "arr[" i "] = " arr[i] } }
ファイルのフルパス名は、\ で区切られています。これを引用符で囲むときは、\ を二つ重ねます。引用符の中でも、\ は特殊なメタキャラクタとみなされ、その性格を打ち消すために \ が前につけられ \\ となっています。それで"\\"を区切り文字として、split関数でフルパス名を配列変数arrに分割します。いくつに分割されたかは、split関数の返値 num で分かります。for文で 1 から num まで それぞれの配列変数の値を取り出すことができます。上のスクリプトの実行結果は下のようになるでしょう。Input欄 Null Output欄 Screen で実行してみて下さい。
arr[1] = c: arr[2] = Program Files arr[3] = ForAwk arr[4] = ForAwk.exe
たとえばあるファイルを処理しているときに、処理結果の最初に短いファイル名をつけたいという場合は以下のようにします。現在の入力ファイル名(ディレクトリも含むフルパス名)は FILENAME という組込み変数に入っているので、それに、split 関数を適用します。
# script 16-3 FNR == 1{ num = split(FILENAME,arr,"\\") shortfname = arr[num] #配列の最後の要素がファイル名 print shortfname } { print $0}
このスクリプトで、上で使ったdata15.txtを処理してみましょう。Input欄 File Output欄 Screen 入力ファイルリストに data16.txt を登録して実行してみてください。結果は次のようになるでしょう
data16.txt # この行が出力される。 逢沢 3 赤松 69 池田 13 石井 49 大畠 17 岡本 55 岸本 51 北川 43 西川 43 福島 76
今回は配列について見てきましたが、次回はawk独自の連想配列について学びます。
(注)配列変数は、多くのプログラミング言語でサポートしている変数形式であるが、通常は、配列変数の数を最初に決めて使うことが多い(変数の必要な数が不明の場合は、だいたいのところを予想して、不足すれば、その度に増やす等の処理を必要とする)。awkの場合は、初めに決める必要はなく、必要になるたびに添え字を増やして使えばよい。実に簡単につかえるようになっている。