在Ruby中获取system()调用的输出
如果我在Ruby中调用一个使用Kernel#system的命令,我该如何得到它的输出?
system("ls")
我想扩大和澄清混乱的答案了一下。
如果你用反引号来包围你的命令,那么你根本不需要(明确地)调用system()。 反引号执行命令并将输出作为字符串返回。 然后,您可以将值分配给一个变量,如下所示:
output = `ls` p output
要么
printf output # escapes newline chars
请注意,将包含用户提供的值的字符串传递给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
请注意,块表单将自动关闭标准输入,标准输出和标准错误 – 否则他们将不得不明确关闭 。
更多信息在这里: 在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.capture2
, capture2e
和capture3
像system
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