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