mcommit's message

大阪でソフトウェア開発の仕事をしている simotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

Ruby CGI 外部コマンドの標準出力と戻り値を取得する

仕事でCGIRubyで書いていてはまったので書いておきます。※Rubyのバージョンは1.8.7


Rubyで外部コマンドを実行する方法は、

1).system関数を使う


ret = system('ls')
puts ret #=> true

この場合 実行したコマンド(ここではls)の結果は標準出力され、
ret には コマンドの処理結果が true/false で入ります。

2).exec関数を使う


ret = exec('ls')
puts ret #=> true

この場合も結果は基本的に 1).のsystem関数を使用した場合と同じです。
execはRubyの例外を捕まえることが特徴らしいですが、標準出力と戻り値に関して言えばsystem関数と同じです。

3).`実行コマンド名`を使う


ret = `ls`
puts ret #=> lsの結果

この場合 ret にはlsの結果が入ります。コマンドの結果は取得されません。
※ちなみにこの場合、実行するコマンド名を変数で持っている場合は

# コマンド文字列
cmd = 'ls'

# コマンド実行
ret =`#{cmd}`

とするそうです。


さてここからが本題なのですが、
CGIとして実行するRubyスクリプトで、外部コマンドを実行する場合
1),2)のやり方だとうまく動いてくれません。

CGIは標準出力をHTTPのレスポンスとして使用するため、外部コマンドでの標準出力によって
本来レスポンスを返すために使用する標準出力がつぶされてしまうからだと思います。
※私が試した環境(Apache2)では Internal Server Error 500が出ていました。

ちなみ外部コマンドが標準出力しない場合は 1), 2)のやり方で問題ありません。


で、CGI用のスクリプト内で、外部コマンドを実行して、
 ・標準出力
 ・処理の結果
の両方を取得する必要がある場合ですが、以下のように書いたらうまくいきました。


stdout =`ls`
ret = $?

stdout には ls の結果が格納されます。
$? には最後に実行したプロセスの結果が格納されるらしいです。(つまりここでは、lsの処理結果)

ネットや書籍を見ていると、system関数実行後に $? で結果を取得したりしている例はありましたが
`ls` のようにバッククォートで外部コマンドを実行した後に $? で結果を取得している例はありませんでした。
なので勝手に `コマンド`で外部コマンドを実行した後は、 $? は使ってはいけない。みたいに思い込んでいました。

ちなみに $? は true/false ではなくプロセスの終了コードです。

実は、今回私が実行したかったのはコマンドではなくシェルスクリプトだったのですが、
$? には シェルスクリプトの exit コードが格納されます。


#!/bin/bash

exit 0

のようなシェルを上記のように呼び出すと、 $? には 0が入ります。


#!/bin/bash

exit 1

だと $? には 256が入ります。
※終了コードは8ビットシフトされるためらしいです。

RubyCGIを書く人がそもそもすくないのか、今回の情報はあまりヒットしませんでした。
※私が無知なだけかもしれませんが。。。

似たようなことをされる方はご参考にしてください。


話は変わりますが、Rubyでコードを書くのにもだいぶ慣れてきたのですが、
なれるとRubyって本当に楽しいです。
※2〜3年前からぼちぼちと触ってはいたのですが、本格的に触りだしたのは最近です。

普段はC言語ばかり書いているのですが、スクリプト言語の手軽さとRuby独特の明快さは
C言語にはない特徴ですね!

これからもRubyを使い込んでいきたいと思います。

ちなみに今回の記事のネタのなったCGIを書く際にはRubyのレシピブックを参考に書いていたのですが、記事のような外部コマンドと標準出力の戻り値を取得するというレシピは載っていませんでした。※RubyCGIを書く人ってあんまりいないのかな・・・

Rubyレシピブック 第2版 268の技

Rubyレシピブック 第2版 268の技


どの言語でも学習し始めはこういったレシピブックやHow to本が手元にあると「やりたい事の実現」までに回り道しなくてよいので助かります。