如果我在Ruby中使用Kernel#system调用命令,我该如何获得它的输出?
system("ls")
Craig Walker.. 340
我想扩大和澄清一下混乱的答案.
如果用反引号包围命令,则根本不需要(显式地)调用system().反引号执行命令并将输出作为字符串返回.然后,您可以将值分配给变量,如下所示:
output = `ls` p output
要么
printf output # escapes newline chars
您可以像使用常规字符串一样进行表达式求值:`ls#{filename}`. (47认同)
这个答案是不可取的:它引入了未经过用户输入的新问题. (35认同)
如果你想要限制stderr,只需在你的命令结束时输入2>&1.例如output =`command 2>&1` (19认同)
如果我需要在命令中提供变量怎么办?也就是说,当使用反引号时,系统("ls"+ filename)会变成什么? (4认同)
@Dogweather:这可能是真的,但它与任何其他方法有什么不同? (4认同)
@Dogweather它只引入未经过清理的用户输入,如果使用了尚未清理过的用户输入的变量.经验丰富的Ruby程序员会小心标记相关的变量(例如`filename_sane`或`filename_escd`),并且只在危险的上下文中使用它们. (2认同)
Simon Hürlim.. 239
要知道,在那里你传递一个字符串包含用户提供的值到所有的解决方案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中形成卫生shell命令或系统调用
我想扩大和澄清一下混乱的答案.
如果用反引号包围命令,则根本不需要(显式地)调用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
请注意,块形式将自动关闭stdin,stdout和stderr-否则必须明确关闭它们.
更多信息:在Ruby中形成卫生shell命令或系统调用
只是为了记录,如果你想要两个(输出和操作结果)你可以做:
output=`ls no_existing_file` ; 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')
您可以使用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 }
如果你还需要传递stdin,这应该适用于1.9和1.8:
out = IO.popen("xxd -p", "r+") { |io| io.print "xyz" io.close_write io.read.chomp } p out # "78797a"
你使用反引号:
`ls`
另一种方式是:
f = open("|ls") foo = f.read()
请注意,这是打开"ls"之前的"管道"字符.这也可用于将数据馈送到程序标准输入以及读取其标准输出.
我发现如果你需要返回值,以下内容很有用:
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 }
请注意,块形式将自动关闭stdin,stdout和stderr-否则必须明确关闭它们.
虽然使用反引号或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重定向它.
作为直接系统(...)替换,您可以使用Open3.popen3(...)
进一步讨论:http: //tech.natemurray.com/2007/03/ruby-shell-commands.html