在Ruby中获取system()调用的输出


309

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

system("ls")


这是一个非常手工的线程,谢谢。在示例代码中,用于运行命令和获取反馈的类很棒。
ylluminate 2011年

3
对于未来的谷歌人。如果您想了解其他系统命令调用及其区别,请参阅SO答案
乌兹别克斯坦'16

Answers:


347

我想扩大和澄清混乱的答案

如果您在命令中加上反引号,则根本不需要(明确地)调用system()。反引号执行命令并以字符串形式返回输出。然后可以将值分配给变量,如下所示:

output = `ls`
p output

要么

printf output # escapes newline chars

4
如果我需要在命令中提供变量怎么办?也就是说,当使用反引号时,类似于system(“ ls” + filename)的东西会转换成什么?
Vijay Dev

47
您可以像使用常规字符串一样进行表达式求值:ls #{filename}
克雷格·沃克

36
这个答案是不明智的:它引入了未经处理的用户输入的新问题。
Dogweather '04 -4-2

4
@Dogweather:也许是这样,但这与其他方法有什么不同吗?
Craig Walker

20
如果要使stderr变长,只需在命令末尾添加2>&1。例如,输出=command 2>&1
2012年

243

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

据我只能说systemOpen3.popen3确实在Ruby 1.8中提供了安全/转义变体。在Ruby 1.9中IO::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

请注意,块形式将自动关闭stdin,stdout和stderr,否则必须显式关闭它们

此处提供更多信息:在Ruby中形成卫生外壳命令或系统调用


26
这是实际回答问题并解决问题而又不引入新答案(未经处理的输入)的唯一答案。
Dogweather

2
谢谢!这就是我所希望的答案。一个更正:gets调用应传递参数nil,否则我们将只获得输出的第一行。因此,例如stdout.gets(nil)
格雷格价格

3
应该以非块形式显式关闭 stdin,stdout和stderr 。
Yarin 2014年

有人知道Ruby 2.0或2.1是否有所改变?编辑或评论将不胜感激;-)
SimonHürlimann2014年

1
我认为周围的讨论Open3.popen3遗漏了一个主要问题:如果您有一个子进程向stdout中写入的数据超出了管道的容纳能力,那么该子进程将被挂起stderr.write,而您的程序将被卡在其中stdout.gets(nil)
hagello 2014年

165

仅作记录,如果您同时想要(输出和操作结果),则可以执行以下操作:

output=`ls no_existing_file` ;  result=$?.success?

4
这正是我想要的。谢谢。
jdl 2011年

12
那仅捕获stdout,并且stderr进入控制台。要获得stderr,请使用: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
这个答案是不安全的,不应使用-如果命令不是常量,则反引号语法可能会导致错误,也可能是安全漏洞。(即使它是一个常数,也可能会导致某人稍后将其用于非常数并导致错误。)有关正确的解决方案,请参见SimonHürlimann的回答
格雷格价格

23
格雷格·普赖斯(Greg Price)对逃避用户输入的必要性的理解表示敬意,但是说出此答案的正确性是不正确的。提到的Open3方法更加复杂,并且引入了更多的依赖关系,有人“以后将其用于非常数”的论点是稻草人。的确,您可能不会在Rails应用程序中使用它们,但是对于一个简单的系统实用程序脚本,它不可能有不受信任的用户输入,反引号是完全可以的,并且不应让任何人对使用它们感到难过。
sbeam 2014年

69

直截了当的方式这样做正确,安全地是使用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.capture2capture2ecapture3工作一样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')

2
这是正确的答案。它也是最有用的。唯一缺少的是有关关闭std * s的警告。参见其他注释require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } 请注意,块形式将自动关闭stdin,stdout和stderr-否则必须显式关闭它们
Peter H. Boling 2014年

@ PeterH.Boling:最好的我知道,capture2capture2ecapture3也接近它们的std * S自动。(至少我从来没有遇到过这个问题。)
Denis de Bernardy 2014年

如果不使用块形式,则代码库无法知道何时应关闭某些内容,因此我非常怀疑它们是否已关闭。您可能永远都不会遇到问题,因为不关闭它们不会在短期内导致问题,并且如果您足够频繁地重新启动长时间运行的过程,那么除非您在其中打开std * s,否则otto也不会在那里出现。一个循环。Linux具有很高的文件描述符限制,您可以点击它,但在您点击它之前,您将看不到“错误”。
Peter H. Boling 2014年

2
@ PeterH.Boling:不,不,请参见源代码。该功能只是包装了Open3#popen2popen2e并且popen3与预定义的块:ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
丹尼斯日Bernardy

1
@Dennis de Barnardy也许您错过了我链接到相同的类文档(尽管适用于Ruby 2.0.0和其他方法)的问题 。ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… 从示例中:```stdin,stdout,stderr,wait_thr = Open3.popen3([env,] cmd ... [,opts])pid = wait_thr [:pid]#启动进程的pid ... stdin.close#stdin ,stdout和stderr应该以这种形式显式关闭。stdout.close stderr.close```我只是在引用文档。“#stdin,stdout和stderr应该以这种形式显式关闭。”
Peter H. Boling

61

您可以根据需要的结果使用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

博客发布者周杰伦字段详细说明了使用系统之间的差异,exec和%×[..]。


2
感谢您使用%x []的提示。它只是解决了我在Mac OS X的ruby脚本中使用反向标记的问题。当在Windows计算机上使用Cygwin运行相同的脚本时,由于反向标记而失败,但是使用了%x []。
亨里克·沃恩

22

如果需要转义参数,则在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"

谢谢!太棒了。
格雷格价格

21

您使用反引号:

`ls`

5
反引号不会在终端上产生输出。

3
它不会产生stderr,但会产生stdout。
Nickolay Kondratenko

1
它不会写入stdout或stderr。让我们尝试这个例子ruby -e '%x{ls}'-注意,没有输出。(fyi %x{}相当于反引号。)
ocodo 2015年

这很棒。使用sh会将输出回显到控制台(即STDOUT)并返回。不是这样
约书亚·品特

19

另一种方法是:

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

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


只是用来读取aws cli命令的标准输出以读取json而不是'true'的官方返回值
kraftydevil

14

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

result = %x[ls]
puts result

我特别想列出机器上所有Java进程的pid,并使用以下代码:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]

这是一个很好的解决方案。
罗南·罗恩(Ronan Louarn)


9

虽然经常需要使用反引号或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


8

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

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

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

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




By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.