Perl запускает интерактивное внешнее приложение с обратным вызовом - PullRequest
0 голосов
/ 02 июня 2018

Я ищу способ сделать следующее:

  1. У сценария Perl выполнить внешнюю интерактивную программу оболочки
  2. Захватить STDIN и STDOUT для внешней программы
  3. Привязка какого-либо подпрограммы обратного вызова для выполнения каждый раз, когда внешняя программа печатает что-либо в STDOUT
  4. Используйте эту подпрограмму для анализа STDOUT и, если она соответствует регулярному выражению, выведите ответ на STDIN для внешней программы.

Я нашел Expect и IPC, но все, что я нашел до сих пор, похоже, находится в контексте выполнения -> запись -> чтение-> выход, где мне нужно, чтобы это внешнее приложение продолжало работать, а сценарий Perl продолжал отвечать, пока я не уничтожил оба.

РЕДАКТИРОВАТЬ: я нашел решение в модуле «ожидаем» для Perl, установив тайм-аутдо неопределенности и вызова «exp_continue» после моей логики, я был в состоянии поддерживать выполнение сценария и обрабатывать ввод-вывод, пока не убью его.

1 Ответ

0 голосов
/ 02 июня 2018

Мне нужно, чтобы это внешнее приложение продолжало работать, а скрипт perl продолжал отвечать до тех пор, пока я не уничтожу оба.

Это интересный вопрос - один из возможных способов решить это с помощью событиярамочная петля типа POE.Несмотря на то, что у него есть некоторая кривая обучения (но см. поваренная книга ), я считаю, что он отлично подходит для таких вещей, как специализированные сетевые серверы, или для таких вещей, как ваш случай - оставаться интерактивным на консоли, в то время как другие вещи(сетевые соединения, последовательные порты и т. д.).

use warnings;
use strict;

sub POE::Kernel::ASSERT_DEFAULT () { return 1 }
use POE qw/ Wheel::ReadWrite Wheel::Run /;

my @CHILD = ('perl', '-wMstrict', '-nle',
    q{ $|=1; print uc; sleep 5; print lc });

POE::Session->create( inline_states => {
    _start => sub {
        $poe_kernel->alias_set('console_handler');
        $_[HEAP]{console} = POE::Wheel::ReadWrite->new(
                InputHandle => \*STDIN, OutputHandle => \*STDOUT,
                InputEvent => 'console_input', ErrorEvent => 'console_error' );
    },
    console_input => sub {
        my ($heap, $input) = @_[HEAP,ARG0];
        if ($input=~/^(?:quit|exit)$/i) {
            $poe_kernel->post(signal_handler => 'signal_shutdown',
                'user request');
        }
        elsif ($input=~/^send\s+(.*)$/i) {
            $poe_kernel->post(child_handler => 'child_stdin', $1);
        }
        else {
            $heap->{console}->put('Unknown command - try "send ..."');
        }
    },
    console_output => sub {
        my ($heap, $output) = @_[HEAP,ARG0];
        if (defined $heap->{console})
            { $heap->{console}->put($output) }
        else # assume we're shut down, don't need to go through the wheel
            { print $output, "\n" }
    },
    console_error => sub {
        my ($op, $errnum, $errstr) = @_[ARG0..ARG2];
        $poe_kernel->post(signal_handler => 'signal_shutdown',
            $op eq 'read' && $errnum==0 ? 'EOF'
                : "console error (op $op error $errnum: $errstr)" );
    },
    console_shutdown => sub { delete $_[HEAP]{console} },
    _stop => sub {  },
}, );

POE::Session->create( inline_states => {
    _start => sub {
        $poe_kernel->alias_set('child_handler');
        $poe_kernel->post(console_handler => 'console_output',
            "Starting child...");
        $_[HEAP]{child} = POE::Wheel::Run->new( Program => \@CHILD,
            StdoutEvent  => "child_stdout", StderrEvent  => "child_stderr", );
        $poe_kernel->sig_child($_[HEAP]{child}->PID, "child_signal");
    },
    child_stdin => sub {
        my ($stdin) = $_[ARG0];
        warn localtime." Send STDIN <$stdin>\n";
        $_[HEAP]{child}->put($stdin);
    },
    child_stdout => sub {
        my ($stdout) = $_[ARG0];
        warn localtime." Got STDOUT <$stdout>\n";
        $poe_kernel->post(console_handler => 'console_output',
            "Child said <$stdout>");
    },
    child_stderr => sub {
        my ($stderr) = $_[ARG0];
        warn localtime." Got STDERR <$stderr>\n";
        $poe_kernel->post(console_handler => 'console_output',
            "Child STDERR <$stderr>");
    },
    child_signal => sub {
        my ($status) = $_[ARG2];
        $poe_kernel->post(console_handler => 'console_output',
            "Child process exited with status $status.");
        $poe_kernel->delay('child_kill');
        delete $_[HEAP]{child};
    },
    child_shutdown => sub {
        $poe_kernel->post(console_handler => 'console_output',
            "Sending child process SIGINT...");
        $_[HEAP]{child}->kill('INT');
        $poe_kernel->delay('child_kill', 5);
    },
    child_kill => sub {
        return unless defined $_[HEAP]{child};
        $poe_kernel->post(console_handler => 'console_output',
            "Sending child process SIGKILL.");
        $_[HEAP]{child}->kill('KILL');
        delete $_[HEAP]{child};
    },
    _stop => sub {  },
}, );

POE::Session->create( inline_states => {
    _start => sub {
        $poe_kernel->alias_set('signal_handler');
        $poe_kernel->sig(INT  => 'signal_shutdown');
        $poe_kernel->sig(TERM => 'signal_shutdown');
        $poe_kernel->sig(HUP  => 'signal_shutdown');
    },
    signal_shutdown => sub {
        my ($signal) = $_[ARG0];
        warn $signal ? "Got $signal, " : '', "Shutting down\n";
        $poe_kernel->post(child_handler   => 'child_shutdown');
        $poe_kernel->post(console_handler => 'console_shutdown');
        $poe_kernel->sig_handled;
    },
    _stop => sub {  },
}, );

$poe_kernel->run;

Пример сеанса:

Starting child...
send Foo
Sat Jun  2 16:44:37 2018 Send STDIN <Foo>
Sat Jun  2 16:44:37 2018 Got STDOUT <FOO>
Child said <FOO>
send Bar
Sat Jun  2 16:44:39 2018 Send STDIN <Bar>
Sat Jun  2 16:44:42 2018 Got STDOUT <foo>
Sat Jun  2 16:44:42 2018 Got STDOUT <BAR>
Child said <foo>
Child said <BAR>
Sat Jun  2 16:44:47 2018 Got STDOUT <bar>
Child said <bar>
quit
Got user request, Shutting down
Sending child process SIGINT...
Child process exited with status 2.

Как видите, консоль остается интерактивной, пока дочерний процессвыполняется с выводом дочернего процесса асинхронно (send Foo, send Bar и quit - это мой ввод с консоли).Обратите внимание, что вы также можете использовать POE::Wheel::ReadLine вместо POE::Wheel::ReadWrite, если вам нужны расширенные функции, такие как история ввода.

...