如果我在Ruby中使用Kernel#system调用命令,如何获取其输出?
system("ls")
如果我在Ruby中使用Kernel#system调用命令,如何获取其输出?
system("ls")
Answers:
我想扩大和澄清混乱的答案。
如果您在命令中加上反引号,则根本不需要(明确地)调用system()。反引号执行命令并以字符串形式返回输出。然后可以将值分配给变量,如下所示:
output = `ls`
p output
要么
printf output # escapes newline chars
ls #{filename}
。
command 2>&1
请注意,所有在那里你传递一个包含用户提供的值到字符串的解决方案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
请注意,块形式将自动关闭stdin,stdout和stderr,否则必须显式关闭它们。
此处提供更多信息:在Ruby中形成卫生外壳命令或系统调用
gets
调用应传递参数nil
,否则我们将只获得输出的第一行。因此,例如stdout.gets(nil)
。
Open3.popen3
遗漏了一个主要问题:如果您有一个子进程向stdout中写入的数据超出了管道的容纳能力,那么该子进程将被挂起stderr.write
,而您的程序将被卡在其中stdout.gets(nil)
。
仅作记录,如果您同时想要(输出和操作结果),则可以执行以下操作:
output=`ls no_existing_file` ; result=$?.success?
output=`ls no_existing_file 2>&1`; result=$?.success?
直截了当的方式这样做正确,安全地是使用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')
require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
请注意,块形式将自动关闭stdin,stdout和stderr-否则必须显式关闭它们。
capture2
,capture2e
和capture3
也接近它们的std * S自动。(至少我从来没有遇到过这个问题。)
Open3#popen2
,popen2e
并且popen3
与预定义的块:ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
您可以根据需要的结果使用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
个博客发布者周杰伦字段详细说明了使用系统之间的差异,exec和%×[..]。
如果需要转义参数,则在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"
您使用反引号:
`ls`
ruby -e '%x{ls}'
-注意,没有输出。(fyi %x{}
相当于反引号。)
sh
会将输出回显到控制台(即STDOUT)并返回。不是这样
另一种方法是:
f = open("|ls")
foo = f.read()
请注意,这是打开“ ls”之前的“ pipe”字符。这也可以用于将数据输入到程序的标准输入以及读取其标准输出。
我发现如果需要返回值,以下内容很有用:
result = %x[ls]
puts result
我特别想列出机器上所有Java进程的pid,并使用以下代码:
ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
虽然经常需要使用反引号或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
。
作为直接的系统替换,您可以使用Open3.popen3(...)
进一步的讨论:http : //tech.natemurray.com/2007/03/ruby-shell-commands.html
我在这里没有找到这个,因此添加它时,我在获取完整输出时遇到一些问题。
如果要使用反引号捕获STDERR,可以将STDERR重定向到STDOUT。
输出=`grep hosts / private / etc / * 2>&1`
来源:http : //blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html