Как читать и писать из трубы в Perl?
Я - Perl noob, поэтому, пожалуйста, извините этот основной вопрос. Мне нужно изменить существующую программу Perl. Я хочу передать строку (которая может содержать несколько строк) через внешнюю программу и прочитать вывод из этой программы. Таким образом, эта внешняя программа используется для изменения строки. Позвольте просто использовать cat
в качестве программы фильтрации. Я попробовал это, но это не сработает. (Вывод cat
переходит в stdout вместо чтения perl
.)
#!/usr/bin/perl
open(MESSAGE, "| cat |") or die("cat failed\n");
print MESSAGE "Line 1\nLine 2\n";
my $message = "";
while (<MESSAGE>)
{
$message .= $_;
}
close(MESSAGE);
print "This is the message: $message\n";
Я читал, что это не поддерживается Perl, потому что он может оказаться в тупике, и я могу это понять. Но как мне это сделать?
Ответы
Ответ 1
Вы можете использовать IPC:: Open3 для достижения двунаправленной связи с дочерним элементом.
use strict;
use IPC::Open3;
my $pid = open3(\*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, 'cat')
or die "open3() failed $!";
my $r;
for(my $i=1;$i<10;$i++) {
print CHLD_IN "$i\n";
$r = <CHLD_OUT>;
print "Got $r from child\n";
}
Ответ 2
Это связано с системным программированием, поэтому его более чем основной вопрос. Как написано, ваша основная программа не требует полнодуплексного взаимодействия с внешней программой. Поток данных перемещается в одном направлении, а именно
строка → внешняя программа → основная программа
Создание этого конвейера является простым. Perls open
имеет полезный режим, описанный в разделе "Безопасный канал открывается" в документации perlipc.
Еще один интересный подход к межпроцессному обмену - сделать вашу единую программу многопроцессом и общаться между вами или даже между вами. Функция open
принимает аргумент файла либо "-|"
, либо "|-"
, чтобы сделать очень интересную вещь: она разворачивает дочерний элемент, связанный с файловой дескрипцией, которую вы открыли. Ребенок выполняет ту же программу, что и родитель. Это полезно для безопасного открытия файла при запуске под предполагаемым UID или GID, например. Если вы откроете трубу до минус, вы можете написать файл дескриптора файла, который вы открыли, и ваш ребенок найдет его в своем STDIN
. Если вы открываете трубу с минусом, вы можете прочитать из дескриптора файла, который вы открыли, как пишет ваш малыш, на его STDOUT
.
Это open
, который включает в себя канал, который дает нюанс возвращаемому значению. документация perlfunc на open
объясняет.
Если вы откроете канал в команде -
(то есть укажите либо |-
, либо -|
с формами с одним или двумя аргументами open
), выполняется неявное fork
поэтому open возвращается дважды: в родительском процессе он возвращает pid дочернего процесса, а в дочернем процессе он возвращает (определенный) 0
. Используйте defined($pid)
или //
, чтобы определить, был ли open
успешным.
Чтобы создать строительные леса, мы работаем в порядке справа налево, используя open
to fork
новый процесс на каждом шаге.
- Ваша основная программа уже запущена.
- Далее,
fork
процесс, который в конечном итоге станет внешней программой.
- Внутри процесса с шага 2
- Первый
fork
процесс строковой печати, чтобы сделать его вывод на нашем STDIN
.
- Затем
exec
внешняя программа для выполнения своего преобразования.
- Попросите струйный принтер выполнить его работу, а затем
exit
, который поднимается до следующего уровня.
- Вернитесь в основную программу, прочитайте преобразованный результат.
Со всем этим, все, что вам нужно сделать, это наложить ваше предложение внизу, мистер Кобб.
#! /usr/bin/env perl
use 5.10.0; # for defined-or and given/when
use strict;
use warnings;
my @transform = qw( tr [A-Za-z] [N-ZA-Mn-za-m] ); # rot13
my @inception = (
"V xabj, Qnq. Lbh jrer qvfnccbvagrq gung V pbhyqa'g or lbh.",
"V jnf qvfnccbvagrq gung lbh gevrq.",
);
sub snow_fortress { print map "$_\n", @inception }
sub hotel {
given (open STDIN, "-|" // die "$0: fork: $!") { # / StackOverflow hiliter
snow_fortress when 0;
exec @transform or die "$0: exec: $!";
}
}
given (open my $fh, "-|" // die "$0: fork: $!") {
hotel when 0;
print while <$fh>;
close $fh or warn "$0: close: $!";
}
Спасибо за возможность написать такую забавную программу!
Ответ 3
Вы можете использовать ключ -n командной строки, чтобы эффективно обернуть ваш существующий программный код в цикл while... посмотрите man-страницу для -n:
LINE:
while (<>) {
... # your program goes here
}
Затем вы можете напрямую использовать механизм работы операционной системы
cat file | your_perl_prog.pl
(Edit)
Я попытаюсь объяснить это более тщательно...
Вопрос неясно, какую роль играет программа perl: фильтр или заключительный этап. Это работает в любом случае, поэтому я предполагаю, что это последний.
'your_perl_prog.pl' - ваш существующий код. Я назову фильтр фильтра фильтра.
Измените your_perl_prog.pl так, чтобы строка shebang имела добавленный '-n' переключатель: #!/usr/bin/perl -n или #!/bin/env "perl -n"
Это эффективно помещает некоторое время (< > ) {} в цикл в коде your_perl_prog.pl
добавьте блок BEGIN для печати заголовка:
BEGIN {print "HEADER LINE\n");}
Вы можете прочитать каждую строку с помощью '$line = <>;'
и обработать/распечатать
Затем вызовите лот с помощью
cat sourcefile |filter|your_perl_prog.pl
Ответ 4
Я хочу расширить ответ на @Greg Bacon, не меняя его.
Мне пришлось выполнить что-то подобное, но мне хотелось
заданные команды /when, а также найденный явный выход()
потому что в образце кода он провалился и вышел.
Мне также пришлось заставить его работать и над версией, работающей с ActiveState perl,
но эта версия perl не работает.
См. Этот вопрос Как читать и писать из канала в perl с помощью ActiveState Perl?
#! /usr/bin/env perl
use strict;
use warnings;
my $isActiveStatePerl = defined(&Win32::BuildNumber);
sub pipeFromFork
{
return open($_[0], "-|") if (!$isActiveStatePerl);
die "active state perl cannot cope with dup file handles after fork";
pipe $_[0], my $child or die "cannot create pipe";
my $pid = fork();
die "fork failed: $!" unless defined $pid;
if ($pid) { # parent
close $child;
} else { # child
open(STDOUT, ">&=", $child) or die "cannot clone child to STDOUT";
close $_[0];
}
return $pid;
}
my @transform = qw( tr [A-Za-z] [N-ZA-Mn-za-m] ); # rot13
my @inception = (
"V xabj, Qnq. Lbh jrer qvfnccbvagrq gung V pbhyqa'g or lbh.",
"V jnf qvfnccbvagrq gung lbh gevrq.",
);
sub snow_fortress { print map "$_\n", @inception }
sub hotel
{
my $fh;
my $pid = pipeFromFork($fh); # my $pid = open STDIN, "-|";
defined($pid) or die "$0: fork: $!";
if (0 == $pid) {
snow_fortress;
exit(0);
}
open(STDIN, "<&", $fh) or die "cannot clone to STDIN";
exec @transform or die "$0: exec: $!";
}
my $fh;
my $pid = pipeFromFork($fh); # my $pid = open my $fh, "-|";
defined($pid) or die "$0: fork: $!";
if (0 == $pid) {
hotel;
exit(0);
}
print while <$fh>;
close $fh or warn "$0: close: $!";