Perl使用回调运行交互式外部应用程序

问题描述 投票:2回答:1

我正在寻找一种方法来做到以下几点:

  1. 让perl脚本执行外部交互式shell程序
  2. 捕获外部程序的STDIN和STDOUT
  3. 当外部程序向STDOUT打印任何内容时,绑定某种回调子
  4. 使用此子例程解析STDOUT,如果它与正则表达式匹配,则打印对外部程序的STDIN响应。

我找到了ExpectIPC,但到目前为止我发现的所有内容似乎都在运行 - > write - > read - > exit的上下文中,我需要这个外部应用程序继续运行并且Perl脚本继续响应直到我杀了两个。

编辑:我在perl的'expect'模块中找到了一个解决方案,通过将超时设置为undef,并在我的逻辑后调用“exp_continue”,我能够保持脚本运行并处理I / O直到我杀死它。

shell perl wrapper
1个回答
0
投票

我需要这个外部应用程序继续运行和perl脚本继续响应,直到我杀死它们。

这是一个有趣的问题 - 解决这个问题的一种可能方法是使用像POE这样的事件循环框架。虽然它有一点学习曲线(但请参阅cookbook),我发现它非常适合像专门的网络服务器或类似的情况 - 在控制台保持交互,而其他方面(网络连接,串行端口)等等。

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 Foosend Barquit是我的控制台输入)。请注意,如果您需要输入历史记录等高级功能,也可以使用POE::Wheel::ReadLine而不是POE::Wheel::ReadWrite

© www.soinside.com 2019 - 2024. All rights reserved.