【问题标题】:Perl run interactive external application with callbackPerl 运行带有回调的交互式外部应用程序
【发布时间】:2018-06-02 12:58:29
【问题描述】:

我正在寻找一种方法来执行以下操作:

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

我找到了 Expect, 和IPC,但到目前为止我发现的一切似乎都在运行-> 写入-> 读取-> 退出的上下文中,我需要这个外部应用程序继续运行并且Perl 脚本继续响应直到我杀死两者都有。

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

【问题讨论】:

  • 你在 Expect 的哪里找到了出口?
  • @choroba 抱歉,选词不当。我发现的所有示例似乎都在运行外部程序,将一些东西打印到 STDIN,然后等待它退出并捕获 STDOUT 并在它退出后做一些事情,而不是绑定一个子例程以基于 STDIN 打印东西在 STDOUT 的“新”输出上。我觉得期望应该能够做到这一点,但我不确定所以我想问,所以我不会走错路重新发明轮子。 :)
  • 我刚刚找到了一种使用 Expect 的方法。对不起! $ob->expect [ qr/regex1/ => sub {} ]
  • @ErikaF。您应该发布您的问题的答案,以帮助其他来到这里的人。否则这个问题没有什么价值。
  • @Erika:请写下您的解决方案作为答案。 Stack Overflow 问题的主要用途不是帮助他们的作者,而是在其他地方发生类似问题时帮助世界其他地方。您应该考虑以与在 Wikipedia 上创建新页面相同的方式提出问题。

标签: shell perl wrapper


【解决方案1】:

我需要这个外部应用程序继续运行,并且 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-30
    • 2011-09-07
    • 2012-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-11
    相关资源
    最近更新 更多