Ruby プログラム内からシェル コマンドを呼び出すにはどうすればよいですか? これらのコマンドからの出力をRubyに戻すにはどうすればよいですか?
22 に答える
この説明は、私の友人がコメントしたRuby スクリプトに基づいています。スクリプトを改善したい場合は、リンクで自由に更新してください。
まず、Ruby がシェルを呼び出す場合、通常はBash/bin/sh
ではなくを呼び出すことに注意してください。一部の Bash 構文は/bin/sh
、すべてのシステムでサポートされていません。
シェルスクリプトを実行する方法は次のとおりです。
cmd = "echo 'hi'" # Sample string that can be used
Kernel#`
、一般にバックティックと呼ばれる –`cmd`
これは、Bash、PHP、Perl など、他の多くの言語と同様です。
シェル コマンドの結果 (つまり、標準出力) を返します。
ドキュメント: http://ruby-doc.org/core/Kernel.html#method-i-60
value = `echo 'hi'` value = `#{cmd}`
組み込み構文、
%x( cmd )
文字の後に
x
は区切り文字があり、任意の文字を使用できます。区切り文字が文字(
、[
、{
、またはのいずれか<
である場合、リテラルは、ネストされた区切り文字のペアを考慮して、一致する終了区切り文字までの文字で構成されます。他のすべての区切り文字の場合、リテラルは区切り文字が次に現れるまでの文字で構成されます。文字列の補間#{ ... }
が許可されています。バッククォートと同様に、シェル コマンドの結果 (つまり、標準出力) を返します。
ドキュメント: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings
value = %x( echo 'hi' ) value = %x[ #{cmd} ]
Kernel#system
指定されたコマンドをサブシェルで実行します。
true
コマンドが見つかって正常に実行された場合は返し、false
そうでない場合は返します。ドキュメント: http://ruby-doc.org/core/Kernel.html#method-i-system
wasGood = system( "echo 'hi'" ) wasGood = system( cmd )
Kernel#exec
指定された外部コマンドを実行して、現在のプロセスを置き換えます。
何も返しません。現在のプロセスは置き換えられ、続行されません。
ドキュメント: http://ruby-doc.org/core/Kernel.html#method-i-exec
exec( "echo 'hi'" ) exec( cmd ) # Note: this will never be reached because of the line above
追加のアドバイスを次に
示します$?
。 は と同じ$CHILD_STATUS
ですが、バッククォートを使用すると、最後にシステムで実行されたコマンドのステータスにアクセスできます。system()
または%x{}
. exitstatus
その後、およびpid
プロパティにアクセスできます。
$?.exitstatus
詳細については、次を参照してください。
「 Ruby でサブプロセスを起動する各メソッドをいつ使用するか」に基づいたフローチャートを次に示します。「標準出力がパイプではなく端末であるとアプリケーションに思わせる」も参照してください。
私がこれを行うのが好きな方法は、%x
リテラルを使用することです。これにより、次のように、コマンドで引用符を簡単に (そして読みやすく!) 使用できます。
directorylist = %x[find . -name '*test.rb' | sort]
この場合、現在のディレクトリの下にあるすべてのテスト ファイルがファイル リストに入力され、期待どおりに処理できます。
directorylist.each do |filename|
filename.chomp!
# work with file
end
Ruby でシェル スクリプトを実行することについて、私の意見では最高の記事は次のとおりです。「Ruby でシェル コマンドを実行する 6 つの方法」。
出力のみを取得する必要がある場合は、バックティックを使用します。
STDOUT や STDERR などのより高度な機能が必要だったので、Open4 gem を使用しました。そこにすべての方法が説明されています。
私のお気に入りはOpen3です
require "open3"
Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
これらのメカニズムから選択する際に考慮すべき点は次のとおりです。
- stdoutが必要ですか、それともstderrも必要ですか?それとも分離しましたか?
- あなたの出力はどれくらいの大きさですか?結果全体をメモリに保持しますか?
- サブプロセスの実行中に出力の一部を読み取りますか?
- 結果コードが必要ですか?
- プロセスを表し、オンデマンドで強制終了できるRubyオブジェクトが必要ですか?
system()
単純なバッククォート( ``) 、、IO.popen
から本格的なKernel.fork
/ withandまで何でも必要になる場合がKernel.exec
ありIO.pipe
ますIO.select
。
サブプロセスの実行に時間がかかりすぎる場合は、タイムアウトをミックスにスローすることもできます。
残念ながら、それは非常に異なります。
私は間違いなく Ruby の専門家ではありませんが、試してみます。
$ irb
system "echo Hi"
Hi
=> true
次のようなこともできるはずです。
cmd = 'ls'
system(cmd)
もう 1 つのオプション:
次の場合:
- stdout だけでなく stderr も必要
- Open3/Open4 を使用できない/使用しない (Mac の NetBeans で例外がスローされる、理由がわからない)
シェル リダイレクトを使用できます。
puts %x[cat bogus.txt].inspect
=> ""
puts %x[cat bogus.txt 2>&1].inspect
=> "cat: bogus.txt: No such file or directory\n"
この2>&1
構文は、MS-DOS の初期の頃から、Linux、Mac、およびWindowsで機能します。
Perl と同様に、バッククォート演算子 (`) を使用することもできます。
directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
シンプルなものが必要な場合に便利です。
どの方法を使用するかは、何を達成しようとしているかによって異なります。さまざまな方法の詳細については、ドキュメントを確認してください。
複数の方法でそれを達成できます。
を使用するKernel#exec
と、このコマンドが実行された後は何も実行されません。
exec('ls ~')
使用するbackticks or %x
`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"
Kernel#system
コマンドを使用するとtrue
、成功したfalse
場合は戻り、失敗した場合は戻りnil
、コマンドの実行が失敗した場合は戻ります。
system('ls ~')
=> true
ここの回答とMihaiの回答にリンクされているものを使用して、これらの要件を満たす関数をまとめました。
- STDOUT と STDERR をきちんとキャプチャするので、コンソールからスクリプトを実行しても「リーク」しません。
- 引数を配列としてシェルに渡すことができるため、エスケープについて心配する必要はありません。
- エラーが発生したときに明確になるように、コマンドの終了ステータスを取得します。
おまけとして、これはシェルコマンドが正常に終了した場合 (0) に STDOUT を返し、STDOUT に何かを置きます。system
このように、このような場合に単純に戻る とは異なりますtrue
。
コードは次のとおりです。具体的な機能はsystem_quietly
次のとおりです。
require 'open3'
class ShellError < StandardError; end
#actual function:
def system_quietly(*cmd)
exit_status=nil
err=nil
out=nil
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thread|
err = stderr.gets(nil)
out = stdout.gets(nil)
[stdin, stdout, stderr].each{|stream| stream.send('close')}
exit_status = wait_thread.value
end
if exit_status.to_i > 0
err = err.chomp if err
raise ShellError, err
elsif out
return out.chomp
else
return true
end
end
#calling it:
begin
puts system_quietly('which', 'ruby')
rescue ShellError
abort "Looks like you don't have the `ruby` command. Odd."
end
#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"