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

时间:2009-03-27 15:12:54

标签: ruby system call

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

system("ls")

15 个答案:

答案 0 :(得分:335)

我想扩大&澄清chaos's answer一点。

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

output = `ls`
p output

printf output # escapes newline chars

答案 1 :(得分:232)

请注意,将包含用户提供的值的字符串传递给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-否则它们必须是closed explicitly

此处提供更多信息:Forming sanitary shell commands or system calls in Ruby

答案 2 :(得分:162)

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

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

答案 3 :(得分:59)

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

Th blog post by Jay Fields详细解释了使用system,exec和%x [..]之间的区别。

答案 4 :(得分:59)

正确安全地执行此操作的直接方法是使用Open3.capture2()Open3.capture2e()Open3.capture3()

如果与不受信任的数据一起使用,使用ruby的反引号及其%x别名在任何情况下都不安全 DANGEROUS ,简单明了:

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

答案 5 :(得分:19)

你使用反引号:

`ls`

答案 6 :(得分:19)

另一种方式是:

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

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

答案 7 :(得分:19)

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

如果你还需要传递stdin,这应该适用于1.9和1.8:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"

答案 8 :(得分:13)

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

result = %x[ls]
puts result

我特意想在我的机器上列出所有Java进程的pid,并使用它:

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

答案 9 :(得分:11)

作为Simon Hürlimann already explainedOpen3比反叛等更安全。

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

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

答案 10 :(得分:9)

虽然使用反引号或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 :(得分:8)

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

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

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

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

答案 12 :(得分:7)

作为直接系统(...)替换,您可以使用Open3.popen3(...)

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

答案 13 :(得分:0)

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

  

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

     

output =`grep hosts / private / etc / * 2&gt;&amp; 1`

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

答案 14 :(得分:-1)

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title>input to input</title>
</head>

<body>
  <input type="number" id="inp1" min="50.00" placeholder="50.00" value="50.00" onfocus="(this.value == '50.00') && (this.value = '')" onblur="(this.value == '') && (this.value = '50.00')"/>
  <br/>
  <label for="inp2">Value of Hidden Input:</label>
  <output id="out1"></output>
  <input id="inp2" type="hidden" name="inp2" value="">

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
  <script>
    $(function() {
      $('#inp1').on('input', function(e) {
        var input = $('#inp1').val();
        $('#inp2').val(input);

        // See the results in devtools or...
        console.log('inp2: ' + $("#inp2").val());

        // On the page itself.
        $('#out1').html($('#inp2').val());
      });
    });
  </script>
</body>

</html>