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

Posted

技术标签:

【中文标题】在 Ruby 中获取 system() 调用的输出【英文标题】:Getting output of system() calls in Ruby 【发布时间】:2010-10-15 23:09:38 【问题描述】:

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

system("ls")

【问题讨论】:

你可能想看看this thread in comp.lang.ruby 这是一个非常手工的线程,谢谢。示例代码中用于运行命令和获取反馈的类很棒。 为未来的谷歌员工。如果您想了解其他系统命令调用及其差异,see this SO answer。 【参考方案1】:

你使用反引号:

`ls`

【讨论】:

反引号不会在终端产生输出。 它不会产生标准错误,但会产生标准输出。 它不写入标准输出或标准错误。让我们试试这个例子ruby -e '%xls' - 注意,没有输出。 (仅供参考%x 相当于反引号。) 这很好用。使用 sh 会将输出回显到控制台(即 STDOUT)并返回。这没有。【参考方案2】:

另一种方式是:

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

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

【讨论】:

只是用它来读取 aws cli 命令的标准输出,以便读取 json 而不是“true”的官方返回值【参考方案3】:

我想稍微扩展和澄清一下chaos's answer。

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

output = `ls`
p output

printf output # escapes newline chars

【讨论】:

如果我需要在命令中提供一个变量怎么办?也就是说,当使用反引号时,像 system("ls " + filename) 这样的东西会翻译成什么? 您可以像使用常规字符串一样进行表达式评估:ls #filename 这个答案是不可取的:它引入了未经处理的用户输入的新问题。 @Dogweather:这可能是真的,但它与其他任何方法有什么不同吗? 如果你想捕获标准错误,只需将 2>&1 放在你的命令末尾。例如输出 = command 2>&1【参考方案4】:

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

result = %x[ls]
puts result

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

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

【讨论】:

这是一个很好的解决方案。【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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

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

【讨论】:

这正是我想要的。谢谢。 只捕获标准输出,标准错误进入控制台。要获取标准错误,请使用:output=`ls no_existing_file 2>&1`; result=$?.success? 这个答案是 unsafe 并且不应该使用 - 如果命令不是常量,那么反引号语法可能会导致错误,可能是安全问题脆弱性。 (即使它是一个常数,它也可能会导致有人在以后将它用作非常数并导致错误。)请参阅Simon Hürlimann's answer以获得正确的解决方案。 感谢 Greg Price 理解需要转义用户输入,但说这个答案不安全是不正确的。提到的Open3方法更复杂,引入了更多的依赖关系,有人会“稍后将它用于非常数”的说法是稻草人。诚然,您可能不会在 Rails 应用程序中使用它们,但对于一个简单的系统实用程序脚本,不可能有不受信任的用户输入,反引号非常好,不应该让任何人对使用它们感到难过。 此解决方案的两个主要问题是 1. 用户输入未转义 2. $? 是一个全局变量,因此我认为它 不是线程-安全【参考方案7】:

您可以使用 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

Thblog post by Jay Fields 详细解释了使用 system、exec 和 %x[..] 的区别。

【讨论】:

感谢使用 %x[] 的提示。它刚刚解决了我在 Mac OS X 的 ruby​​ 脚本中使用反引号的问题。在使用 Cygwin 在 Windows 机器上运行相同的脚本时,由于反引号而失败,但可以使用 %x[]。 非常感谢! %x[..] 这是紧凑且有效的解决方案!【参考方案8】:

请注意,将包含用户提供的值的字符串传递给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

注意块形式会自动关闭标准输入、标准输出和标准错误——否则它们必须是closed explicitly。

更多信息在这里:Forming sanitary shell commands or system calls in Ruby

【讨论】:

这是唯一真正回答问题并在不引入新问题的情况下解决问题的答案(未经处理的输入)。 谢谢!这是我希望得到的答案。一个更正:gets 调用应该传递参数nil,否则我们只会得到输出的第一行。所以例如stdout.gets(nil). stdin、stdout 和 stderr 应该是closed explicitly in non-block form。 有谁知道 Ruby 2.0 或 2.1 有什么变化?编辑或 cmets 将不胜感激;-) 我认为围绕Open3.popen3 的讨论缺少一个主要问题:如果您的子进程向标准输出写入的数据多于管道可以容纳的数据,则子进程会在stderr.write 中挂起,而您的程序卡在stdout.gets(nil)【参考方案9】:

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

【讨论】:

【参考方案10】:

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

因为我需要这个来测试我的示例,所以我使用块设置来捕获标准输出,因为实际的 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

【讨论】:

【参考方案11】:

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

以附加模式将标准输出和标准错误重定向到文件(/tmp/log):

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

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

【讨论】:

【参考方案12】:

正确且安全地执行此操作的直接方法是使用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.capture2capture2ecapture3system 一样工作,但返回两个或三个参数:

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')

【讨论】:

这是正确答案。它也是信息量最大的。唯一缺少的是关于关闭 std*s 的警告。见this other comment:require 'open3'; output = Open3.popen3("ls") |stdin, stdout, stderr, wait_thr| stdout.read 请注意,块形式将自动关闭标准输入、标准输出和标准错误——否则它们必须是closed explicitly。 @PeterH.Boling:据我所知,capture2capture2ecapture3 也会自动关闭它们的 std*s。 (至少,我从来没有遇到过这个问题。) 如果不使用块形式,代码库无法知道什么时候应该关闭某些东西,所以我高度怀疑它们是否被关闭。您可能从未遇到过问题,因为不关闭它们不会在短期进程中导致问题,并且如果您经常重新启动长时间运行的进程,除非您在其中打开 std*s,否则 otto 也不会出现在那里一个循环。 Linux 有一个很高的文件描述符限制,您可以点击它,但在您点击它之前,您不会看到“错误”。 @PeterH.Boling:不不,看源代码。这些函数只是 Open3#popen2popen2epopen3 的包装器,带有预定义的块:ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… @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 应该以这种形式显式关闭。”【参考方案13】:

由于Simon Hürlimann already explained,Open3 比反引号等更安全。

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

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

【讨论】:

【参考方案14】:
puts `date`
puts $?


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

【讨论】:

【参考方案15】:

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

如果您想使用以下方式捕获 STDERR,您可以将 STDERR 重定向到 STDOUT 反引号。

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

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

【讨论】:

【参考方案16】:

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

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

puts val

缩短版:

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

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

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

【讨论】:

以上是关于在 Ruby 中获取 system() 调用的输出的主要内容,如果未能解决你的问题,请参考以下文章

Python:运行 os.system 后如何获取标准输出? [复制]

inux C程序中获取shell脚本输出(如获取system命令输出)

Ruby 系统参数

pythonos.system() & os.popen() 在 python 执行 cmd 指令

在main()方法中调用多个方法(int)

ruby 系统命令检查退出代码