OSとRubyスクリプトの文字コードが異なる場合の対策

Rubyスクリプトをutf-8で保存して、コマンドプロンプトに日本語を出力した。

すると出力内容が文字化けしてしまった。

なんてことは無いだろうか?

 

私はある。

というかたくさんある。嫌ってほど。

Rubyからコマンドプロンプトに出力した文字列が文字化けする理由

linuxなどのOSでは、インストール時やその後の設定によって、OSの文字コードを指定することができ、そして多くの人がutf-8を選択していると思う。

そういった環境で、utf-8で保存したRubyスクリプトを実行するような場合、環境とRubyスクリプトの文字コードは同じutf-8になる。

 

それに対して、Windowsのコマンドプロンプト環境(日本語版)では、OSの文字コードはWindows-31Jだ。

utf-8で保存したRubyスクリプトをWindowsのコマンドプロンプトで実行すると、OSはWindows-31JなのにRubyスクリプトはutf-8といった状態での実行される。

 

例を挙げて実際に再現してみよう。

 

文字化けを再現するためのRubyスクリプトを用意した。
(utf-8で保存)

たった2行のRubyスクリプトだが、一応解説しておく。

 

1行目の# coding: utf-8は、Rubyスクリプトを実行する文字コードを指定している。

このRubyスクリプトはutf-8で保存しているため、Rubyインタプリタによる解釈はutf-8でしてほしいということをRubyインタプリタに伝えるという役割を持っている。

 

2行目のputs “日本語”は、”日本語”という文字列を標準出力に表示せよという命令だ。
(コマンドプロンプトからRubyスクリプトを実行した場合の標準出力はコマンドプロンプトのコンソール画面ということになる)

 

これをコマンドプロンプトから実行するとどうなるか?

実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
譌・譛ャ隱

 

つまり、というかあからさまに、出力が文字化けする。

理由を少し考えよう。

 

Rubyスクリプトがutf-8で保存されているため、そこで定義されている文字列リテラルである“日本語”も、やはりutf-8文字列のデータになる。

これをコマンドプロンプトで実行すると、プログラムとしては正常終了してコンソール上に出力もされる。

 

しかし、コマンドプロンプトのコンソールは、標準出力に渡された文字列はWindows-31Jであるはずだと決めつけて出力する。

ところが実際に標準出力に渡された文字列はutf-8であり、ここにギャップが生じる。

結果、画面上には、不適切な文字マッピングがされた結果として文字化けした情報が表示される。

String#encodeで文字コードを変換して文字化けを回避

Rubyコードを少し変更した。
(utf-8で保存)

String#encodeメソッドを使用して、utf-8だった文字列をWindows-31Jに変換している。
(”日本語”はStringクラスのインスタンスである。)

 

Windows-31Jに変換された文字列をコマンドプロンプトで表示すると、文字化けは発生しない。

コマンドプロンプトからの実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
日本語

 

文字化けを手軽に回避することができた。

(ちなみに、このやり方はRuby1.9.xから採用された方法で、Ruby1.8.xではkconvというモジュールが使用されていた。)

Windowsでは日本語ファイルパスにも注意が必要

OSとRubyスクリプトのギャップによる問題は、標準出力へ出力する時以外でも発生する。

ファイルパスの取り扱い時だ。

 

(utf-8で保存)

これをコマンドプロンプトから実行するとどうなるか?

実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
study01.rb:2:in
': incompatible character encodings: UTF-8 and Windows-31
J (Encoding::CompatibilityError)

 

実行結果は、エラーによるRubyスクリプトの停止だ。

 

結合しようとしている文字同士の文字コードの不一致が原因だ。

プログラムを少し書き換えると、エラーを回避することができる。

 

(utf-8で保存)

 

実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
このファイルはK:/home/aptanaws/StudyRuby/日本語フォルダ/study01.rbです。

 

utf-8のRubyスクリプトで定義された文字列リテラルのエンコーディングは、utf-8だということは先程説明した。

それに対して、__FILE__定数やFile.expand_pathメソッドは、戻り値に文字列を返すが、そのエンコーディングはOS依存になっている。

つまり、Windows環境であればWindows-31Jエンコーディングの文字列が戻り値になる。

 

だからそのままだと、文字コードは衝突する。

だから、文字コードの異なる文字列を使って何かをする時には、まずは文字コードを統一してからでないと、エラーが発生してしまう。

 

外部エンコーディングの指定で根本解決?

コマンドプロンプトへ文字列出力時の文字化けや、ファイル操作時のファイル名等の文字コードの取り扱いに関する問題は、String#encodeメソッドを使用すれば解決することを説明した。

でも、プログラムのそこらじゅうで、String#encodeを度々呼び出すなんてこと、したくないですよね?

 

そこで使うのが、外部エンコーディングという概念だ。

Rubyの起動オプションに-Eオプションを渡すと、内部エンコーディングと外部エンコーディングの両方を指定した実行ができる。

 

Rubyスクリプトの先頭行にこのように記載することでも、Rubyの起動オプションとして認識してくれる。
参考

 

この場合、外部エンコーディングがWindows-31Jで、内部エンコーディングがutf-8であるということを指定している。

 

 

コマンドプロンプトからの実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
UTF-8
日本語文字列
Windows-31J
日本語文字列

 

utf-8の文字列もWindows-31Jの文字列も、文字化けせずに出力させることができるようになった。

 

さらに、日本語のパスを指定して、そこに日本語を出力する場合でも、指定したパスがWindows-31Jとして解釈され、出力内容もWindows-31Jになる。

 

実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
UTF-8
UTF-8

 

作成された日本語ファイル.txtの内容(文字コードはWindows-31J)になっている。

※外部エンコーディングを指定していない時でも、日本語ファイルは作成されるようだ。

 

このように、内部エンコーディングと外部エンコーディングを指定することで、RubyスクリプトはUTF-8で記述して、プロンプトへの出力やファイルへの出力はWindows-31Jにするといったことが、意識せずに行えるようになる。

 

それでも取得したファイルパスはWindows-31Jに。

内部エンコーディングと外部エンコーディングの指定をすることで、本当に文字コードを全く意識しなくてよくなったのか?

と言われれば、答えはNo。

 

外部・内部エンコーディングを指定していても、__FILE__定数で取得できる文字列はWindows-31Jエンコーディングであるため、文字操作をするためには、文字コードを結合させるなどの文字操作時には、文字コードをあらかじめ合わせておく必要がある。

 

実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
study01.rb:7:in
‘: incompatible character encodings: UTF-8 and Windows-31
J (Encoding::CompatibilityError)

 

エラーを解消するには、こうする。

 

実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
このファイルはK:/home/aptanaws/StudyRuby/日本語フォルダ/study01.rbです。

 

それに、UTF-8コードの文字列をFile.expand_pathなどのファイルパスを操作するメソッドに渡した場合なども、文字コードはWindows_31Jになってしまう。

 

実行結果:

K:\home\aptanaws\StudyRuby\日本語フォルダ>ruby study01.rb
Windows-31J
K:/home/aptanaws/StudyRuby/日本語フォルダ/study01.rb

 

結局どうすればいいか?

結局のところ、Rubyスクリプトで完全に文字コードを意識しないというのは不可能ということになる。(または単に私が無知なだけ?)

 

まとめると、このような感じになる。

 

仕様に制限を

Rubyスクリプトを書き始めるなら、その前にこの辺りの利用制限を明確にしておくといいかもしれない。

例えば、「使用するファイルの内容は全てutf-8に統一しておくこと」とか、「ファイルパスは全て相対パスのみで指定し、絶対パスを必要とする機能は盛り込まない」とか。

 

コードをシンプルに保つためには、利用シーンを考えて、踏み込めるところまで文字コードに関して仕様に制限をかけるということも、ひとつの手ではあると思う。

“OSとRubyスクリプトの文字コードが異なる場合の対策” への1件のコメント

  1. […] 参考: OSとRubyスクリプトの文字コードが異なる場合の対策 | もっとクールにプログラミング […]

コメントを残す

トラックバック: http://pgnote.net/wp-trackback.php?p=211