Rubyからシェルコマンドを呼び出す方法


1077

Rubyプログラムの内部からシェルコマンドを呼び出すにはどうすればよいですか?これらのコマンドからの出力をRubyに戻すにはどうすればよいですか?


3
この質問は役に立ちますが、よく聞かれません。Rubyには、十分に文書化されており、カーネルOpen3のドキュメントを読んでSOで検索することで簡単に見つけられるサブシェルを呼び出す多くの方法があります。
ティンマン

1
悲しいことに、このトピックは非常に複雑です。Open3docs)はほとんどの状況でIMOに最適ですが、Rubyの古いバージョンでは、変更PATHbugs.ruby-lang.org/issues/8004)を考慮せず、引数の渡し方(特に、キーワード以外でoptsハッシュを使用した場合)、破損する可能性があります。しかし、これらの状況に遭遇した場合は、かなり高度なことを行っているので、の実装を読むことで何をすべきかを理解できますOpen3
ジョシュアチーク

3
誰も言及していないことに驚いていますShellwords.escapedoc)。ユーザー入力をシェルコマンドに直接挿入したくない場合は、まずエスケープしてください。コマンドインジェクションも参照してください。
ケルビン

回答:


1319

この説明は、私の友人からのコメント付きのRubyスクリプトに基づいています。スクリプトを改善したい場合は、リンクから自由に更新してください。

まず、Rubyがシェルを呼び出すとき、通常はBash /bin/shではなくを呼び出すことに注意してください。一部のBash構文は/bin/sh、すべてのシステムでサポートされているわけではありません。

シェルスクリプトを実行する方法は次のとおりです。

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` 、一般的にバックティックと呼ばれる– `cmd`

    これは、Bash、PHP、Perlなど、他の多くの言語と同じです。

    シェルコマンドの結果(つまり、標準出力)を返します。

    ドキュメント:http : //ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
  2. 組み込み構文、 %x( cmd )

    x文字の後には区切り文字があり、任意の文字にすることができます。区切り文字が、、、またはのいずれかの文字(である[場合{<リテラルは、ネストされた区切り文字のペアを考慮して、一致する終了区切り文字までの文字で構成されます。その他すべての区切り文字の場合、リテラルは、次に出現する区切り文字までの文字で構成されます。文字列補間#{ ... }が許可されています。

    バックティックと同様に、シェルコマンドの結果(つまり、標準出力)を返します。

    ドキュメント:https : //docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
  3. Kernel#system

    サブシェルで指定されたコマンドを実行します。

    trueコマンドが見つかって正常に実行されたかどうかを返しますfalse。それ以外の場合は返します。

    ドキュメント:http : //ruby-doc.org/core/Kernel.html#method-i-system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
  4. Kernel#exec

    指定された外部コマンドを実行して現在のプロセスを置き換えます。

    noneを返します。現在のプロセスは置き換えられ、続行されません。

    ドキュメント: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

詳細については、以下を参照してください。


4
実行可能ファイルの出力を本番サーバーに記録する必要がありますが、方法が見つかりません。puts #{cmd}とlogger.info(#{cmd})を使用しました。本番環境で出力を記録する方法はありますか?
Omer Aslam

5
そしてIO#popen()とOpen3#popen3()。mentalized.net/journal/2010/03/08/...
hughdbrown

6
完全を期すために(最初にこれはRubyコマンドでもあると思ったので):Rakeshを実行し、「システムコマンドを実行しますcmd。複数の引数が指定された場合、コマンドはシェルで実行されません(カーネルと同じセマンティクス:: execおよびKernel :: system)」。
sschuberth 2013

40
デフォルトでは、バックティックはSTDERRをキャプチャしません。キャプチャしたい場合は、コマンドに「2>&1」を
追加し

14
特定のコマンドの「結果」ではなく、「出力」がバッククォートと%xから返された場合、この回答は少し改善されると思います。後者は終了ステータスと間違われる可能性があります。それとも私だけですか?
skagedal 2015

275

24
すごいね。これが存在しなければならないという事実は残念ですが、非常に便利です
Josh Bodah

注意点として、私は卵()メソッドを見つけ例えば(さまざまな場所で見られるKernelし、Process最も汎用性のあることをそれは多かれ少なかれ同じであります。PTY.spawn()しかし、より一般的な。
SMAR

160

私がこれを行うのが好きな方法は、%xリテラルを使用することです。これにより、次のように、コマンドで引用符を簡単に(そして読みやすく)使用できます。

directorylist = %x[find . -name '*test.rb' | sort]

この場合、ファイルリストに現在のディレクトリの下にあるすべてのテストファイルが読み込まれ、期待どおりに処理できます。

directorylist.each do |filename|
  filename.chomp!
  # work with file
end

4
DOESは%x[ cmd ]あなたに、配列を返しますか?
x-yuri 14年

2
上記は私にはうまくいきません。`` <main> ':未定義のメソッドはeach' for :String (NoMethodError) どのように機能しましたか?私が使用していますruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]ループが実際に動作するようにあなたは必ず配列がコマンドから返されていますか?
Nasser

%x [cmd] .split( "\ n")でもリストが返されます:)
Ian Ellis

65

Rubyでのシェルスクリプトの実行に関する私の意見の中で最も優れた記事を次に示します。 " Rubyでシェルコマンドを実行する6つの方法」です。

出力のみを取得する必要がある場合は、バックティックを使用します。

STDOUTやSTDERRなどのより高度なものが必要だったので、Open4 gemを使用しました。そこで説明されているすべてのメソッドがあります。


2
ここで説明する投稿では、%x構文オプションについては説明していません。
Mei、

Open4の場合は+1。spawnこれを見つけたとき、私はすでに自分のバージョンのメソッドの実装を試み始めていました。
Brandan '19

40

私のお気に入りはOpen3です

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }

2
私はまた、open3、特にOpen3.capture3が好きです:ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/…- > stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin

Ruby std-libでOpen3や他のOpenを使用して仕様およびユニットテストを実行する方法に関するドキュメントはありますか?私の現在の理解レベルでシェルアウトをテストするのは難しいです。
FilBot3 2016年

29

これらのメカニズムのどちらかを選択する際に考慮すべき点は次のとおりです。

  1. stdoutが必要ですか、それともstderrも必要ですか?それとも別れ?
  2. あなたの出力はどのくらいですか?結果全体をメモリに保持しますか?
  3. サブプロセスの実行中に出力の一部を読み取りますか?
  4. 結果コードが必要ですか?
  5. プロセスを表し、必要に応じてプロセスを強制終了できるRubyオブジェクトが必要ですか?

単純なバッククォート( ``)、、system()そしてIO.popen本格的なKernel.fork/ Kernel.execwith IO.pipeIO.select

サブプロセスの実行に時間がかかりすぎる場合は、タイムアウトをミックスに投入することもできます。

残念ながら、それは非常に依存しています。


25

もう1つのオプション:

あなたが:

  • stderrとstdoutが必要
  • 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構文は全体で作品のLinux、MacとWindowsの MS-DOSの初期の頃から。


25

私は間違いなくRubyの専門家ではありませんが、試してみることにします。

$ irb 
system "echo Hi"
Hi
=> true

次のようなこともできるはずです。

cmd = 'ls'
system(cmd)

21

上記の回答はすでに非常に優れていますが、次の要約記事を共有したいと思います。「Rubyでシェルコマンドを実行する6つの方法

基本的に、それは私たちに伝えます:

Kernel#exec

exec 'echo "hello $HOSTNAME"'

system$?

system 'false' 
puts $?

バックティック( `):

today = `date`

IO#popen

IO.popen("date") { |f| puts f.gets }

Open3#popen3 -stdlib:

require "open3"
stdin, stdout, stderr = Open3.popen3('dc') 

Open4#popen4 -宝石:

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]

15

本当にBashが必要な場合は、「最良の」回答の注記に従ってください。

まず、Rubyがシェルを呼び出すとき、通常はBash /bin/shではなくを呼び出すことに注意してください。一部のBash構文は/bin/sh、すべてのシステムでサポートされているわけではありません。

Bashを使用する必要がある場合bash -c "your Bash-only command"は、目的の呼び出しメソッド内に挿入します。

quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")

テストする:

system("echo $SHELL")
system('bash -c "echo $SHELL"')

または、次のような既存のスクリプトファイルを実行している場合

script_output = system("./my_script.sh")

Ruby シバン尊重すべきですが、いつでも使用できます

system("bash ./my_script.sh")

念のため、を/bin/sh実行すると多少のオーバーヘッドが生じる可能性がありますが、/bin/bashおそらく気付かないでしょう。


11

Perlと同様に、バックティック演算子( `)を使用することもできます。

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory

シンプルなものが必要な場合に便利です。

どちらの方法を使用するかは、実行しようとしていることに完全に依存します。さまざまな方法の詳細については、ドキュメントを確認してください。


10

複数の方法でそれを達成できます。

を使用するとKernel#exec、このコマンドの実行後は何も行われません。

exec('ls ~')

使用する backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"

Kernel#systemコマンドを使用trueして、成功したfalse場合は戻りnil、失敗した場合は戻り、コマンドの実行が失敗した場合は戻ります。

system('ls ~')
=> true


9

ここでの回答を使用し、Mihaiの回答にリンクして、これらの要件を満たす関数をまとめました。

  1. STDOUTとSTDERRをきちんとキャプチャして、コンソールからスクリプトを実行したときに「リーク」しないようにします。
  2. 引数を配列としてシェルに渡すことができるため、エスケープについて心配する必要はありません。
  3. コマンドの終了ステータスをキャプチャして、エラーが発生したときにそれを明確にします。

おまけとして、これは、シェルコマンドが正常に終了し(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"

9

spawn指定したコマンドを実行するためのバックグラウンドプロセスを作成するコマンドを忘れないでください。Processクラスと返されたを使用して、その完了を待つこともできますpid

pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid

pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
Process.wait pid

docは言う:このメソッドは似て#systemいますが、コマンドが完了するのを待ちません。


2
Kernel.spawn()他のすべてのオプションよりもはるかに用途が広いようです。
Kashyap 2015

6

で処理できない一般的なケースよりも複雑なケースがある場合は、``チェックアウトしてください Kernel.spawn()。これは、外部コマンドを実行するために標準のRubyによって提供される最も一般的/フル機能のようです。

次の目的で使用できます。

  • プロセスグループを作成します(Windows)。
  • エラーをファイル/その他にリダイレクトします。
  • 環境変数、umaskを設定します。
  • コマンドを実行する前にディレクトリを変更してください。
  • CPU /データ/などのリソース制限を設定します。
  • 他の回答で他のオプションを使用して実行できるすべてのことを行いますが、より多くのコードを使用します。

Rubyのドキュメントは十分に良い例があります。

env: hash
  name => val : set the environment variable
  name => nil : unset the environment variable
command...:
  commandline                 : command line string which is passed to the standard shell
  cmdname, arg1, ...          : command name and one or more arguments (no shell)
  [cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
options: hash
  clearing environment variables:
    :unsetenv_others => true   : clear environment variables except specified by env
    :unsetenv_others => false  : dont clear (default)
  process group:
    :pgroup => true or 0 : make a new process group
    :pgroup => pgid      : join to specified process group
    :pgroup => nil       : dont change the process group (default)
  create new process group: Windows only
    :new_pgroup => true  : the new process is the root process of a new process group
    :new_pgroup => false : dont create a new process group (default)
  resource limit: resourcename is core, cpu, data, etc.  See Process.setrlimit.
    :rlimit_resourcename => limit
    :rlimit_resourcename => [cur_limit, max_limit]
  current directory:
    :chdir => str
  umask:
    :umask => int
  redirection:
    key:
      FD              : single file descriptor in child process
      [FD, FD, ...]   : multiple file descriptor in child process
    value:
      FD                        : redirect to the file descriptor in parent process
      string                    : redirect to file with open(string, "r" or "w")
      [string]                  : redirect to file with open(string, File::RDONLY)
      [string, open_mode]       : redirect to file with open(string, open_mode, 0644)
      [string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
      [:child, FD]              : redirect to the redirected file descriptor
      :close                    : close the file descriptor in child process
    FD is one of follows
      :in     : the file descriptor 0 which is the standard input
      :out    : the file descriptor 1 which is the standard output
      :err    : the file descriptor 2 which is the standard error
      integer : the file descriptor of specified the integer
      io      : the file descriptor specified as io.fileno
  file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
    :close_others => false : inherit fds (default for system and exec)
    :close_others => true  : dont inherit (default for spawn and IO.popen)

6

バックティック( `)メソッドは、Rubyからシェルコマンドを呼び出す最も簡単なメソッドです。シェルコマンドの結果を返します。

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`

5

次のようなコマンドがあるとしattribます。

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end

この方法は思い出に残るものではありませんが、

system("thecommand")

または

`thecommand`

バックティックでは、他のメソッドと比較してこのメ​​ソッドの良い点は、バックティックではputs、実行したいコマンドを実行したり、変数に実行したいコマンドを格納したりできないように見えることです。system("thecommand")、出力を取得しできないように見えることです。この方法では、これらの両方を行うことができ、stdin、stdout、およびstderrに個別にアクセスできます。

Rubyでのコマンドの実行」およびRubyのOpen3ドキュメントを参照してください。


3

これは実際の答えではありませんが、多分誰かがそれを便利だと思うでしょう:

WindowsでTK GUIを使用していて、rubywからシェルコマンドを呼び出す必要がある場合、常に1秒未満しかポップアップしない煩わしいCMDウィンドウが表示されます。

これを回避するには、以下を使用できます。

WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)

または

WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)

どちらもipconfig内部log.txtに出力を保存しますが、ウィンドウは表示されません。

require 'win32ole'スクリプト内に入れる必要があります。

system()exec()およびspawn()TKとrubywを使用すると、迷惑なウィンドウがすべてポップアップします。


-2

OS Xのルビースクリプトで使用するクールなスクリプトを次に示します(ウィンドウから切り替えた後でもスクリプトを開始して更新を取得できるようにするため)。

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.