Aqutras Members' Blog

株式会社アキュトラスのメンバーが、技術情報などを楽しく書いています。

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

こんにちは。taniyuです。
昨日のAqutrasブログ(brakemanの検査をすり抜けたコードと,その理由の調査)は見られましたか?
あの危険なコードを書いていたのは私でした。
あのような悲劇が今後発生しないようにするために、今回は、Rubyで外部コマンドを
呼び出す方法について調査した結果を説明します。

外部コマンドを実行する方法

Rubyでは、外部コマンドを実行する方法として、以下の様なものがあります。

  1. バッククオート ` を使う
  2. systemを使う
  3. Open3系を使う

このように、いくつか書き方がありますが、一番のおすすめは、3. Open3系を使う です。
具体的な使い方は、以下の様な感じになります。

require 'open3'
# Open3.capture3('コマンド名', 引数)
Open3.capture3('mkdir', 'hoge') # hogeというディレクトリを作る

各方法の違い

続いて各方法の違いについて説明していきます。
それぞれの方法では、コマンドを実行した際に得られる情報や安全性の違いが存在します。

得られる情報について

まず、取得できる情報になります。

方法 標準出力 標準エラー出力 終了コード
バッククオート × △1
system △2 × △1
Open3

△1: $?を参照することで取得できます
△2: 自動で出力されます

表のように、Open3を使う場合は、全ての情報を取得することができます。
それ以外は、標準エラー出力が取得できなかったり、ちょっと特殊な取得方法になります。

安全性について

続いて、安全性についての説明です。

昨日のAqutrasブログ(brakemanの検査をすり抜けたコードと,その理由の調査)のように、
バッククオートを使った方法では、単純に書いてしまった場合、ユーザが任意のコマンドを実行できるという危険な状態になります。
それに対し、systemとOpen3を使う方法の場合、関数名(コマンド, 引数) のように書くことで、引数の部分が自動でエスケープされます。
このため、安全性から考えるとバッククオートを使う方法は良くないですね。

おわりに

今回は、Rubyで外部コマンドを実行する方法をいくつか説明しました。
色々ありますが、コマンドを実行して得られる情報、安全性のことを考えると、
Open3 を使うのが、個人的には一番おすすめです。

その他

一応、shellwordsを使って、文字列を直接エスケープする方法もあります。
その場合は、以下のように書くといけます。

 require 'shellwords'
 `mkdir #{'hoge'.shellescape}`

また、「どこかのディレクトリに移動しつつコマンドを実行したい。」という場合には、
以下のような書き方をすることで実現できます。

require 'open3'
# ディレクトリworkに移動して、test.pyを実行する
Open3.capture3("cd work && python test.py")

変数を使う場合は、以下のような感じにすると安全に実行できます。

require 'open3'
dir = "work"
file_name = "test.py"
Open3.capture3("cd #{dir.shellescape} && python #{file_name.shellescape}")

参考

今回は、以下のブログを参考にさせていただきました。ありがとうございます。