335

如果我在 Ruby 中使用Kernel#system调用命令,我如何获得它的输出?

system("ls")
4

16 回答 16

372

我想稍微扩展和澄清混乱的答案。

如果你用反引号包围你的命令,那么你根本不需要(明确地)调用 system() 。反引号执行命令并将输出作为字符串返回。然后,您可以将值分配给一个变量,如下所示:

output = `ls`
p output

或者

printf output # escapes newline chars
于 2009-10-14T00:06:15.900 回答
251

请注意,将包含用户提供的值的字符串传递给等的所有解决方案system都是%x[]不安全的!不安全实际上意味着:用户可以触发代码在上下文中运行,并具有程序的所有权限。

据我所知,system并且Open3.popen3确实在 Ruby 1.8 中提供了一个安全/转义变体。在 Ruby 1.9IO::popen中也接受一个数组。

只需将每个选项和参数作为数组传递给这些调用之一。

如果您不仅需要退出状态,还需要您可能想要使用的结果Open3.popen3

require 'open3'
stdin, stdout, stderr, wait_thr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets(nil)
stdout.close
stderr.gets(nil)
stderr.close
exit_code = wait_thr.value

请注意,块形式将自动关闭标准输入、标准输出和标准错误——否则它们必须被显式关闭

更多信息在这里:在 Ruby 中形成卫生 shell 命令或系统调用

于 2011-05-11T21:25:34.450 回答
177

只是为了记录,如果你想要(输出和操作结果)你可以这样做:

output=`ls no_existing_file` ;  result=$?.success?
于 2010-10-17T00:10:39.780 回答
79

正确且安全地执行此操作的直接方法是使用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

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

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

麻烦的是,它返回退出代码而不是输出,并且捕获后者是复杂和混乱的。

到目前为止,该线程中的最佳答案提到了 Open3,但没有提到最适合该任务的功能。Open3.capture2,capture2ecapture3像 一样工作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')
于 2013-11-15T12:53:05.603 回答
62

您可以使用 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[..] 之间的区别。

于 2010-11-25T16:04:59.043 回答
25

如果您需要转义参数,在 Ruby 1.9 中IO.popen也接受一个数组:

p IO.popen(["echo", "it's escaped"]).read

在早期版本中,您可以使用Open3.popen3

require "open3"

Open3.popen3("echo", "it's escaped") { |i, o| p o.read }

如果您还需要传递标准输入,这应该适用于 1.9 和 1.8:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"
于 2012-07-04T10:36:15.203 回答
21

您使用反引号:

`ls`
于 2009-03-27T15:17:37.750 回答
20

另一种方法是:

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

请注意,这是打开时“ls”之前的“管道”字符。这也可用于将数据输入程序标准输入以及读取其标准输出。

于 2009-03-27T15:23:20.810 回答
15

如果您需要返回值,我发现以下内容很有用:

result = %x[ls]
puts result

我特别想列出我机器上所有 Java 进程的 pid,并使用了这个:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
于 2010-03-02T02:11:23.757 回答
14

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

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

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

此方法使用临时文件捕获给定块中的任何输出以存储实际数据。示例用法:

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

您可以用system内部调用的任何内容替换调用system。如果需要,您也可以使用类似的方法进行捕获stderr

于 2013-05-16T22:47:47.690 回答
14

正如Simon Hürlimann 已经解释的那样,Open3比反引号等更安全。

require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }

请注意,块形式将自动关闭标准输入、标准输出和标准错误——否则它们必须被显式关闭

于 2014-01-12T05:20:05.277 回答
10

如果您希望使用 将输出重定向到文件Kernel#system,您可以像这样修改描述符:

以附加模式将 stdout 和 stderr 重定向到文件(/tmp/log):

system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])

对于长时间运行的命令,这将实时存储输出。您还可以使用 IO.pipe 存储输出并将其从 Kernel#system 重定向。

于 2013-05-26T04:58:41.497 回答
7

作为直接的 system(...) 替代品,您可以使用 Open3.popen3(...)

进一步讨论: http ://tech.natemurray.com/2007/03/ruby-shell-commands.html

于 2010-03-04T14:24:58.507 回答
1

将标准输出捕获到名为val的变量中的最简单解决方案:

val = capture(:stdout) do
  system("pwd")
end

puts val

缩短版:

val = capture(:stdout) { system("ls") }

捕获方法由 active_support/core_ext/kernel/reporting.rb 提供

同样,我们也可以捕获标准错误:stderr

于 2021-12-06T19:00:10.277 回答
0

我在这里没有找到这个,所以添加它,我在获得完整输出时遇到了一些问题。

如果要使用反引号捕获 STDERR,可以将 STDERR 重定向到 STDOUT。

输出 = `grep hosts /private/etc/* 2>&1`

来源:http ://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html

于 2017-02-27T15:39:15.493 回答
-1
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
于 2016-03-08T02:35:52.040 回答