awk
awkは$1,$2とかで入力行のフィールドを参照できるが、$iのように変数はつかえないと思っていた。というのはshではできないから。
% cat foo echo $1 I=1 echo ${$I} % sh foo xxx yyy xxx foo: ${$I}: Bad substitution %
awkだとこんな感じ。
% echo xxx yyy | awk '{ I=1; print $I }' xxx %
awkはCに似せているだけあって値のセマンティックスなのにたいして、shはマクロ展開が基本なのでいろいろ違って当然なのだが、いったいどういう文法になっているのかFreeBSDのawkのソースコード(/usr/src/contrib/one-true-awk)をのぞいてみた。
構成はlex.cが字句解析でawkgram.yがYACCをつかった構文解析になっている。
lex.cを '$' で検索すると目的の箇所はすぐに見つかって、awkgram.yとあわせてみると$は優先度の高い演算子(INDIRECT/IVAR)として扱われていて還元規則が INDIRECT term となっているため、何でもかけると思ってよいだろう。したがってふつうに $1, $2, $variable かけるのはまったくとうぜんなのであった。
lex.cをみるとなぜか$NFは$(NF)として扱われているが、これは /* very special */ とコメントがかいてあるが、意味は理解できなかった。
$func(xxx)とか$array[idx]も特別扱いされているようだ。
% echo a b c | awk 'function f(x) { return x-1 }; { print $f(NF) }' b %
関数引数も特別扱いされている。以下例でいうと関数fで引数xをつかって$xで参照しているところだ。awkには変数宣言がないので変数のスコープはグローバル変数か関数引数のどちらかしかないためこのような扱いが必要になっているようだ。
% echo a b c | awk 'function f(x) { return $x }; { print f(NF-1) }' b %
下のように書くとxはグローバル変数になるためだ。
% echo a b c | awk 'function f() { return $x }; BEGIN{x=2}; { print f() }' b % echo a b c | awk 'function f(y) { x=y; return $x }; { print f(NF-1) }' b %
関連リンク:
awkにはpipeでつないで外部コマンドをフィルタとしてつかう機能がある。
awkプロセスとフィルタプロセスの両方が標準出力に書くようなときにはfflushを適切に呼ばないとだめだよなぁとおもって調べてみたら、いくつか自動でfflush(3C)を呼んでくれるようだ。
FreeBSDの/usr/src/contrib/one-true-awkをみてみる。fflush()してるのは以下のところ:
- FATAL/WARNING: エラーメッセージを出す前にfflush(3C)。
- getline: 読む前にfflush(stdout)。コメントには /* in case someone is waiting for a prompt */ とあるのでプロンプトを出してユーザからの入力待ちのときにflushしたいという意図か。
- printf: パイプで外部プロセスに渡している場合はprintfするたびにfflush(fp)するようだ。
- system: system(3C)の前にfflush(stdout)
- fflush: fflush()かfflush("")なら全fpをfflush(3C)して、openしているfileが指定されたらそれだけをfflush(3C)
- print: リダイレクトしている場合にfflush(3C)する。
- openfile(): openfileはawkの中でファイルやプロセスに関連づけられたfpをとってくるのにつかわれる関数で、fopen(3C)やpopen(3C)する直前にfflush(3C)している。
GNU AWKにinfoにもfflushについて書いてあって
- http://www.gnu.org/software/gawk/manual/gawk.html#I_002fO-Functions
- fflushはPOSIXにはない
- system("")で同じ効果を期待できる
らしい。