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

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

system("ls") 

我想扩大和澄清混乱的答案了一下。

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

 output = `ls` p output 

要么

 printf output # escapes newline chars 

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

请注意,块表单将自动关闭标准输入,标准输出和标准错误 – 否则他们将不得不明确关闭 。

更多信息在这里: 在Ruby中形成卫生shell命令或系统调用

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

 output=`ls no_existing_file` ; 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 

Jay Fields博客文章详细解释了使用system,exec和%x [..]之间的区别。

使用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 capture3工作,但返回两个或三个参数:

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

你使用反引号:

 `ls` 

另一种方法是:

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

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

如果你需要跳过这个参数,在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" 

我发现如果您需要返回值,以下是有用的:

 result = %x[ls] puts result 

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

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

正如SimonHürlimann 已经解释的那样 , Open3比反引号等更安全

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

请注意,表单将自动关闭标准输入,标准输出和标准错误 – 否则他们将不得不被明确关闭 。

作为一个直接的系统(…)更换,你可以使用Open3.popen3(…)

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

虽然使用反引号或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。

如果你想让输出重定向到一个使用Kernel#system的文件,你可以这样修改描述符:

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

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

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

 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