The state of the AWK

Ben Hoyt

AWKの現状

原文は Ben Hoyt により に公開されました。 このブログを購読する

AWKは40年以上の歴史を持つテキスト処理言語だ。POSIX標準があり、それに準拠した実装も複数存在し、2020年になった今でも驚くほど現役だ――単純なテキスト処理から「ビッグデータ」の取り回しまで幅広く使われている。先日リリースされたGNU Awk 5.1は、AWKを取り巻く状況を概観し、GNU Awkがこのところどのような動きを見せてきたのか、そして昨今AWKがどこで使われているのかを見渡す格好の機会といえる。

この言語は1977年にベル研究所で生まれた。その名前はオリジナルの作者であるAlfred Aho、Peter Weinberger、Brian Kernighanの頭文字に由来する。根っからのUnixツールであるAWKは、1つのことをうまくこなすように設計されている。すなわち、テキストの行をフィルタリングし、変換することだ。ログファイルからフィールドを解析したり、他のツールの出力を変換したり、単語やフィールドの出現回数を数えたりするのによく使われる。AhoはAWKの機能を次のように簡潔にまとめている

AWKは入力を1行ずつ読み込む。プログラム中の各パターンについて行が走査され、一致したパターンごとに対応するアクションが実行される。

AWKプログラムは、コマンドラインから直接実行されるワンライナーであることが多い。たとえば、ある架空のウェブサーバーのログからGETリクエストの平均応答時間を計算するには、次のように入力するかもしれない。

    $ awk '/GET/ { total += $6; n++ } END { print total/n }' server.log 
    0.0186667

これは次のような意味だ。正規表現/GET/に一致するすべての行について、応答時間(6番目のフィールド、すなわち$6)を合計し、行数を数える。最後に、応答時間の算術平均を出力する。

さまざまなAWKのバージョン

現在使われているAWKには主に3つのバージョンがあり、いずれも(少なくとも大多数のユースケースにおいては十分な程度に)POSIX標準に準拠している。1つ目は古典的なawkで、Aho、Weinberger、Kernighanが著書The AWK Programming Languageで解説したAWKのバージョンだ。しばしば「new AWK」(nawk)や「one true AWK」と呼ばれ、現在はGitHubでホストされている。これはBSDベースの多くのシステム、たとえばmacOSにプリインストールされているバージョンだが、macOSに付属するものは古く、アップグレードする価値がある。

2つ目はGNU Awk(gawk)で、群を抜いて最も多機能で、活発にメンテナンスされているバージョンだ。Gawkは通常Linuxシステムにプリインストールされており、しばしばデフォルトのawkとなっている。macOSにはHomebrewを使って簡単にインストールでき、Windows用バイナリも入手可能だ。Arnold Robbinsは1994年からgawkの主要なメンテナであり、言語の舵取りを続けている(彼は古典的なawkバージョンにも多くの修正を寄せている)。GawkにはawkやPOSIX標準にはない多くの機能があり、新しい関数やネットワーク機能、C拡張API、プロファイラやデバッガ、そして最近では名前空間などが含まれる。

3つ目の主要なバージョンは、Michael Brennanによって書かれたmawkだ。UbuntuやDebian Linuxではデフォルトのawkとなっており、バイトコードコンパイラとよりメモリ効率の良い値の表現により、今でも最速のAWKである(Gawkも4.0以降バイトコードコンパイラを備えたため、現在では速度はmawkにかなり近づいている)。

AWKをワンライナーや基本的なテキスト処理に使いたいのであれば、上記のどれを使っても問題ない。より大きなスクリプトやプログラムに使おうと考えているなら、Gawkの機能の豊富さから、それが賢明な選択となる。

他にも成熟度やメンテナンス状況がさまざまなAWK実装がいくつか存在する。特に注目すべきものとして、組み込みLinux環境で使われるサイズ最適化されたBusyBox版、実行時にJava言語の機能にアクセスできるJavaによる再実装、そして筆者自身によるGoで書かれたPOSIX準拠の実装であるGoAWKがある。3つの主要なAWKとBusyBox版はいずれもCで書かれている。

Gawk 4.0以降の変更点

LWNがgawk 4.0のリリースを取り上げてから、もう10年近くが経つ。「2011年以降、多くのことが変わった」と言いたくなるが、実際にはAWKの世界では物事は比較的ゆっくりと進む。ここでは4.0以降の注目すべき機能について述べるが、詳細については完全な4.xおよび5.xの変更履歴を参照してほしい。Gawk 5.1.0はちょうど1か月強前の4月14日にリリースされた。

ユーザーから見て最も大きな新機能は、5.0で導入された名前空間だ。ほとんどの現代的な言語には、名前の衝突なしに大規模なプロジェクトやライブラリを提供しやすくするために、何らかの名前空間の概念がある。Gawk 5.0では後方互換性を保った形で名前空間が追加され、開発者は次のようなおもちゃの数学ライブラリのようなライブラリを作成できるようになった。

    # area.awk
    @namespace "area"

    BEGIN {
        pi = 3.14159  # namespaced "constant"
    }

    function circle(radius) {
        return pi*radius*radius
    }

ライブラリ内の変数や関数を参照するには、C++と同様にnamespace::nameという構文を使う。

    $ gawk -f area.awk -e 'BEGIN { print area::pi, area::circle(10) }'
    3.14159 314.159

Robbinsは、AWKに名前空間がないことが、大規模なプログラミング言語として普及しなかった主な理由の1つであると考えており、gawk 5.0のこの機能がその問題の解決に役立つかもしれないとしている。RobbinsがAWKの足かせになっていると考えるもう1つの大きな問題は、優れたC拡張インターフェースの欠如だ。Gawkの動的拡張インターフェースは4.1で全面的に刷新され、定義されたAPIを持つようになり、既存のCやC++ライブラリをラップしてAWKから簡単に呼び出せるようになった。

ユーザーマニュアルにあるサンプルCラッパーコードの次のスニペットは、stat()システムコールからのファイル名と値をAWKの配列(文字列をキーとするハッシュテーブル)に格納するものだ。

    /* empty out the array */
    clear_array(array);

    /* fill in the array */
    array_set(array, "name", make_const_string(name, strlen(name), &tmp));
    array_set_numeric(array, "dev", sbuf->st_dev);
    array_set_numeric(array, "ino", sbuf->st_ino);
    array_set_numeric(array, "mode", sbuf->st_mode);

4.2リリース(そして5.0でも継続された)におけるもう1つの変更は、大幅に刷新されたソースコードのプリティプリンタだ。Gawkのプリティプリンタにより、Goのgo fmtツールやPythonのBlackフォーマッタと同様に、標準化されたAWKコードフォーマッタとして使うことができる。たとえば、上記のarea.awkファイルをプリティプリントするには次のようにする。

    $ gawk --pretty-print -f area.awk

これにより次のような出力が得られる。

    @namespace "area"

    BEGIN {
        pi = 3.14159    # namespaced "constant"
    }


    function circle(radius)
    {
        return (pi * radius * radius)
    }

このツールの選択に疑問を抱くかもしれない。なぜ「BEGIN {」では「{」の前に改行が入らないのに、functionでは改行が入るのか?(AWKの構文ではそれが許されていないのだ)。なぜ関数の前に2つの空行があり、return式の周りに括弧があるのか? しかし少なくとも一貫しており、コードスタイルをめぐる議論を避けるのに役立つかもしれない。

Gawkでは限定的な実行時の型検査が可能であり、4.2で追加されたtypeof()関数によってそれが拡張された。typeof()は入力の型に応じて「string」「number」「array」といった文字列定数を返す。これらの関数は、たとえばネストされた配列のすべての項目を再帰的にたどるコードにとって重要だ(これはPOSIX AWKではできないことだ)。

4.2では、gawkは@/foo/という構文を使って正規表現定数を第一級のデータ型としてもサポートするようになった。以前は正規表現定数を変数に格納することはできなかった。typeof(@/foo/)は文字列「regexp」を返す。パフォーマンスに関しては、gawk 4.2ではLinuxシステムで利用可能な場合にfwrite_unlocked()を使用することで大幅な改善がもたらされた。gawkはシングルスレッドであるため、ロックしないstdio関数を使用でき、これにより生の出力速度が7〜18%向上する。たとえば大きなファイルに対するgawk '{ print }'などだ。

GNU Awkユーザーズガイドは常に徹底したリファレンスであったが、4.1とさらに5.xリリースで大幅に更新され、新しい例や要約セクション、演習が追加され、大幅な校正も行われた。

最後に(そして最も些細なことだが)、私が面白いと感じた4.0でのさりげない変更として、sub()gsub()におけるバックスラッシュの扱いが元に戻されたことがある。Robbinsは次のように書いている

sub()gsub()におけるバックスラッシュのデフォルトの扱いは、3.1の挙動に戻された。たとえ標準に準拠するためであっても、そのような形で互換性を壊せると考えるのは愚かだった。

subおよびgsub関数は中核的な正規表現置換関数であり、バックスラッシュの複雑な扱いに対するわずかな「修正」でさえ人々のコードを壊してしまった。

バージョン4.0.0がリリースされたとき、gawkのメンテナはPOSIXの規則をデフォルトにし、10年以上にわたる後方互換性を壊した。言うまでもなく、これは悪い考えであり、バージョン4.0.1以降、gawkは従来の挙動に戻し、--posixが指定された場合にのみPOSIXの規則に従うようになった。

Robbinsは当初の変更で判断をわずかに誤ったかもしれないが、後方互換性を真剣に考えていることは明らかだ。特にgawkのような人気のあるツールでは、仕様に反し続ける方が、これまで常にそう動作してきたものを変えるよりも良い場合もある。

AWKは今でも現役なのか

AWKが今でも現役かどうかを問うのは、空気が今でも必要かどうかを問うようなものだ。目には見えなくても、至るところに存在している。多くのLinux管理者やDevOpsエンジニアが、データを変換したり、ログファイルを通じて問題を診断したりするためにAWKを使っている。AWKのいずれかのバージョンは、ほぼすべてのUnixベースのマシンにインストールされている。アドホックな用途に加えて、多くの大規模オープンソースプロジェクトが、ビルドやドキュメント生成のツールの一部でAWKを使っている。ほんの数例を挙げると、Linuxカーネルはx86ツールでobjdumpファイルをチェックしたり再フォーマットしたりするためにAWKを使い、Neovimはドキュメントを生成するために、FFmpegはビルドやテストのためにAWKを使っている。

AWKのビルドスクリプトは、人々が置き換えたいと思っても驚くほどしぶとく残る。2018年にはLWNが、GCCのコントリビューターがオプション解析コードを生成するスクリプトでAWKをPythonに置き換えたいと考えていることについて報じた。当時はこの提案を支持する声もあったが、どうやら実際に移植作業を志願する者は現れず、AWKスクリプトは今も残っている

Robbinsは2018年の論文で、AWK(具体的にはgawk)を「システムプログラミング言語」として、ここではより大きなツールやプログラムを書くための言語という意味で、使うことを論じている。彼はそれが普及しなかった理由を概説しているが、Kernighanは拡張機構の欠如がAWKが大規模なプログラムで広く使われていない主な理由であるという考えに「100%確信しているわけではない」としている。彼は、システムコールなどへのアクセスが組み込みでサポートされていないことが原因かもしれないと示唆した。しかし、それでも多くの人々がより大きなツールを構築することを妨げられてはいない。Robbins自身による1300行のAWKで書かれた文芸的プログラミングツールTexiWeb Jr.、Werner Stoopによるソースコード中のMarkdownコメントからドキュメントを生成する800行のツールd.awk、そしてクラウドベースの翻訳APIへのかなり強力なコマンドラインインターフェースを提供する6000行のAWKツールTranslate Shellなどだ。

ここ数年、何人かの開発者が、SparkやHadoopのような重い分散コンピューティングシステムよりもはるかにシンプルで(時には高速な)ツールとして、AWKを「ビッグデータ」ツールキットの中で使うことについて書いている。Nick Strayerは複数のコアにわたって25テラバイトのデータを解析するためにAWKとRを使ったことについて書いている。その他のビッグデータの例としては、Adam Drakeによる示唆に富むタイトルの記事「Command-line Tools can be 235x Faster than your Hadoop Cluster」や、Brendan O'Connorによる「Don’t MAWK AWK – the fastest and most elegant big data munging language!」がある。

アドホックなテキスト処理、ビルドツール、「システムプログラミング」、そしてビッグデータ処理の間で――テキストモードのFPSは言うまでもなく――AWKは2020年においても健在であるように見える。

[この記事の草稿をレビューしてくれたArnold Robbinsに感謝する。]

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント