Ruby pipe: как связать вместе выходные данные двух подпроцессов?

Есть ли автоматизированный способ создания оболочки в Ruby? Я пытаюсь преобразовать следующий код оболочки в 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 для подключения выхода a к входу b? Мой текущий подход становится довольно громоздким, когда количество фильтров растет.

Я, очевидно, знаю о Kernel#system('a | b'), но мне нужно смешивать фильтры Ruby с внешними программными фильтрами общим способом.


person Arno    schedule 20.11.2010    source источник
comment
Есть одно решение с командой spawn в ruby. Вы должны открыть несколько каналов, а затем использовать параметры порождения для перенаправления Stdin и Stdout дочерних процессов. Вы можете найти более подробный пример в моем ответе здесь: " title="marshal ruby ​​pipe, отправляющий сериализованный объект дочерним процессам"> stackoverflow.com/questions/11898528/   -  person Pavel Chernov    schedule 07.11.2012
comment
Конвейеры Open3 кажутся идеальными ruby -doc.org/stdlib-2.4.1/libdoc/open3/rdoc/   -  person kch    schedule 31.05.2017


Ответы (5)


Старый вопрос, но поскольку это один из первых результатов в Google, вот ответ: 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
person sloonz    schedule 19.01.2011

Используя простой рубин, 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

Конечно, вы можете инкапсулировать это во что-то вроде sh.system из упомянутой библиотеки Shell и создать метод |() для соединения.

В модуле open3 стандартной библиотеки есть несколько действительно хороших инструментов для такого рода вещей, включая создание полных конвейеров.

person PSkocik    schedule 10.03.2015

Если a, b и c являются командами, доступ к которым обычно осуществляется из командной строки, вы можете использовать:

captured_output = `a | b | c`

Ruby будет запускать команды в дополнительной оболочке и захватывать STDOUT.

Если вам по какой-то причине нужно направить вывод в файл, вы также можете добавить перенаправление в команду. В этом случае STDOUT не будет вам возвращен, но вы можете открыть файл и обработать его вручную:

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

Он не предлагает такого же контроля, как использование system или popen3, но это удобно:

>> 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"
person the Tin Man    schedule 21.11.2010
comment
Я бы также вместо использования потоков сделал это, используя большой цикл для всего... неприятное решение для неприятной проблемы - person mpapis; 21.11.2010

К сожалению, конвейеризация в оболочке — серьезное дело, и действительно, для этого потребуется изрядное количество кода. Нет необходимости создавать потоки с циклами чтения/записи, но это все равно потребует много работы.

Самый простой пример, который я нашел, — это реализация перенаправления в dash (оболочка Debian Almquist). Как правило, если вы хотите сделать то же самое в Ruby, вам нужно воспроизвести эти приемы манипуляции с fd, используя Ruby IO#dup, IO#fileno, IO#pipe, IO#reopen и т. д. Возможно, будет проще повторно использовать оболочку (например, код dash) в библиотеке C .so для интерпретатора Ruby, чем пытаться объединить одно и то же, используя только примитивы Ruby.

Я не знаю ни одного существующего обобщенного Ruby API для сложных межпроцессных конвейеров/перенаправлений. Если бы вы могли предложить хороший API, который вы хотели бы использовать, возможно, я мог бы участвовать в реализации.

person GreyCat    schedule 27.11.2010

Я знаю, что это очень старый вопрос, но я только что прошел через эту боль.

По сути, я придумал пару служебных методов, которые запускают команды с помощью popen внутри ветвей, передавая результаты через конвейеры, чтобы добиться чего-то вроде:

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

Чтобы сделать это в Руби:

# 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< /а>

person Phil    schedule 17.12.2020