Ruby Freaks Lounge

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

この記事を読むのに必要な時間:およそ 4 分

はじめに

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

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

ワンライナーとは

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

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

※1
zshなど,複数行のコマンドラインを指定可能なシェルも存在します。

簡単なワンライナー

それでは,ワンライナーで簡易的な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"と書くこともできます。今回の話であれば,ダブルクォートが使えないわけですが,文字列中に現れない適当な文字を使えば問題を回避できます。

※2
任意の文字と書きましたが,%記法にはその意味を変える特別な指定があるため,アルファベットは避けておくのが無難です。また,例に挙げたように括弧類を使う場合は開き括弧と閉じ括弧で対応が取られます。詳しくはリファレンスマニュアルの%記法の項を参照してください。

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

著者プロフィール

中村宇作(なかむらうさく)

某弱小IT企業の技術担当取締役。2000年末よりRubyコミッタ(mswin32,64担当)。 主にWindows関連の対応とバグ取りとバグ埋めをしています。

コメント

コメントの記入