Ruby管道:如何将两个子进程的输出绑定在一起?

时间:2010-11-20 17:53:55

标签: ruby pipe popen

是否有自动方式在Ruby中进行shell管道?我正在尝试将以下shell代码转换为Ruby:

a | b | c... > ...

但到目前为止我找到的唯一解决方案是自己进行缓冲管理(简化,未经测试,希望它能得到我的意思):

a = IO.popen('a')
b = IO.popen('b', 'w+')
Thread.new(a, b) { |in, out|
    out.write(in.readpartial(4096)) until in.eof?
    out.close_write
}
# deal with b.read...

我想我正在寻找的是告诉popen使用现有流而不是创建新流的方法?或者,IO#merge方法将输出连接到b的输入?当滤波器数量增加时,我当前的方法变得相当笨拙。

我明显知道Kernel#system('a | b'),但我需要以通用方式将Ruby过滤器与外部程序过滤器混合。

5 个答案:

答案 0 :(得分:10)

老问题,但由于它是谷歌的第一个结果之一,所以答案是:http://devver.wordpress.com/2009/10/12/ruby-subprocesses-part_3/(方法8)

简而言之:

sh = Shell.new
sh.system("a") | sh.system("b") | sh.system("c")

你可以做更复杂的事情,比如

sh.echo(my_string) | sh.system("wc") > "file_path"
xml = (sh.echo(html) | sh.system("tidy", "-q")).to_s

答案 1 :(得分:3)

如果abc是通常从命令行访问的命令,那么您可以使用:

captured_output = `a | b | c`

Ruby将在子shell中运行命令,并捕获STDOUT。

如果由于某种原因需要将输出路由到文件,那么您也可以将重定向添加到命令中。在这种情况下,STDOUT不会退还给您,但您可以打开文件并手动处理它:

`a | b | c > captured_output`
File.foreach('captured_output') do |li|
  print li
end

它不提供与使用systempopen3一样多的控制权,但它很方便:

>> sin, sout, serr = Open3.popen3('ls -al | tail -1') #=> [#<IO:fd 4>, #<IO:fd 5>, #<IO:fd 7>, #<Thread:0x00000100bb8798 run>]
>> sout.read #=> "drwxr-xr-x   3 greg  staff    102 Nov  2 21:01 python\n"

答案 2 :(得分:1)

使用普通红宝石,spawn具有可用于通过管道连接进程的重定向选项。

1)创建一个管道

r,w = IO.pipe

2)用它来连接两个衍生进程

spawn(*%w[echo hello world], out: w)
spawn(*%w[tr a-z A-Z], in: r)
# => HELLO WORLD

当然,您可以在提到的Shell库中将其封装在sh.system之类的内容中,并创建一个|()方法来进行互连。

标准库的open3模块有一些非常好用的工具,包括创建完整的管道。

答案 3 :(得分:0)

可悲的是,shell中的管道是一个严重的问题,实际上,它需要相当多的代码。没有必要使用读/写循环生成线程,但它仍然需要大量的工作。

我发现的最简单的例子是redirect implementation in dash (Debian Almquist Shell)。通常,如果你想在Ruby中做同样的事情,你需要使用Ruby的IO#dupIO#filenoIO#pipe来复制这些 fd 操作技巧。对于Ruby解释器,在C .so库中重用shell(例如dash)代码可能比使用Ruby原语组合相同的东西更容易。

我不知道任何现有的通用Ruby API用于复杂的进程间管道/重定向。如果您可以建议您想要使用的API,可能我可以参与实施。

答案 4 :(得分:0)

我知道这是一个非常老的问题,但我只是经历了这一痛苦。

基本上,我想到了一对实用程序方法,它们在fork中使用popen运行命令,将结果通过管道传递,以实现以下目的:

echo 'ABC12345 234234 24523' | wc | grep '1'

要在Ruby中执行此操作:

# Setup two pipes (the two we see in the shell command line above)
# plus one more to receive the result from.
pipe_pair1 = IO.pipe
pipe_pair2 = IO.pipe
pipe_pair_result = IO.pipe
pipe_in_out nil, pipe_pair1,  ['echo', 'ABC12345 234234 24523']
pipe_in_out pipe_pair1, pipe_pair2, ['wc']
pipe_in_out pipe_pair2, pipe_pair_result, ['grep', '1']

# Get the result from the final pipe
puts "Output: #{pipe_result(pipe_pair_result)}"

方法的代码位于以下要点:https://gist.github.com/philayres/c0d96cd263329e41fa84c2e3c7b9ae7b