如果我调用一个命令使用内核#系统在Ruby中,我如何得到它的输出?

system("ls")

当前回答

另一种方法是:

f = open("|ls")
foo = f.read()

注意,这是open中“ls”前面的“pipe”字符。这也可用于将数据送入程序的标准输入以及读取其标准输出。

其他回答

您可以使用system()或%x[],这取决于您需要什么样的结果。

System()如果找到命令并成功运行,则返回true,否则返回false。

>> s = system 'uptime'
10:56  up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
>> s.class
=> TrueClass
>> $?.class
=> Process::Status

% x [. .]另一方面,将命令的结果保存为字符串:

>> result = %x[uptime]
=> "13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> p result 
"13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> result.class
=> String

Jay Fields的博客文章详细解释了system、exec和%x[..]]。

你可以用反勾号:

`ls`

正确而安全地做到这一点的简单方法是使用Open3.capture2()、Open3.capture2e()或Open3.capture3()。

如果与不受信任的数据一起使用ruby的反刻度号和它的%x别名在任何情况下都是不安全的。这很危险,简单明了:

untrusted = "; date; echo"
out = `echo #{untrusted}`                              # BAD

untrusted = '"; date; echo"'
out = `echo "#{untrusted}"`                            # BAD

untrusted = "'; date; echo'"
out = `echo '#{untrusted}'`                            # BAD

相反,如果使用正确,系统函数会正确转义参数:

ret = system "echo #{untrusted}"                       # BAD
ret = system 'echo', untrusted                         # good

问题是,它返回的是退出代码而不是输出,捕获后者是复杂而混乱的。

到目前为止,这篇文章中最好的答案提到了Open3,但没有提到最适合这项任务的函数。Open3。Capture2, capture2e和capture3的工作方式类似system,但返回两个或三个参数:

out, err, st = Open3.capture3("echo #{untrusted}")     # BAD
out, err, st = Open3.capture3('echo', untrusted)       # good
out_err, st  = Open3.capture2e('echo', untrusted)      # good
out, st      = Open3.capture2('echo', untrusted)       # good
p st.exitstatus

另一个提到了IO.popen()。从它想要一个数组作为输入的意义上来说,语法可能是笨拙的,但它也可以工作:

out = IO.popen(['echo', untrusted]).read               # good

为了方便起见,你可以将Open3.capture3()包装在一个函数中,例如:

#
# Returns stdout on success, false on failure, nil on error
#
def syscall(*cmd)
  begin
    stdout, stderr, status = Open3.capture3(*cmd)
    status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol
  rescue
  end
end

例子:

p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')

产生如下结果:

nil
nil
false
false
/usr/bin/which         <— stdout from system('which', 'which')
true                   <- p system('which', 'which')
"/usr/bin/which"       <- p syscall('which', 'which')
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0

虽然使用反勾号或popen通常是您真正想要的,但它实际上并不能回答所提出的问题。可能有捕获系统输出的正当理由(可能是为了自动化测试)。我在谷歌上搜索了一下,找到了一个答案,我想把它贴在这里,以造福他人。

因为我需要这个来测试我的例子使用块设置来捕获标准输出,因为实际的系统调用隐藏在被测试的代码中:

require 'tempfile'

def capture_stdout
  stdout = $stdout.dup
  Tempfile.open 'stdout-redirect' do |temp|
    $stdout.reopen temp.path, 'w+'
    yield if block_given?
    $stdout.reopen stdout
    temp.read
  end
end

该方法使用tempfile存储实际数据来捕获给定块中的任何输出。使用示例:

captured_content = capture_stdout do
  system 'echo foo'
end
puts captured_content

您可以将系统调用替换为任何内部调用系统的东西。如果需要,还可以使用类似的方法来捕获stderr。