Getting output of system() calls in Ruby
如果我使用Ruby中的内核系统调用一个命令,如何获得它的输出?
1 | system("ls") |
我想扩大和澄清一下混乱的答案。
如果用反勾号包围命令,那么根本不需要(显式)调用System()。backticks执行命令并以字符串形式返回输出。然后可以将该值赋给变量,如下所示:
1 2 | output = `ls` p output |
或
1 | printf output # escapes newline chars |
请注意,将包含用户提供值的字符串传递给
据我所知,在Ruby1.8中,只有
只需将每个选项和参数作为数组传递给其中一个调用。
如果您不仅需要退出状态,还需要使用
1 2 3 4 5 6 7 | 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,否则必须显式关闭它们。
这里的更多信息:在Ruby中形成卫生外壳命令或系统调用
对于记录,如果您同时需要(输出和操作结果),可以执行以下操作:
1 | output=`ls no_existing_file` ; result=$?.success? |
正确和安全地执行此操作的直接方法是使用
如果与不受信任的数据一起使用,在任何情况下,使用Ruby的backticks和它的
1 2 3 4 5 6 7 8 | untrusted ="; date; echo" out = `echo #{untrusted}` # BAD untrusted = '"; date; echo"' out = `echo"#{untrusted}"` # BAD untrusted ="'; date; echo'" out = `echo '#{untrusted}'` # BAD |
相反,如果正确使用
1 2 | ret = system"echo #{untrusted}" # BAD ret = system 'echo', untrusted # good |
问题是,它返回退出代码而不是输出,捕获后者是复杂和混乱的。
到目前为止,这个线程中最好的答案提到了open3,但没有提到最适合该任务的函数。
1 2 3 4 5 | 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 |
另一个提到了
1 | out = IO.popen(['echo', untrusted]).read # good |
为了方便起见,您可以将
1 2 3 4 5 6 7 8 9 10 | # # 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 |
例子:
1 2 3 4 5 6 | p system('foo') p syscall('foo') p system('which', 'foo') p syscall('which', 'foo') p system('which', 'which') p syscall('which', 'which') |
生成以下内容:
1 2 3 4 5 6 7 | nil nil false false /usr/bin/which <— stdout from system('which', 'which') true <- p system('which', 'which') "/usr/bin/which" <- p syscall('which', 'which') |
根据需要的结果类型,可以使用System()或%x[]。
如果找到命令并成功运行,则返回true,否则返回false。
1 2 3 4 5 6 7 | >> 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[..]将命令结果保存为字符串:
1 2 3 4 5 6 7 8 | >> result = %x[uptime] =>"13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23 " >> p result "13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23 " >> result.class => String |
Jay Fields的博客文章详细解释了使用System、Exec和%x的区别。
如果需要转义参数,在Ruby1.9io.popen中还接受一个数组:
1 | p IO.popen(["echo","it's escaped"]).read |
在早期版本中,您可以使用open3.popen3:
1 2 3 | require"open3" Open3.popen3("echo","it's escaped") { |i, o| p o.read } |
如果您还需要通过stdin,那么这应该在1.9和1.8中都有效:
1 2 3 4 5 6 | out = IO.popen("xxd -p","r+") { |io| io.print"xyz" io.close_write io.read.chomp } p out #"78797a" |
另一种方法是:
1 2 | f = open("|ls") foo = f.read() |
注意,这是"ls"前面的"pipe"字符。这也可用于将数据输入程序的标准输入并读取其标准输出。
使用倒计时:
1 | `ls` |
如果您需要返回值,我发现以下内容很有用:
1 2 | result = %x[ls] puts result |
我特别想在我的机器上列出所有Java进程的PID,并使用它:
1 | ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs] |
正如Simon H_rlimann已经解释的那样,open3比backticks等更安全。
1 2 | require 'open3' output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } |
请注意,块表单将自动关闭stdin、stdout和stderr,否则必须显式关闭它们。
当你真正想要的往往是使用backticks或popen时,它实际上并没有回答你提出的问题。捕获
由于我在测试时需要这样做,因此我的示例使用块设置来捕获标准输出,因为实际的
1 2 3 4 5 6 7 8 9 10 11 | 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 |
此方法使用tempfile来存储实际数据,捕获给定块中的任何输出。示例用法:
1 2 3 4 | captured_content = capture_stdout do system 'echo foo' end puts captured_content |
您可以将
如果希望使用
在附加模式下将stdout和stderr重定向到文件(/tmp/log):
system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])
对于长时间运行的命令,这将实时存储输出。您还可以使用io.pipe存储输出,并将其从内核系统重定向。
作为直接系统(…)替换,您可以使用open3.popen3(…)
进一步讨论:http://tech.natemurray.com/2007/03/ruby-shell-commands.html
我在这里没有找到这个,所以加上它,我在获得完整输出时遇到了一些问题。
You can redirect STDERR to STDOUT if you want to capture STDERR using
backtick.output = `grep hosts /private/etc/* 2>&1`
来源:http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
1 2 3 4 5 6 | puts `date` puts $? Mon Mar 7 19:01:15 PST 2016 pid 13093 exit 0 |