Ruby 1.9.1がリリースされてから3ヶ月が経ちました。この連載などを参考にしながらRuby 1.9を体験してみた人も多いことと思います。
さて、既に紹介された数多くの新機能や性能向上など、1.9では1.8までのRubyに比べて大幅な改良が行われました。しかしその一方で、1.8での意外な挙動がそのまま残されていたり、1.9で新たに予想外の挙動が追加されていたりもします。
昔の人は言いました。「君子危うきに近寄らず」と。しかし、何が危ないのか分かっていなければ、その危険を避けることもできません。そこで、Windows版のRuby 1.9を題材にしながら、どうやってRubyに埋め込まれた「地雷」を避けていくか、実践的に学習してみることにしましょう。
Windows版Ruby
話を始める前に、今回取り扱うWindows版Rubyについて簡単に説明しておきましょう。実は、Windows上で実行できるRubyには様々な種類が存在します。Rubyインタプリタは自分の実行対象となる環境をRUBY_PLATFORMという定数に格納していますが、各種のWindows版RubyにおけるRUBY_PLATFORMは以下のようになっています。
- mswin32版
- i386-mswin32
- Visual C++ 6.0で32bit版Windows向けにコンパイルされたrubyです。後述のmingw32版とバイナリ互換性があります。
- i386-mswin32_70
- Visual C++ .NETで32bit版Windows向けにコンパイルされたrubyです。
- i386-mswin32_71
- Visual C++ .NET 2003で32bit版Windows向けにコンパイルされたrubyです。
- i386-mswin32_80
- Visual C++ 2005で32bit版Windows向けにコンパイルされたrubyです。
- i386-mswin32_90
- Visual C++ 2008で32bit版Windows向けにコンパイルされたrubyです。
- mswin64版
- x64-mswin64_80
- Visual C++ 2005でx64版Windows向けにコンパイルされたrubyです。
- x64-mswin64_90
- Visual C++ 2008でx64版Windows向けにコンパイルされたrubyです。
- mingw32版
- i386-mingw32
- MinGWで32bit版Windows向けにコンパイルされたrubyです。前述のmswin32版のうちVisual C++ 6.0でコンパイルされたもの(i386-mswin32)とバイナリ互換性があります。
- bccwin32版
- i386-bccwin32
- Borland C++でコンパイルされたrubyです。1.9ではBorland C++向けサポートが打ち切られているため、構築できなくなりました。
- cygwin版
- i386-cygwin
- cygwin向けにコンパイルされたrubyです。
- interix版
- i386-interixN
- SFU(Services for UNIX)向けにコンパイルされたrubyです。Nの部分にはSFUのバージョンに応じた数字が入ります。
特に注記したものを除き、このリストで挙げられたもの同士の間でバイナリ互換性はありません。つまり、例えばi386-mswin32を名乗るruby向けにコンパイルされた拡張ライブラリはi386-mswin32_70を名乗るrubyでは動作しないということです。
このリストを見るだけで頭がクラクラしてきそうですね。ここまで読んだだけで「WindowsでRubyを使うのは危険なんじゃないか」と気づいた人は、既に一定の危機回避能力を身に付けていると言えます。さあ、早く脱出しましょう!
……とはいうものの、ここで脱出してしまうとこの記事はこれで終わってしまうので、とりあえず今回はmswin32版またはmingw32版を対象にすることにしましょう。これらを選んだのは、Windowsがあって、rubyインタプリタが導入できさえすれば、実行には他に特別な環境が必要ではないためです。また、コンパイル環境が存在しない場合にも、mswin32版であればartonさんが提供してくださっているインストールパッケージを利用して簡単に導入することができます(※1)。
なお、以下の実行例では実行環境としては日本語版のWindowsを使用します。
分かりやすい地雷――ファイル名
さて、Rubyには以前からディレクトリ(フォルダ)内のファイルの一覧を取得するメソッドとしてDir.entriesが用意されています。改めて説明する必要はないと思いますが、ちょっと動作を確認してみましょう。
問題ないようですね。それでは、今度はファイル名に日本語を混ぜてみましょう。
森鷗外の名前が「?」に化けてしまっています。これは、Rubyが日本語版Windows環境で使用する外部エンコーディングであるWindows-31Jでは「鷗」の字が表現不可能であるのが原因です。WindowsのファイルシステムであるNTFSではUTF-16LE(厳密には多少異なります)でファイル名をつけることができ、またコマンドプロンプトにおいても同様にUTF-16LEでの入出力が可能です。よって、Windowsの内部コマンドを使っていれば何の問題も起きません。ところが、現状のRubyではファイル名のエンコーディングとしてUTF-16LEではなくWindows-31Jを仮定して動作しているため、この「鷗」のような文字をOSから取得しようとすると、ご覧のような文字化けが発生してしまいます。また、ファイル名にこのような文字を使ってしまうと、RubyからOSに正確なファイル名を渡すこともできなくなってしまうため、openなどのメソッドでファイルを扱うこともできなくなってしまいます。
将来のRubyでは、Ruby M17Nの実装がさらに進み、このような問題も例えばUTF-8やUTF-16LEをエンコーディングとして使用することで回避できるようになる見込みです。しかし、現時点では、直接WindowsのAPIを操作するなどの方法を採らない限りはどうしようもありません。よって、Rubyで操作するようなファイルには不用意に日本語のファイル名をつけたりしないのが安全、ということになります。
分かりやすい地雷――存在しない機能
Rubyは比較的移植性の高い言語処理系です。つまり、それぞれ異なる環境向けのRubyが存在し、そうした環境間の差異をRubyインタプリタが吸収することによって、それぞれの環境の違いによらず、同一のRubyスクリプトを実行できるかもしれないということになります。実際、簡単なファイル操作や文字列処理といった、Rubyが得意とする分野のスクリプトは、入出力のエンコーディングの取り扱えさえ間違えなければ、WindowsでもUNIXでも共通のスクリプトをほとんど問題なくそのまま実行できます。
fork
しかし、どうしてもWindowsでは実現困難な機能もごく一部ではありますが存在します。その代表例がforkメソッドです。ご存知の通り、forkメソッドは現在実行中のプロセスのコピーを子プロセスとして実行するというものです。しかし、通常必要なのは現在のプロセスのコピーではないので、即座にexecメソッドを呼びだして別のコマンドを実行させる、という使い方が主流となっています。
これは、コマンドfooをバックグラウンドで実行し、そのプロセスIDを変数pidに格納する、というスクリプトです。しかし、このスクリプトをmswin32版で実行すると、以下のようにNotImplementedError例外が発生します。
これは、エラーメッセージで説明されている通り、mswin32版Rubyにはforkメソッドが実装されていないためです。
しかし、Ruby 1.9ではこのようなパターンを実現するためにforkメソッドを使う必要はありません。1.9で追加されたメソッドProcess.spawnが使用できます。
よって、そもそもほとんどの場合はforkメソッドを使用する必要はなく、Process.spawnメソッドを使えばよい、ということになります。
File.symlink、File.link
また、UNIX環境では多用される機能であり、Windows環境でも同等の機能が存在するにも拘らず、ほとんど利用されていない、というものも存在します。ファイルやディレクトリに対するシンボリックリンクやハードリンクがそうです。実はこの両方がNTFS上であればWindowsでも使用可能なのですが、シンボリックリンクを作成するFile.symlinkメソッドに関しては現時点ではmswin32版Rubyには実装されていません。
実はシンボリックリンクの作成はWindowsのAPIレベルではWindow Vista以降でしかサポートされておらず、また作成には管理者権限が必要だという制限があります。そのため、Ruby上での実装は様子見という段階です。
一方、ハードリンクを作成するFile.linkメソッドに関しては特に問題なく使用できます。
そのため、どうしてもシンボリックリンクでなければいけない場面以外はハードリンクの使用を考慮してみるべき、ということになります。
ところで、この例ではファイルの同一性を判定するためにFile.identical?メソッドを使用しています。従来UNIX環境向けのスクリプトでファイルの同一性を判定する場合、File.statの結果のdevおよびinoを比較するという手法が多用されていました。しかし、この手法はmswin32版ではinoが常に0になるためにうまくいきません。File.identical?メソッドであれば環境によらずファイルの同一性を判定できることが期待できます。
まとめ
今回のまとめは以下の通りです。
- ファイル名に日本語を使うのは避けましょう。
- 環境に依存した機能については代替手段の有無を確認しましょう。
次回は、もっと困った事例とその対応策を見ていくことにします。