AWKのトリッキーな配列&連想配列の仕組み・動作と目からウロコのテクニック
► 「シェル芸」に効く AWK処方箋 最終回 (月刊『USP MAGAZINE 2014 Semtember (Vol.17)』より転載)
今回は配列について学びます。通常の言語と異なり、AWKで用いられる配列はすべて連想配列として扱われます。とはいえ、連想配列でありながら、通常の配列と同じように扱える側面があるために、他のプログラミング言語を習得している方で違和感を覚える方もいるでしょう。これはAWKの作者たちが、配列と連想配列を同じように扱えるように工夫した歴史的な経緯によるものです。それでは、少し変わったAWKの配列を勉強していきましょう。
配列と連想配列
「配列」とは、インデックスが数字であるものを称し、「連想配列」とはインデックスが文字列であるものを指します。他の言語であれば、連想配列はハッシュや辞書と呼ばれますが、AWK界隈では連想配列と呼ばれています。配列の役目は「○○番目のものが○○である」という指定を行うことです。したがって、次のようにApple、Orange、Bananaというデータに対し、この通りの順番で番号を割り振るような場合に配列を用います。配列であることを明確に示すために配列名に複数形を使うと分かりやすいでしょう。
BEGIN {
names[1] = "Apple";
names[2] = "Orange";
names[3] = "Banana";
}
もちろん、次のように変数で書くこともできます。
BEGIN {
name1 = "Apple";
name2 = "Orange";
name3 = "Banana";
}
しかし、変数の場合には後で呼び出すような場面で大変になります。一方、配列に入れることでfor文やwhile文で呼び出すことができます。
BEGIN {
names[1] = "Apple";
names[2] = "Orange";
names[3] = "Banana";
for (i = 1; i <= 3; i++) {
print i, names[i];
}
}
このように配列に入れておくと、いろいろと便利なことがあります。
さて、配列の個数を自動的に取得する方法はないのでしょうか? 上記の場合は「3」と決め打ちになっています。配列の個数を取得するにはlength関数を使います。
BEGIN {
names[1] = "Apple";
names[2] = "Orange";
names[3] = "Banana";
for (i = 1; i <= length(names); i++) {
print i, names[i];
}
}
このlength関数は少し特殊で、引数がない場合には$0の文字数を返し、引数が変数の場合には変数の長さを返します。引数が配列である場合には、配列の個数を返します。このようにlength関数はちょっとトリッキーな関数です。一部のAWKではバグが残っていて、うまく動作しないこともあります。
次に連想配列ですが、連想配列は「○○の○○」というイメージになります。例えば、人の身長や果物の値段などが該当します。したがって、連想配列の名前は「***_of」のようにすると分かりやすいでしょう。
BEGIN {
value_of["Apple"] = 100;
value_of["Orange"] = 200;
value_of["Banana"] = 300;
}
この連想配列を取得するには、「for ~ in」というfor文で連想配列専用の方法を用います。
BEGIN {
value_of["Apple"] = 100;
value_of["Orange"] = 200;
value_of["Banana"] = 300;
for (i in value_of) {
print i, value_of[i];
}
}
連想配列として呼び出しているので、出力される順序は何かでソートされているわけではありません。他の言語において、連想配列はハッシュと呼ばれるように、ハッシュテーブルを呼び出しているに過ぎないからです。もし、結果をソートする必要がある場合にはGNU AWKの拡張を用いるか、自前でソートするための関数を準備する必要があります。
普段、他の言語を用いている方には配列と連想配列が一緒であるというのは不思議に感じられるかもしれませんが、これは「AWKには変数の型がない」という仕様に起因するものです。したがって、配列においてはインデックスを数値で扱いましたが、文字列でも構わないのです。
BEGIN {
names["1"] = "Apple";
names["2"] = "Orange";
names["3"] = "Banana";
}
さて、この配列と連想配列ですが、AWKでは同じ連想配列です。つまり、配列の中身を連想配列として「for ~ in」で取得できます。
BEGIN {
names[1] = "Apple";
names[2] = "Orange";
names[3] = "Banana";
for (i in names) {
print i, names[i];
}
}
前述のように、length関数にバグがあって配列の個数を取得できない場合には、次のようにする必要があります。
BEGIN {
names[1] = "Apple";
names[2] = "Orange";
names[3] = "Banana";
for (i in names) {
num_names++;
}
print num_names;
}
リストがない
このようにすべてが連想配列であるということは、順番を付けるリストというものがAWKには存在しない、ということです。とはいえ、毎回1つずつ定義するのは面倒ですよね。そこで、split関数を用います。
BEGIN {
fruit_list = "Apple Orange Banana";
num_fruits = split(fruit_list, fruits);
for (i = 1; i <= num_fruits; i++) {
print i, fruits[i];
}
}
split関数は第1引数の文字列を第3引数の正規表現(第3引数がない場合には組込変数FS)で分割し、第2引数の名前を持つ配列に格納します。また、戻り値は配列の数になります。このようにすることで、疑似的にリストのようなものを作ることができます。
本連載が単行本になりました!
この記事は参考になりましたか?
- USP MAGAZINEコラボレーション連載/「シェル芸」に効く AWK処方箋連載記事一覧
-
- AWKのトリッキーな配列&連想配列の仕組み・動作と目からウロコのテクニック
- シェルで表計算ができるかも!? AWKの四則演算と数値演算関数
- AWKで一番の得意ワザ! シェルで文字列を自在に扱うための文字列関数
- この記事の著者
-
斉藤 博文(サイトウヒロフミ)
最初にAWKと出会ってから○十年、AWKの魅力に取りつかれ、勢い余って「日本 GNU AWKユーザー会」を立ち上げています。会としてOSCなどのイベントにも出展しつつ、GNU AWKの開発も手伝っています。「USP友の会」では幹事役ですが、「シェル芸勉強会」にはほぼ毎回参加して一緒に勉強しています。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア

