AWKで1byteずつファイルを読む方法 + 1文字ずつ読む方法の構想

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文字単位でダンプ出力できるとか、そういうものはないだろうか?