Подтвердить что ты не робот

Как читать и писать из трубы в 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, потому что он может оказаться в тупике, и я могу это понять. Но как мне это сделать?

4b9b3361

Ответ 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: $!";