如何从Ruby程序内部调用shell命令?然后如何将这些命令的输出返回到Ruby中?


当前回答

使用这里的答案和Mihai的答案链接,我组合了一个满足这些要求的函数:

整洁地捕获STDOUT和STDERR,因此当我的脚本从控制台运行时,它们不会“泄漏”。 允许参数作为数组传递给shell,因此不需要担心转义。 捕获命令的退出状态,以便在发生错误时显示出来。

作为奖励,当shell命令成功退出(0)并将任何内容放在STDOUT上时,该命令还将返回STDOUT。在这种情况下,它与system不同,后者在这种情况下只返回true。

代码如下。具体的函数是system_quiet:

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"

其他回答

还有一个选择:

当你:

需要stderr和stdout 不能/不会使用Open3/Open4(他们在我的Mac上的NetBeans中抛出异常,不知道为什么)

你可以使用shell重定向:

puts %x[cat bogus.txt].inspect
  => ""

puts %x[cat bogus.txt 2>&1].inspect
  => "cat: bogus.txt: No such file or directory\n"

从MS-DOS的早期开始,2>&1语法就适用于Linux、Mac和Windows。

如果你真的需要Bash,请参考“最佳”答案中的注释。

首先,请注意,当Ruby调用shell时,它通常调用/bin/sh,而不是Bash。在所有系统上/bin/sh不支持某些Bash语法。

如果你需要使用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应该尊重shebang,但你总是可以使用

system("bash ./my_script.sh")

为了确保这一点,尽管/bin/sh运行/bin/bash可能会有轻微的开销,但您可能不会注意到。

您可以使用如下格式方法打印一些信息:

puts format('%s', `ps`)
puts format('%d MB', (`ps -o rss= -p #{Process.pid}`.to_i / 1024))

上面的答案已经非常棒了,但是我真的很想分享下面的总结文章:“在Ruby中运行Shell命令的6种方法”

基本上,它告诉我们:

# exec内核:

exec 'echo "hello $HOSTNAME"'

System和$?:

system 'false' 
puts $?

Backticks ():

today = `date`

IO # execlp:

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>]

下面是一个基于“何时在Ruby中使用启动子进程的每种方法”的流程图。另请参见“欺骗应用程序,使其误以为标准输出是终端,而不是管道”。