Tclではファイル入出力、シリアル通信、ソケット通信でバイナリデータの読み書きができますが、fconfigureの-translationオプション等をちゃんと理解しておかないと、訳が分からない挙動に見えることがあります。
Tclでバイナリデータ入出力する時の留意点
- チャンネルをfconfigureで-encoding binaryと-translation binaryに設定する。
- バイナリデータの書き出しにはputs -nonewlineを使う。
- バイナリデータの読み込みにはreadを使う。
- 通信の場合はfconfigureで-blocking 0も設定する。
-encoding binary
バイナリデータ入出力に注目した場合は、入出力するバイナリデータを改竄してしまわないようにするための設定になります。
-translation binary
バイナリデータ入出力に注目した場合は、入力するバイナリデータに含まれる0x0Aという値のバイトを削除してしまなないようにするための設定になります。
puts -nonewline
putsは文字列に改行を付加してチャンネルに出力するコマンドです。
私は-translation binaryとしてputsを実行すると改行が付加されないと誤解していてハマりました。
Tclインタプリタ内では改行コードは\n(LF 0x0a)ですが、-translation binaryはTclインタプリタ内の改行コードをそのまま使うという意味で、従って-translation lfと同じ意味になるため、-nonewlineオプションを付けずにputsで出力すると0x0aが追加されてしまいます。
要するにputsにおける-translationは「何を追加するか」という指定であって、何も追加したくないならputs -nonewlineで出力しなければならないのでした。
逆に言うと、-translation設定が何であれ、puts -nonewlineを使えばバイナリデータを出力できるので、-translation binaryはバイナリデータ出力には関係無いということになります。
read
Tclの入力コマンドには
- gets
- read
の2つがありますが、バイナリデータ入力にgetsは使えません。
私は-translation binaryとしてgetsを実行すると改行コードも含めて入力されると誤解していてハマりました。
Tclインタプリタ内では改行コードは\n(LF 0x0a)ですが、-translation binaryはTclインタプリタ内の改行コードをそのまま使うという意味で、従って-translation lfと同じ意味になるため、getsで入力すると0x0aという値のバイトが削除され、そのバイト以前のデータだけが取得されます。
要するにgetsにおける-translationは「何を削除して入力を中断するか」という指定であって、何も削除したくないならgetsは使えないのでした。
一方、readですが、私はreadはまるっと全部手を加えずに読むコマンドで-translationは-nonewlineで削除する一番最後についている改行コードの指定だろうと誤解していました。
ハマったというほどの被害がなかったのは、バイナリデータを扱う場合は常に-translation binaryを設定していたからですが、しかしそれは以下の挙動を理解してそうしていたわけではありませんでした。
readはまるっと全部手を加えずに読んでいるわけではありませんでした。
Tclインタプリタ内では改行コードは\n(LF 0x0a)ですが、readは読み込み結果を\nでsplitすれば-translation binaryのgetsを繰り返し実行した時のように1行づつ取り出せるように、-translation設定に応じてすべての改行コードを変換してしまうのでした。
-trasnlation設定のうちauto、cr、crlfではCR(0x0d)やCR(0x0d)/LF(0x0a)をLF(0x0a)に置換してしまうため、バイナリデータ入力でこれをやってしまうと0x0dという値のバイトが削除されてしまいます。lfやbinaryでは後でlfでsplitできるようlfのまま残すため、実質的に無変換で入力することになるのでした。
-blocking 0
readはファイル末尾まで読み込もうとするため、相手側がチャンネルを閉じずにバイナリデータ送受信が継続するような通信の場合、-blocking 1でreadを実行するとフリーズしてしまいます。
-blocking 0でreadして、取り敢えず既に届いているデータを全部取得して変数にappendしておいて、パケット末尾まで受信できているかどうかは別途受信データを解析して判断しなければなりません。
所感
-translationがputs、gets、readにどのように作用するのかは、Tclインタプリタは入力された文字コードや改行コードのまま処理しているのではなく、内部表現用の文字コードや改行コードになるよう入力時に変換して取り込み、内部表現から外用に変換して出力している、と明確に意識できるようになるまで不可解で、処理系のバグすら疑ってしまいました。