gawkやmawkで1byte/1文字ずつファイルを読む方法

id:eel3:20151212:1449931944 へのはてブのコメントより。

gawk なら簡単にできるけど、これも意外と知られてないかな。

http://b.hatena.ne.jp/entry/273514414/comment/Rocco

コメントのように、拡張アリアリなら、gawkで1文字/1byteずつ読むことや、mawkで1byteずつ読むことは可能である。例えば次のコードはgawkとmawk双方で有効だ。

$ echo $LANG
ja_JP.UTF-8
$ echo -n aあbc | gawk '
> BEGIN {
>   FS = ""
>   RS = "^$"
> }
> {
>   for (i = 1; i <= NF; i++)
>     print $i
> }'
a
あ
b
c
$ _

FSに空文字列を設定することで、レコードをフィールド単位に分割せずに丸ごと読みこむようにしている。RSに正規表現^$(決して何にもマッチしない)を設定することで、入力をレコード単位に分割せずに丸ごと読みこむようにしている。これで、改行コードだろうが何だろうが全て1文字/1byteずつ読むことが可能となる。

gawkロケールエンコーディングを解釈するので、この方法で1文字ずつ読むことができる。1byteずつ読みたい場合は、LANG=Cgawkを実行するか、gawk 4.0以降ならオプション-bないし--characters-as-bytesをつけて実行すればよい。

mawkは「マルチバイトってなに?」なawk実装なので、この方法で1byteずつ読むことになる。「1文字ずつ」は無理そうだ。

元ネタであるhcaslのsh版はPOSIX縛りだったのだが、POSIXにはFSに空文字列を設定した時の振る舞いについて、堂々と「unspecified」と書いてある。

If FS is a null string, the behavior is unspecified.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html

もっともUnix環境のawkの場合、FSに空文字列を設定した時にgawkやmawkのように振る舞う実装は多いらしい。FreeBSDなどのmanを見た限り、少なくともnawkは同様に振る舞うようだ。

RSについては、そもそも正規表現を受けつける旨の記述が無い上に、2文字以上の文字列を設定した時の振る舞いが「unspecified」となっている。

If RS contains more than one character, the results are unspecified.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html

FreeBSDNetBSDOpenBSDawkのmanも、RSが正規表現を受けつける旨の記述はない。ということは、nawkも駄目っぽいのか?

ちなみにFSに2文字以上の文字列を設定した時は正規表現として扱われる。POSIXにもそう書かれている。しかしRSはそうではない。ちょっと混同しそうだ。