【发布时间】:2010-10-15 23:09:38
【问题描述】:
如果我在 Ruby 中使用 Kernel#system 调用命令,我如何获得它的输出?
system("ls")
【问题讨论】:
-
这是一个非常手工的线程,谢谢。示例代码中用于运行命令和获取反馈的类很棒。
-
为未来的谷歌员工。如果您想了解其他系统命令调用及其差异,see this SO answer。
如果我在 Ruby 中使用 Kernel#system 调用命令,我如何获得它的输出?
system("ls")
【问题讨论】:
你使用反引号:
`ls`
【讨论】:
ruby -e '%x{ls}' - 注意,没有输出。 (仅供参考%x{} 相当于反引号。)
sh 会将输出回显到控制台(即 STDOUT)并返回。这没有。
另一种方式是:
f = open("|ls")
foo = f.read()
请注意,打开时“ls”之前的“管道”字符。这也可以用于将数据输入程序的标准输入以及读取其标准输出。
【讨论】:
我想稍微扩展和澄清一下chaos's answer。
如果你用反引号包围你的命令,那么你根本不需要(明确地)调用 system()。反引号执行命令并将输出作为字符串返回。然后,您可以将值分配给一个变量,如下所示:
output = `ls`
p output
或
printf output # escapes newline chars
【讨论】:
ls #{filename}。
command 2>&1
如果需要返回值,我发现以下内容很有用:
result = %x[ls]
puts result
我特别想列出我机器上所有 Java 进程的 pid,并使用了这个:
ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
【讨论】:
作为直接的 system(...) 替代品,您可以使用 Open3.popen3(...)
进一步讨论: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
【讨论】:
只是为了记录,如果你想要(输出和操作结果)你可以这样做:
output=`ls no_existing_file` ; result=$?.success?
【讨论】:
output=`ls no_existing_file 2>&1`; result=$?.success?
$? 是一个全局变量,因此我认为它 不是线程-安全
您可以使用 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[..] 这是紧凑且有效的解决方案!
请注意,将包含用户提供的值的字符串传递给system、%x[] 等的所有解决方案都是不安全的!不安全实际上意味着:用户可以触发代码在上下文中运行,并具有程序的所有权限。
据我所知,只有 system 和 Open3.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).
Open3.popen3 的讨论缺少一个主要问题:如果您的子进程向标准输出写入的数据多于管道可以容纳的数据,则子进程会在stderr.write 中挂起,而您的程序卡在stdout.gets(nil)。
如果您需要转义参数,在 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"
【讨论】:
虽然使用反引号或 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。
【讨论】:
如果您希望使用Kernel#system 将输出重定向到文件,您可以像这样修改描述符:
以附加模式将标准输出和标准错误重定向到文件(/tmp/log):
system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])
对于长时间运行的命令,这将实时存储输出。您还可以使用 IO.pipe 存储输出并将其从 Kernel#system 重定向。
【讨论】:
正确且安全地执行此操作的直接方法是使用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、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')
【讨论】:
require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } 请注意,块形式将自动关闭标准输入、标准输出和标准错误——否则它们必须是closed explicitly。
capture2、capture2e 和 capture3 也会自动关闭它们的 std*s。 (至少,我从来没有遇到过这个问题。)
Open3#popen2、popen2e 和 popen3 的包装器,带有预定义的块:ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/…
由于Simon Hürlimann already explained,Open3 比反引号等更安全。
require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
请注意,块形式将自动关闭标准输入、标准输出和标准错误——否则它们必须是closed explicitly。
【讨论】:
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0
【讨论】:
我在这里没有找到这个,所以添加它,我在获得完整输出时遇到了一些问题。
如果您想使用以下方式捕获 STDERR,您可以将 STDERR 重定向到 STDOUT 反引号。
输出 = `grep hosts /private/etc/* 2>&1`
来源:http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
【讨论】:
将标准输出捕获到名为 val 的变量中的最简单解决方案:
val = capture(:stdout) do
system("pwd")
end
puts val
缩短版:
val = capture(:stdout) { system("ls") }
capture 方法由提供 active_support/core_ext/kernel/reporting.rb
同样,我们也可以使用:stderr 捕获标准错误
【讨论】: