id:eel3:20151102:1446476928 のhcaslのsh版にて、AWKで標準入力/ファイルを1byteずつ読むために試行錯誤したので、独立した記事として書いておく。
現時点での結論としては、POSIXの範囲内では、awk(1)単体で標準入力やファイルから1byteずつ中身を読む方法はなさそうだ。FSやRSを変更すればなんとかなりそう――と思いきや、実はそうでもなかったという。
そもそもFSやRSが存在する時点で、「特定の文字を特別扱いする」と宣言しているようなものだ。確実に1byteずつ読みこむ、という動作自体と相性が悪い。
そこで考えた方法が、他のツールを使って文字から別の表現――文字コードに変換してawk(1)に食わせて、awk(1)内で文字コードから文字に戻す、というものだ。
hcaslでは、od(1)を使って1byteごとに10進数でダンプし、awk(1)に流しこんでいる。中核部分のコードは、こんな感じ。
#!/bin/sh # sample.sh od -A n -t d1 -v ${@+"$@"} | sed 's/^[ \t]*//' | sed 's/[ \t][ \t]*/\n/g' | awk ' { c = sprintf("%c", $0 + 0) print c }'
実行例:
$ echo -n abcd | od -A n -t d1 -v 97 98 99 100 $ echo -n abcd | > od -A n -t d1 -v | > sed 's/^[ \t]*//' | > sed 's/[ \t][ \t]*/\n/g' 97 98 99 100 $ echo -n abcd | ./sample.sh a b c d $ _
この例ではod(1)を使っているので、問答無用で1byteずつ読みこむことになる。
ところで、1文字ずつ読みこむにはどうしたらよいか? まだ妄想の領域だが、アイデアはある。
例えばUTF-8の「あ」の文字コードは16進数で「E3 81 82」、10進数でいうと「227 129 130」だ。そこで、何らかのツール(例えばfooという仮想のフィルタ)を通して:
$ echo -n aあbc | foo 97 227 129 130 98 99
こんな出力を得られるのなら、次のようなことが可能になる。
$ echo -n aあbc | foo | awk ' > { > c = "" > for (i = 1; i <= NF; i++) > c = c sprintf("%c", $i + 0) > print c > }' a あ b c
問題は、上に挙げた仮想のフィルタfooに相当するツールが見当たらない、もしくは、そのようなツールを自作する際に流用できそうな何かが見当たらない、ということだ。うーん、なにかよいツールは無いものか。文字コード変換ツールのオプションか何かで、1文字単位でダンプ出力できるとか、そういうものはないだろうか?