あれ?xargsって入ってないの?(Rubyで特定のコマンドが存在するかを調べる方法)

xargsやgrepといったコマンドはOSによっては存在しないので、安易にsystem('xargs')とかすると一部環境で動かないプログラムになってしまいます。Windows環境でもCygwinが入っていると使えたりするのでOS種類で判別するのも余りよい方法ではありません。

そこで今の環境でそのコマンドが使えるか?を調べる関数を作りました。Milkodeをgrepを組み合わせて・・で必要だったのです。

exist_command?、その1

関数名をexist_command?(command)という名前にします。まずはLinux, OSX, Windows+Cygwinで動くことを目指してみます。

typeコマンドで確認する方法を思いつきました。

# 指定したコマンドが存在するか?
def exist_command?(command)
  system("type #{command}")
end

Kernel.#systemはコマンドが終了ステータス 0 で終了するとtrueを返し、それ以外の時はfalseを返すので検出出来そうです。

コンソールに結果が出力されてしまうのが嫌

結果は期待通りのものが返ってくるのですが、cat is /bin/catといったtypeのコンソール出力が合わせて出力されてしまいます。exist_command?はチェック用の関数として使いたいので出力は抑制したい所です。

irb(main):010:0> exist_command?('cat')
cat is /bin/cat     # typeの実行結果が出力されてしまう
=> true

exist_command?、改良版

色々調べた結果として今回はOpen3.capture3を使うことにしました。

[標準出力, 標準エラー, プロセスの終了ステータス]の配列で結果が返ってくるのでOpen3.capture3(…)[2]で終了ステータスを取得し、exited?関数で正常終了かをチェックします。typeコマンドが無い時も例外を補足して見つからなかったことにしています。(これは状況によっては違うアプローチになるかも)

# 指定したコマンドが存在するか?
def exist_command?(command)
  begin
    Open3.capture3("type #{command}")[2].exited?
  rescue Errno::ENOENT
    false
  end
end

使い方

# cat, grep, xargs コマンドの存在を確認
if exist_command?('cat') && exist_command?('grep') && exist_command?('xargs')
  system("cat /path/to/file | xargs grep keyword") # あるよ!
else
  puts "Not found command..."
end

実例としては'exist_command?' in milkode 辺りをどうぞ。

Milkodeの使い方としてはgrepとxargsがある時は高速なxargs+grepを使って検索を行い、無い時はRubyの標準ライブラリを使って検索する、ということをするために使っています。

考察

  • このブログ記事のために調べていたら$?で実行ステータスが取れるようなのでKernel.#`を使っても実装出来るかもしれません
  • typeコマンドが存在しない時、常にfalseになってしまうのはもう少し改善の余地がありそうです(他のコマンドも試すなど)

関連文献