共通テーマ: 実例でわかる awk: 第 2 回

レコード、ループ、および配列

第 1 回の awk の紹介に引き続き、Daniel Robbins 氏に、awk という不思議な名前を持つすばらしい言語について説明していただきます。複数行レコードの処理方法、ループ構成体の使用法、および awk 配列の作成方法と使用法を紹介します。この記事を読めば、awk の幅広い機能に精通し、独自の強力な awk スクリプトを作成できるようになるでしょう。

Daniel Robbins, President/CEO, Gentoo Technologies, Inc.

ニューメキシコ州アルバカーキに住む Daniel Robbins 氏は、Gentoo Technologies, Inc. の社長兼 CEO であり、PC 拡張版 Linux である Gentoo Linux、および Linux 版次世代ポート・システムである Portage の作成者です。また、Macmillan 書籍の Caldera OpenLinux Unleashed、SuSE Linux Unleashed、および Samba Unleashed の共同執筆者でもあります。彼は、小学 2年のとき初めて Logo プログラム言語や、潜在的に危険な Pac Man に出会って以来、何らかの形でコンピューターに関係してきています。これで、彼がなぜ SONY Electronic Publishing/Psygnosis でリード・グラフィック・アーチストを務めていたかが分かるでしょう。彼は妻 Mary さんや生まれたばかりの娘 Hadassah さんと過ごす時間を大切にしています。



2001年 1月 01日

複数行のレコード

awk は、システムの /etc/passwd ファイルなどの構造化データを読み取って、処理するためのすばらしいツールです。/etc/passwd は、UNIX ユーザー・データベースであり、既存のすべてのユーザー・アカウントやユーザー ID を含め、重要な多くの情報が格納された、コロンを区切り文字とするテキスト・ファイルです。第 1 回 では、awk がどれほど簡単にこのファイルを構文解析できるかを紹介しました。FS (フィールド区切り文字) を ":" に設定しさえすれば、後は何も行う必要がありませんでした。

FS 変数を正しく設定すれば、行あたりのレコード数が 1つである限り、ほとんどのすべての種類の構造化データを解析するように awk を構成することができます。ただし、複数行に渡るレコードを解析したい場合には、FS を設定するだけでは済みません。この場合には、RS レコード区切り文字変数も変更する必要があります。RS 変数は、現在のレコードが終了し、新しいレコードが開始されることを awk に伝えます。

例として、連邦証人保護プログラム (Federal Witness Protection Program) の参加者の住所リストを処理する作業の扱い方について説明しましょう。

Jimmy the Weasel
100 Pleasant Drive
San Francisco, CA 12345
Big Tony
200 Incognito Ave.
Suburbia, WA 67890

理想的には、awk は、3 行をそれぞれ別々のレコードとして認識するのではなく、3 行からなる住所全体を 1つの独立したレコードとして認識する必要があります。awk が、住所の 1 行目を第 1 フィールド ($1) として、町名と番地を第 2 フィールド ($2) として、都市と州の名前および郵便番号を第 3 フィールド ($3) として認識すれば、コードはかなり簡単になります。次に、希望どおりのことを実行するコードを示します。

BEGIN {
	FS="\n"
	RS=""
}

このコードでは、FS を "\n" に設定しているので、awk は各フィールドがそれぞれ別々の行に表示されていることを認識します。また、RS を "" に設定しているので、awk は各住所レコードがブランク行で区切られていることも認識します。awk は、入力がどのようにフォーマットされているかを把握すると、すべての構文解析作業を自動的に実行してくれるので、残りのスクリプトは簡単です。では、この住所リストを構文解析し、各住所レコードを 1 行に出力し、各フィールドをコンマで区切る、スクリプト全体を見てみましょう。

address.awk
BEGIN {
	FS="\n"
	RS=""
}
{
	print $1 ", " $2 ", " $3
}

このスクリプトを address.awk として保存し、住所データを address.txt というファイルに格納している場合に、このスクリプトを実行するには、"awk -f address.awk address.txt" と入力します。このコードは、次の出力を生成します。

Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345
Big Tony, 200 Incognito Ave., Suburbia, WA 67890

OFS と ORS

address.awk の print 文では、awk が 1 行に連続して置かれているストリングを連結 (結合) することが分かります。この機能を使用して、1 行に表示する 3つの住所フィールドの間にコンマとスペース (", ") を挿入しました。この方法でもうまくいきますが、見栄えがあまりよくありません。フィールドの間にリテラルの ", " ストリングを挿入する代わりに、OFS という特殊な awk 変数を設定すれば、awk で同じことを自動的に実行することができます。このコード部分を見てみましょう。

print "Hello", "there", "Jim!"

この行上のコンマは、実際のリテラル・ストリングの一部ではありません。awk は、このコンマによって "Hello"、"there"、および "Jim!" が別々のフィールドであり、OFS 変数をストリングとストリングの間に出力しなければならないことを認識します。デフォルトでは、awk は次の出力を生成します。

Hello there Jim!

つまり、デフォルトでは、OFS は " " (単一のスペース) に設定されることが分かります。ただし、awk が希望どおりのフィールド区切り文字を挿入するように OFS を簡単に再定義することができます。次に、OFS を使用してフィールドとフィールドの間に ", " ストリングを出力する、address.awk の改訂バージョンを示します。

address.awk の改訂バージョン
BEGIN {
	FS="\n"
	RS=""
	OFS=", "
}
{
	print $1, $2, $3
}

awk には、ORS (Output Record Separator: 出力レコード区切り文字) という特殊変数もあります。ORS (デフォルトでは改行 ("\n")) を設定することで、print 文の最後に自動的に出力する文字を制御することができます。デフォルトの ORS 値では、awk は新しい各 print 文を出力するときに改行を実行します。ダブル・スペースで出力したい場合は、ORS を "\n\n" に設定します。また、レコードを単一のスペース (および改行なし) で区切りたい場合は、ORS を " " に設定します。


複数行からタブ付き行への変換

スプレッドシートにインポートするために、住所リストを、レコードごとに 1 行の、タブ区切り形式に変換するスクリプトを作成したとしましょう。address.awk を少し変更したバージョンを使用すると、このプログラムが 3 行からなる住所の場合しか動かないことが明らかになりました。awk が次の住所を見つけると、4 行目は破棄され、出力されません。

Cousin Vinnie
Vinnie's Auto Shop
300 City Alley
Sosueme, OR 76543

このような状況を処理するには、フィールドあたりのレコード数を考慮して、各レコードを順番どおりに出力するとよいでしょう。現時点のコードでは、住所の上から 3 つのフィールドしか出力されません。次に、希望どおりのことを実行するコードを示します。

任意のフィールド数からなる住所に対応する address.awk のバージョン
BEGIN { 
    FS="\n" 
    RS="" 
    ORS="" 
} 
 
{  
        x=1 
        while ( x<NF ) { 
                print $x "\t" 
                x++ 
        } 
        print $NF "\n" 
}

まず、フィールド区切り文字 FS を "\n" に設定し、レコード区切り文字 RS を "" に設定して、awk が前と同じように複数行の住所を構文解析するようにします。次に、出力レコード区切り文字 ORS を "" に設定し、print 文が各呼び出しの最後に改行を出力しない ようにします。つまり、テキストを新しい行から始めたい場合には、明示的にprint "\n" を書き込む必要があります。

メインのコード・ブロック内に、処理している現在のフィールドの数を保持する変数 x を作成します。初期値は 1 に設定します。次に、while ループ (awk ループ構成体は、C 言語のループ構成体と同じです) を使用して、最後のレコード以外のすべてのレコード内を反復し、レコードとタブ文字を出力します。最後に、最後のレコードとリテラル改行を出力します。繰り返しますが、ORS は "" に設定されているので、print で自動的に改行が出力されることはありません。希望したとおりのプログラム出力は、次のようになります。

希望どおりの出力。見栄えはよくないが、タブで区切られているのでスプレッドシートに簡単にインポートできる。
Jimmy the Weasel        100 Pleasant Drive      San Francisco, CA 12345 
Big Tony        200 Incognito Ave.      Suburbia, WA 67890
Cousin Vinnie   Vinnie's Auto Shop      300 City Alley  Sosueme, OR 76543

ループ構成体

awk の while ループ構成体が C 言語のループ構成体と同じものであることは、すでに紹介しました。awk には、標準の while ループのようにコード・ブロックの先頭ではなく、最後で条件を評価する "do...while" というループもあります。これは、他の言語で言えば "repeat...until" ループに相当します。次に例を示します。

do...while example
{
	count=1
	do {
		print "I get printed at least once no matter what" 
	} while ( count != 1 )
}

条件はコード・ブロックの最後で評価されるので、"do...while" ループは、標準の while ループと異なり、少なくとも必ず 1 回は実行されます。一方、標準の while ループは、ループが最初に見つかったときに条件が偽であると、絶対に実行されません。

for ループ
awk では、for ループを作成することができます。これは、while ループと同様、C 言語の for ループと同じです。

for ( initial assignment; comparison; increment ) {
	code block
}

次に、簡単な例を示します。

for ( x = 1; x <= 4; x++ ) {
	print "iteration",x
}

このコード部分では、以下が出力されます。

iteration 1
iteration 2
iteration 3
iteration 4

break と continue

繰り返しますが、C と同じように、awk には break 文と continue 文が用意されています。これらの文を利用して、awk のさまざまなループ構成体を細かく制御することができます。次に、break 文が使用できないと困ってしまうコード部分を示します。

無限の while ループ
while (1) {
	print "forever and ever..."
}

1 は常に真なので、この while ループは永久に実行されます。次に、10 回しか実行されないループを示します。

break 文の例
x=1
while(1) {
	print "iteration",x
	if ( x == 10 ) {
		break
	}
	x++
}

ここでは、内側ループから「抜け出る」ために break 文を使用しています。"break" により、ループは直ちに終了し、実行はループのコード・ブロックの次の行から続行されます。

continue 文は、break を補うもので、次のように働きます。

x=1
while (1) {
	if ( x == 4 ) {
		x++
		continue
	}
	print "iteration",x
	if ( x > 20 ) {
		break
	}
	x++
}

このコードは、"iteration 4" を除く、"iteration 1" から "iteration 21" までを出力します。iteration が 4 の場合、x は増分され、continue 文が呼び出されて、それにより awk はコード・ブロックの残りを実行せずに次のループ反復に移動して開始します。continue 文は、break と同じように、あらゆる種類の awk の反復ループで使用できます。for ループの本体で使用した場合、continue はループ制御変数を自動的に増分します。次に、同じ結果になる for ループを示します。

for ( x=1; x<=21; x++ ) {
	if ( x == 4 ) {
		continue
	}
	print "iteration",x
}

for ループはx を自動的に増分するので、while ループのように、continue を呼び出す直前にx を増分する必要はありません。


配列

うれしいことに、awk には配列があります。ただし、awk では、配列指標は 0 ではなく 1 から始まるのが通例です。

myarray[1]="jim"
myarray[2]=456

awk は、最初の代入を見つけると、myarray を作成して、要素myarray[1] を "jim" に設定します。2つ目の代入を評価した後、配列内の要素は 2つになります。

配列上での反復

定義が終わった後、awk には、次のように、配列の要素の上を反復する便利なメカニズムがあります。

for ( x in myarray ) {
	print myarray[x]
}

このコードは、配列myarray 内の各要素をすべて出力します。for ループのこの特殊な "in" 形式を使用した場合、awk は、myarray の既存のすべての指標を順番に x (ループ制御変数) に代入し、それぞれの代入後にループのコード・ブロックを実行します。これは非常に便利な awk の機能ですが、欠点が 1つあります。awk が配列指標内をサイクルするとき、特定の順序に従わないという点です。つまり、前述のコードの出力が次のどちらになるかはまったく分かりません。

jim
456

または

456
jim

フォレスト・ガンプ風に分かりやすくたとえるならば、配列の内容上を反復することは、チョコレート・ボックスのようなもの、つまり、何が出てくるかまったく分からない、ということです。これは、次に説明する、awk 配列の「ストリング指向性」に関係があります。


配列指標のストリング指向性

第 1 回では、awk が実際にストリング・フォーマットで数値を格納することを示しました。awk は、この作業に必要な変換を実行しますが、少し奇妙なコードになります。

a="1"
b="2"
c=a+b+3

このコードの実行後、c6 に等しくなります。awk は「ストリング指向」なので、ストリング "1" と "2" を追加することは、機能上は数値 1 と 2 を追加することとまったく同じです。どちらの場合も、awk は正常に算術演算を実行します。awk の「ストリング指向」の性質は少し変わっています。配列にストリング指標を使用した場合はどうなるのでしょうか。たとえば、次のようなコードがあるとします。

myarr["1"]="Mr. Whipple"
print myarr["1"]

ご想像のとおり、このコードは "Mr. Whipple" を出力します。しかし、2つ目の "1" 指標の前後に引用符を付けないと、どうなるでしょうか。

myarr["1"]="Mr. Whipple"
print myarr[1]

このコード部分の結果を考えると、少し難しくなります。awk はmyarr["1"]myarr[1] を配列内の 2つの別々の要素と見なすのでしょうか、それとも、どちらも同じ要素を指しているのでしょうか。答えは、どちらも同じ要素を指している、です。したがって、awk は、1つ目のコード部分と同じように "Mr. Whipple" を出力します。奇妙に思われるかもしれませんが、awk は陰で常に配列にストリング指標を使用します。

この奇妙な事実を学習すると、中には次のようなおかしなコードを実行しようとする人もいるかもしれません。

myarr["name"]="Mr. Whipple"
print myarr["name"]

このコードは、エラーを発生させないだけでなく、機能的には前述の例と同じで、前とまったく同じように "Mr. Whipple" を出力します。お分かりのように、awk では、純粋な整数指標しか使用できないわけではありません。問題を起こさずに、必要に応じてストリング指標を使用することもできます。myarr["name"] のような整数以外の配列指標を使用する場合は常に、連想配列 を使用していることになります。技術上、ストリング指標を使用する場合も awk が陰で実行することはまったく同じです (「整数」指標を使用する場合でも、awk はそれをストリングとして処理するからです)。ただし、こうした配列のことを連想配列 と呼ぶことにします。いかにもかっこいい感じで、上司を感動させることができるでしょう。ストリング指向の指標だということは、ここでの内緒の話にしておきましょう。


配列ツール

配列という話になると、awk は高い柔軟性を発揮します。ストリング指標を使用できるため、指標を連続した数のシーケンスにする必要はありません (たとえば、myarr[1]myarr[1000] を定義することはできますが、他の要素はすべて未定義のままで構いません)。これは非常に役立つこともありますが、場合によっては混乱を招くこともあります。幸いなことに、awk には、配列の管理に役立つ便利な機能がいくつか用意されています。

まず、配列の要素を削除することができます。配列fooarray の要素1 を削除したい場合は、次のように入力します。

delete fooarray[1]

また、特定の配列要素が存在するかどうかを確認したい場合は、次のように特殊な "in" というブール演算子を使用することができます。

if ( 1 in fooarray ) {
	print "Ayep!  It's there."
} else {
	print "Nope!  Can't find it."
}

次回

今回は、さまざまな分野についてお話ししました。次回は、awk の知識の締めくくりとして、awk の演算関数とストリング関数の使用法と、独自の関数の作成方法を紹介します。また、小切手帳残高計算プログラムの作成も紹介します。そのときまで、ぜひ独自の awk プログラムを作成し、次の参考文献を参照しておきましょう。

参考文献

  • developerWorks の実例でわかる awk 第 1 回を参照してください。
  • もし古典的な本がお好みなら、O'Reilly 社から出版されているsed & awk, 2nd Edition をお勧めします。
  • comp.lang.awk FAQ も忘れずに調べるとよいでしょう。ここにも、さらにたくさんの awk リンクがあります。
  • Patrick Hartigan 氏のawk tutorial には、便利な awk スクリプトがまとめられています。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=226666
ArticleTitle=共通テーマ: 実例でわかる awk: 第 2 回
publish-date=01012001