Ruby Freaks Lounge

第10回Windows版Ruby 1.9で培う危機回避スキル(後編)

はじめに

第8回は、Windows版Rubyを題材として、Rubyに埋め込まれた「地雷」をいかに避けていくか、そのさわりを紹介しました。最大の地雷はWindows版Rubyを使うことなんじゃないか、というツッコミが聞こえてきたりもしますが、人間、ちょっとは痛い目に遭っておかないと身につきません。大病を患う前に予防接種を受けておくようなものだと思って我慢してください。

というわけで、後編となる今回も、引き続きWindows版Rubyを題材にして話を進めていきます。今回はコマンドライン引数解釈について重点的に見てみましょう。

ワンライナーとは

そもそも、皆さんがRubyのようなスクリプティング言語を利用する理由は何でしょうか。人によって理由は様々だろうとは思いますが、やはり「手軽であること」という要素は大きいのではないかと思います。コンパイルやリンクなどといった手間を踏むことなく、書いたコードをいきなり実行できるというのは、当たり前すぎて時として忘れられがちではありますが、なかなか大きなメリットなのではないでしょうか。

さて、そんなお手軽なスクリプティング言語ですが、その使い方の中でも最も手軽なのが、ワンライナーと呼ばれるものです。ワンライナーとは、その名の通り、1行で書くプログラムです。ほとんどのスクリプティング言語では、コマンドラインの引数としてプログラムを与えることにより、いちいちファイルにプログラムを書くという一手間を省くことができます。これがワンライナーです。もちろん、コマンドラインの引数なので改行を含めることもできませんし[1]⁠、あまり長いプログラムを書けないという制限もあります。しかし、既存のコマンド類の組み合わせだけではうまく処理できないけれど、わざわざこのためだけにプログラムを1本書き下ろすのもめんどくさい、という程度の作業が発生したときに、スクリプティング言語によるワンライナーが威力を発揮します。特に、今回のターゲット環境であるWindowsの場合、grep・sed・awkに代表される、UNIXユーザーにお馴染みの強力なコマンド類が、標準ではほとんど存在しません。ですが、とりあえずRubyがあれば、これらのコマンドでできるようなことは、ほとんどの場合はRubyのワンライナーで簡単に実現できます。つまり、WindowsでRubyを利用する人たちこそ、ワンライナーの恩恵を最も多く受けられるユーザーだということになるのです。そういえば、前編の実行結果の中でもワンライナーを使用していましたね。

簡単なワンライナー

それでは、ワンライナーで簡易的なgrepのようなものを実現させてみましょう。以下は、フォルダ内のテキストファイル(ここでは拡張子.txtを持つもの)全体から、⁠hello」という文字列を含む行を、大文字小文字を区別せずに抽出するものです。

簡易grepの例(その1)
ruby -n -e 'print if /hello/i' *.txt

皆さんにとっては釈迦に説法かもしれませんが、念のため、簡単に説明しておきましょう。-eオプションは、次に続くコマンドライン引数をRubyプログラムとして実行せよ、という指定です。つまりこれがワンライナーの要です。-nオプションが指定された場合、-eで指示したプログラムを、while gets~endでくくられているかのように実行します。getsメソッドは特殊変数ARGFから1行読み込んで、それを特殊変数$_に格納します。特殊変数ARGFは、通常は標準入力からの読み込みに対応しますが、コマンドライン引数でファイルが指定された場合、それらのファイルを全て結合した仮想的なファイルからの読み込みに対応します。この例ではオプション類の他に*.txtというコマンドライン引数があるので、フォルダ内のテキストファイル全体、ということになります。

残るのは肝心のプログラム本体ですが、if修飾子で正規表現マッチを行い、マッチした入力だけをprintメソッドで出力しています。しかし、このプログラム、ぱっと見ると、なんだかいろいろ足りてないような気もしますね。まず、正規表現マッチの対象となる相手の指定が存在しません。それどころか、お馴染みの正規表現マッチ演算子=~も見当たりません。また、printメソッドにも表示すべき対象が引数として渡されていません。実は、Rubyでは、単に条件式に正規表現のみが渡された場合、暗黙のうちに$_を対象として正規表現マッチが行なわれることになっています。また、printメソッドに引数が渡されなかった場合、これまた暗黙の引数として$_が渡されたものとみなします。先に説明した通り、-nオプションによって$_には読み込まれた行が入っていますので、これだけで期待通りの動作が実現される、というわけです。

なお、これらの$_の特別扱いはPerlから受け継いだものなのですが、現在のRubyではこのような省略はあまり行儀がいいものとはみなされていません。実際、このプログラムを-eオプションを使わずに普通にファイルに書き込んで実行すると、正規表現マッチ部分で警告が出力されます。ですが、ワンライナーの場合はあまり長いプログラムを書けないということもあって、こうした省略が常套手段として利用されます。そのため、-eオプションでプログラムを渡した場合には警告を出力しないようになっています。

ワンライナーで踏む地雷

さて、これで終われば単に「便利なRubyの使い方」なのですが、本稿の目的はそこにはないので、ちゃんと地雷を踏みに行きましょう。とりあえず、せっかく正規表現が使えるのですから、もうちょっと複雑な検索を実行してみることにしましょう。先の例をちょっと変更して、⁠hello」だけでなく「bye」を含む行も抽出してみます。

簡易grepの例(その2)
ruby -n -e 'print if /hello|bye/i' *.txt

見ての通りで、単に正規表現部分を変更しただけです。ところが、これをWindowsのコマンドラインで実行すると、図1のようにエラーになってしまいます。

図1 簡易grepその2の実行結果
図1 簡易grepその2の実行結果

どうしてでしょうか。これは、正規表現中にある「|」がシェル(ここではcmd.exe)のリダイレクト指定として解釈されてしまうのが原因です。⁠|」はいわゆるパイプ指定で、その前で指定されたコマンドの出力を、その後ろで指定されたコマンドの入力として扱います。そのため、helloまでとbyeから後ろとが別々のコマンドの実行であると解釈されているのです。しかし、この例はUNIXにおける一般的なシェルでは問題なく動作します。それらのシェルではシングルクォートでくくられた部分をひとかたまりとみなし、その中身についてシェルに対する指定としては解釈しないためです。しかし、cmd.exeはそれらのシェルとは異なり、シングルクォートに対する特別扱いは存在しません。そのため、上記のようなエラーになってしまいます。

この問題の解決方法自体は簡単です。cmd.exeでは、シングルクォートの代わりにダブルクォートを使えば、期待通り、⁠|」などが含まれてもちゃんとひとかたまりのコマンドライン引数としてコマンドに渡されます。

簡易grepの例(その3)
ruby -n -e "print if /hello|bye/i" *.txt

これでWindowsのコマンドプロンプトでも正しく動作するようになりました。

Ruby関連書籍やサイトなどでも、ワンライナーの例は頻繁に紹介されていますが、多くの場合はUNIX系の環境を対象としているため、プログラム部分はシングルクォートで囲まれて例示されています。Windowsのコマンドプロンプトで実行する場合はダブルクォートに読み替えてください。

……と、これだけで地雷を回避できると思ったら大間違いです。世の中は、というか、Windows環境でのRuby使用はそんなに甘くはありません。以下は、Rubyのリファレンスマニュアルから見つけてきたです。

ワンライナーで小文字を大文字に置き換える例
% echo matz | ruby -p -e '$_.tr! "a-z", "A-Z"'
MATZ

-pオプションの効果についてはリンク先を参照してください。さて、この例では案の定シングルクォートが使用されています。そこで、先ほど学習したようにシングルクォートをダブルクォートに置き換えると、容易に想像できると思いますが、うまく動作しません。

図2 シングルクォートをダブルクォートに変えてみた実行結果
図2 シングルクォートをダブルクォートに変えてみた実行結果

ご覧の通り、プログラム中でもダブルクォートが使われているため、おかしな解釈をされてしまっています。というわけで、この手の例をWindowsのコマンドプロンプトで実行する場合は、シングルクォートをダブルクォートに変えるだけではだめで、逆にダブルクォートをシングルクォートに変える必要もあることになります。

図3 さらにダブルクォートをシングルクォートに変えてみた実行結果
図3 さらにダブルクォートをシングルクォートに変えてみた実行結果

これでうまく行きました。めでたしめでたし……ではありません。ご存知の通り、Rubyプログラムでは、シングルクォートとダブルクォートではどちらも文字列リテラルを意味しますが、その効果が異なります。上記の例では結果は同じですが、バックスラッシュ記法や式展開が必要な場合、ダブルクォートをシングルクォートに置き換えることができません。どうしましょう。

そこで登場するのが、普段は忘れられがちな%記法です。例えば、%(string)と書けば、"string"とダブルクォートでくくって書いたのと同じように扱われます。%の後には任意の文字[2]が使えますので、%!string!と書いてもいいですし、あまり意味はないですが%"string"と書くこともできます。今回の話であれば、ダブルクォートが使えないわけですが、文字列中に現れない適当な文字を使えば問題を回避できます。

というわけで、WindowsのコマンドプロンプトでRubyのワンライナーを利用する場合は、シングルクォートとダブルクォートの取り扱いに十分注意する必要がある、ということがご理解頂けたことと思います。

ちょっと補足

ところで、最初の例をもう一度思い返してみましょう。

簡易grepの例(その1、再掲)
ruby -n -e 'print if /hello/i' *.txt

この例はシングルクォートを使っていましたが、コマンドプロンプト上でも問題なく動作していました。しかし、先ほど書いたように、cmd.exeはシングルクォートを特別扱いしません。ということは、-eオプションの対象としてRubyインタプリタに渡されるプログラムは「print if /hello/i」ではなく、⁠'print」となるはずです。そして残りの部分はプログラムそのものではなくプログラムに対する引数として渡され、おそらくはARGFの対象のファイルとして解釈されるはずです。それなのに、なぜちゃんと期待通りに動作してしまったのでしょうか。

実は、mswin版およびmingw版のRubyでは、コマンドライン引数をRubyインタプリタの中で再解釈し、シングルクォートでくくられた場合もちゃんとひとかたまりの引数として扱っています。そのため、この例のような単純なケースであれば、シングルクォートで引数をくくっても問題なく動作するのです。しかし、⁠|」などのcmd.exeにとって特別な意味を持つ文字が含まれている場合は、Rubyインタプリタに引数が渡される前にcmd.exeがこれを解釈してしまうため、Rubyインタプリタがどんなに頑張ってもどうしようもない、ということになります。

勘のいい人は既にお気づきかと思いますが、先ほどの小文字を大文字に置き換える例は、cmd.exeにとって特別な意味を持つ文字がシングルクォートでくくられた中には含まれていないため、実はそのまま実行可能です。

図4 元々の例をそのまま素直に実行した場合の実行結果
図4 元々の例をそのまま素直に実行した場合の実行結果

地雷を避けようとして、与えられた回避策を機械的に適用しようとすると、このように見事に罠に誘い込まれることになります。問題の原因と回避策の意味をちゃんと把握して、正しく回避策を利用しましょう。

ところで、本稿では長さの都合で触れませんが、コマンドライン引数の解釈については、他にもエスケープの方法やワイルドカードの展開、エンコーディングの問題など、踏みたくない地雷が山のように埋め込まれています。どのような地雷があって、どうすれば避けられるのか、この辺りは皆さんへの自習課題として残しておくことにします。

まとめ

今回のまとめは以下の通りです。

  • UNIX系シェルとcmd.exeの解釈の違いに気をつけましょう。
  • 世間の与えてくれるサンプルコードがそのままWindowsでも実行可能という甘い考えは捨てましょう。
  • 回避策を機械的に適用するのではなく、ちゃんとその意味を理解して自分の頭で考えましょう。
  • 書かれたことが全てではありません。地雷はそこかしこに幾らでも埋め込まれています。

以上、前後編の2回に渡って様々な地雷とその避け方を見てきました。わざわざWindows版Rubyの嫌なところばかりを抽出して並べたため、皆さんにはWindows版Rubyについてかなりネガティブな印象を与えてしまったのではないかと思います。ですが、どんなソフトウェアにも大抵は地雷は埋まっているものです。場当たりに使ってみたあげくに地雷を踏んで、⁠こんなもん使えるか!」と投げ出すのではなく、注意深く事前に危険を察知してうまく立ち回ることができれば、そのソフトウェアが提供する機能をもっと楽しく利用できるようになるのではないでしょうか。本稿がそうしたスキルを培うための一助になっていれば幸いです。

ところで、単なるソフトウェアのユーザーであるならば地雷を踏んでしまうのは不快な経験に過ぎないわけですが、敢えて地雷原に突っ込んで行って片っ端から踏んでみるというのも、またある種の楽しみ方だったりもします。踏んでみた感想をブログなどで愚痴ってネタにするのもいいですし、ソフトウェア作者に送りつけて改善を促すのもいいでしょう。幸運にもそのソフトウェアがオープンソースであるならば、そこからさらに踏み込んで、自分で地雷を撤去することも可能です。そしてそのパッチを作者にフィードバックすれば、あなたも立派にオープンソース開発者の仲間入りです。地雷原の中の道なき道を踏破してみるのも、心の持ちようによってはかなり楽しめる体験となりえます。気が向いたら勇気を出して踏み込んでみてください。

おすすめ記事

記事・ニュース一覧