bit

Rubyエンコーディング

Rubyを時々触るのだけれど、こんなプログラム:

# -*- coding: utf-8 -*-
puts "あいうえお"

を組んで、実行すると

PS D:\home\iwsttty\> ruby .\puts_japanese.rb
縺ゅ>縺・∴縺

こうなる。で、あれ?と思って、ああ「-U」オプション忘れてたと気づく。国産言語なのに、こんな単純なプログラムでさえ、日本語をJavaみたいによろしくやってくれない。
くやしいので、なぜこうなるのかというところを理解するために、とりあえず図にする。

左側が書き込みのとき、右側が読み込みのとき。矢印は実線と破線の二種類があって、前者はそのままバイト列が渡されること、後者は何らかの変換がされることを表わしている。
書き込みのときは、外部エンコーディングが明示的に設定されているかどうかで挙動が異なる(内部エンコーディングは関係ない)。設定されていなければ、Stringオブジェクトのバイト列がそのまま出力される。設定されていれば、文字列エンコーディングからそのIOオブジェクトの外部エンコーディングに変換されて出力される。明示的と言ったのは、EncodingのメソッドにEncoding.default_externalというのがあって、外部エンコーディングが設定されていないとあたかもこちらの値が使われるようなイメージがあるからであるが、書き込み時はこの値は関係ない。
読み込みのときは、内部エンコーディングが設定されているかどうかで挙動が異なる。設定されていなければ、入力されたバイト列は外部エンコーディングを仮定して、Rubyプロセス内部に読み込まれる。設定されていれば、さらに内部エンコーディングに変換される。
先ほどのEncoding.default_externalは、読み込み時で、かつそのIOオブジェクトに外部エンコーディングが設定されていない場合に用いられる。ニッチだ。
冒頭のプログラムに戻ると、Rubyは何もしないと標準出力オブジェクトSTDOUTのエンコーディングは内部、外部ともに設定されないため、UTF-8文字列を外部に出力すると、そのままPowerShellの端末に出力される。PowerShellのエンコーディングはディフォルトでは「日本語 (シフト JIS)」なので、文字化けする。

PS D:\home\iwsttty> [console]::OutputEncoding.EncodingName
日本語 (シフト JIS)

結局、STDOUTの外部エンコーディングが初期状態でnilなのがダメなんだろうな。せっかくEncoding.default_externalが適切に設定されているのに。