Awkward Programming in awk

2002-12, Daichi Mochihashi <daiti-m@is.aist-nara.ac.jp>

0. はじめに

awk とは, テキスト処理用の言語です. 特に, フィールドに分かれたデータの処理に 適しています.
awk のプログラミングについて初心者を対象に解説しているページは結構ありますが, awk の突っ込んだ知識についてまとめたページは見当たらなかったようなので書いてみました.
awk でのプログラミングについては, 6. で紹介しておいた入門サイトや ドキュメントなどをご覧下さい。

1. Why awk?

テキスト処理用の言語としては Perl (最近だと日本では Ruby) などが有名ですが, どうして awk なのでしょう.
awk には, 次のような利点があると個人的に思っています.
  1. 変数に$@がつかない, C言語ライクな簡素な シンタクス.
  2. 自動的に分割されるフィールド.
    Perl にも自動フィールド分割オプション(-a)がありますが, 個人的な経験としてはあまり使いません.
  3. ミニマリズム (^^;).
    今となっては趣味の範疇ですが, たとえば DOS版 GNU awk 2.15.2 (gawk.exe) は実行ファイルが約180KBだったのに対し, JPerl 4.036 (jperl.exe) は 約310KBでした.それ以上に違うのがメモリ使用量で, メモリ640KBの MS-DOS では perl を普段から(特にサブプロセスで)使うのは躊躇されたような記憶が あります.
この中で, 最も意味があると思われるのはたぶん 1. でしょう.
複雑なプログラムを書くには perl は便利ですが (というか awk では書けない プログラムも多い), 簡単な処理, 特に最初からフィールドに分かれていることが わかっているデータを処理するプログラムを書くには awk は便利で, $などがない分 プログラムが見やすくなっています.
また, log, exp, sin, tan-1 などの基本的な算術関数も揃っていますので, アルゴリズムの簡単な実験にも適しているでしょう.

といっても勿論私も普段から awk を使っているわけではなく (普段使っているのは Objective CamlPerl です), 必要な時に使う程度ですが, 逆に 簡素であるだけに, その可能性を極めるのは面白い言語でもあります.

2. awk の基本書

awk でのプログラミングについて学ぶとき, 最も基本となるのは でしょう.
この本は awk の文法について深く網羅的に解説してあるだけでなく, サンプルスクリプトも面白く, 正規表現についての入門もあります.後ろの方では 再帰下降パーサや make の簡単なものを作るなど, アルゴリズムの実験までしていて, 情報科学的に見ても非常に面白いものになっています.
awk の文法は C言語に 似ているので(作者が重なっているので当然ですが), C言語への入門としても適して いるかもしれません.

なお, 上のベル研究所のAWKのページでは, 原作者が書いた 'One true awk'の ソースコード(awk.shar, awk.tar.gz) と Windows バイナリ(awk95.exe)が公開されて います.

その次に有名なのが,

だと思われます.
ただし, この本は awk については一通りの紹介しかしていません.ここで書かれて いる awk についての内容はほぼ上の「プログラミング言語AWK」に書かれている ので, この本はどちらかというと awk の本というより, sed の本と言ったほうが いいでしょう. sed 10行野郎を育成する:) 本は, 仙石さんのSED教室などの オンライン文書を除けば他にほとんどありません.
この本には後ろに, awk と sed で書かれたいくつかの実用プログラムが載って いますので, 特にシェルスクリプトとの組み合わせ方などで参考になりそうです.
なお, awk 初心者の方には, (当時)阪大情報処理教育センターの荻原剛志さんの 書かれた「AWKの簡単な使い方」がおすすめです. 私が最初に(自習課題として)先生に渡されて読んだのもこれでした. 付属データは, こちらに置いてあります。
この文書は様々な ftp にアーカイブされていますが, 以前はvectorからもダウンロードすることができました。

3. awk の応用

基本の次は応用です.日本語訳はまだありませんが, O'Reilly から という本が出ているようです. 本文のXML ソースが, http://examples.oreilly.com/awkprog3/eap3.tar から読めます.
前半は「プログラミング言語AWK」に比べて新しいことはないようですが, gawk 3.1.x 以降の/inet/スペシャルファイルを用いた10章の awk ネットワークプログラミング, 12章のawkライブラリの使い方/作り方(最近の GNU awk には assert.awk, getopt.awk などいくつかのライブラリが share/awk/ の下に付属 してきます)は最近の拡張として参考になりそうです.

さらに深い awk の知識を目指すなら, 次は

しかないでしょう.:-)

この本はアスキー256倍シリーズらしく, 書き方にちょっと'下品'なところがあって, 上の Addison-Wesley のような瀟洒な本を読んでいると躊躇してしまうかもしれません. (私は最初引きました.^^;) が, 実は GNU awk について徹底的に解説し尽くしている本であるのみならず, gawk の各コマンド/変数の使い方と 簡単なサンプルプログラムのインデックスまで付いていて, awk でプログラムを書く際のハンドブックとしても, とても重宝する本です.

もちろんその他の内容はマニアの極みで, awk における連想配列がハッシュとして どのように実現されているか, といった内部構造の解析に始まって, DOS extender go32版の gawk.exe との速度比較, 果ては awk のソースを書き換えて自分専用の awk を作る話など, awk をとことん楽しみ尽くしている本です. 巻末には awk で書かれた100行, 200行のプログラムが連続し, これでもかこれでもか といったawkプログラミングの濃さが味わえます. (^^;)
awk や sed など, コマンドラインで使うスクリプト言語は「1行野郎」と呼ばれ, 1行のプログラムでいかに簡単に仕事を片付けられるか, ということが楽しさでも ありますが, そういった意味ではこの本は「awk 100行野郎」を養成する本だと 言えます. :-) (中でもそう述べられています.)
なお, この本の「100行awkプログラム」は上のページの ここから手に入れることができます.

4. awk の極限

単なるテキスト処理言語であるはずの awk ですが, gawk の TeXinfo の Glossary にもいくつか述べられているように, awk には awk で書かれた 驚くようなプログラムが存在します.
以下で TeXinfo で触れられていないその他のものも含め, これまでに見つけたそうした 極限の awk プログラムをご紹介します.

4.1 awk Assembler

これは gawk の TeXinfo にも載っているものです. gawk.info より:
Amazing `awk' Assembler
     Henry Spencer at the University of Toronto wrote a retargetable
     assembler completely as `awk' scripts.  It is thousands of lines
     long, including machine descriptions for several eight-bit
     microcomputers.  It is a good example of a program that would have
     been better written in another language.
このプログラムは ftp://ftp.freefriends.org/arnold/Awkstuff/aaa.tgz から入手することができます.
なお, 作者のHenry Spencer氏は awk で nroff クローン awf というものも書いており, 昔は有名なプログラム だったようです(nroff がインストールされていなくとも, awk さえあれば man が見れる).awf は, 同じディレクトリの ftp://ftp.freefriends.org/arnold/Awkstuff/awf.tgzから入手できます.

4.2 awk Lisp interpreter

1994年に alt.sources に, Roger Rohrbach 氏によって awk で書かれた Lisp インタプリタ walk がポストされました. (Message-ID: <OZ.94May31112039@ursa.sis.yorku.ca>)
これは探した限りでは, http://www.funet.fi/pub/archive/alt.sources/volume94/Jun/940601.09.gz で手に入るようです.
cl:~/atelier/awk/walk% nawk -f walk
walk (LISP in awk)	Copyright (c) 1988, 1990 Roger Rohrbach
-> (cons 'a nil)
(a)
-> (set 'l (list 'a 'b 'c 'd))
(a b c d)
-> (car (cdr (cdr l)))
c
-> (set 'S '(lambda (x y) x))
(lambda (x y) x)
-> (set 'K '(lambda (x y z) (x z (y z))))
(lambda (x y z) (x z (y z)))
-> 29 atoms, 140 list cells.
cl:~/atelier/awk/walk% 
walk メインドライバ(awk 690行) walkマニュアル walk.pdf (walk.ms をps->pdf化したもの)
walk のベンチマーク結果が こちらにあります.
なお, 最近(2001年)になって, Ehud Lamm氏が新しく Awklisp というものを書かれたようです.

4.3 その他

この中だと, 私は断然 Lisp インタプリタが凄いと思います.アセンブラといえども (バイナリ文字もテキストに含めれば)結局テキスト処理を行っているといえる わけですが, これだけは全然意味が違っています.
もちろんスタック等を自力でエミュレートすれば何でもできるわけですが...

5. awk の環境

5.1 各種環境の awk

5.2 GNU awk 標準ライブラリ

GNU awk 3.x 以降には, ${prefix}/share/awk/の下に 以下のようなライブラリが付属しています.
これらを使うには, @include をプリプロセスする igawk を使って,
#!/usr/local/bin/igawk -f
@include getopt.awk
..
のように書くとよいでしょう.(もちろん, 自力で cpp などに渡しても大丈夫です. この辺りの話は「AWKを256倍使う本」に色々と書かれていますのでどうぞ.)
assert.awk
assert(3) を提供します.
assertion に失敗した場合, エラーは stderr (特殊ファイル "/dev/stderr") に吐かれます.
ctime.awk
ctime(3) を提供します.
ftrans.awk
複数のファイルを入力とするとき, ファイルの最初と最後にある関数を 実行したい場合, これを include して beginfile() と endfile() を定義 することで行えます.
getopt.awk
getopt(3) を提供します.
使用例:
      while ((opt = getopt(ARGC, ARGV, "ab:cd")) != -1)
              printf("opt = %c, optarg = %s\n",
                      opt, Optarg);
      #  終了後, Optind にオプションでない最初のARGVのインデクスがセットされる
      for (i = Optind; i < ARGC; i++)
              printf("realarg[%d] = %s\n", i, ARGV[i]);
      
gettime.awk
gettimeofday(3) を提供します.
gettimeofday(tm) とすると, tm["second"],tm["minute"], tm["hour"],..等で現在の時間が取り出せます.
group.awk
libexec/awk/grcat を実行して得た group エントリを基に, getgrent, getgrnam, getgrgid などを提供します.
join.awk
s = join(array, 2, 10, "\t"); とすると, 配列arrayの array[2]..array[10]を順にTABで連結した文字列を 返します. join(array, 2, 10, SUBSEP)とすると何も挟まずに 連結します.
mktime.awk
mktime(3) を提供します.
mktime("2002 01 23 12 34 56") は, 1011756896 を返します.
nextfile.awk
アクション中で next; とすると次の行を読みに行きます が, これを最初に読んでおくと nextfile(); とすると次の「ファイル」を 読みに行きます. 内部では(簡単ですが), nextfile(); が指定された以降 そのファイルの内容を捨てるアクションが加えられます.
ord.awk
perlと同じ ord() と, 文字cをキャラクタコードに換える chr() を提供します.
passwd.awk
libexec/awk/pwcat を実行して得た passwd エントリを基に, getpwent, getpwnam, getpwuid などを提供します.
round.awk
round() 関数を提供します (awk にはデフォルトでは floor() も round() も ない)

6. 関連リンク

7. その他

GNU awk 3.1.0 は extension/ の下にdllを呼んで fork() する extension("./fork.so", "dlload"); なんてコードがあったり, /inet/* でネットワークにアクセスできたりと, ほとんど awk の範疇を超えている ようです. (^^;)
私も(日本語対応していないこともあって)まだ入れていないので, 暇があったら見てみると面白いかなと思っています.


daiti-m@is.aist-nara.ac.jp
Last modified: Sun Oct 2 14:02:15 2022