Reputation: 21
I am looking for a way to do the following:
I have found
Expect
,
and IPC
, but everything I've found so far seems to be in the context of run -> write -> read -> exit, where I need this external application to continue running and the Perl script to continue responding until I kill both.
EDIT: I found a solution in the 'expect' module for perl, by setting the timeout to undef, and calling "exp_continue" after my logic, I was able to keep the script running and handling I/O until I kill it.
Upvotes: 2
Views: 568
Reputation: 3013
I need this external application to continue running and the perl script to continue responding until I kill both.
That's an interesting question - one possible way to solve this is with an event loop framework like POE
. Although it has a bit of a learning curve (but see the cookbook), I've found it great for things like specialized network servers, or for things like your case - staying interactive at the console while other things (network connections, serial ports, etc.) are handled as well.
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;
Example Session:
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.
As you can see, the console remains interactive while the child process is running, with output from the child process shown asynchronously (send Foo
, send Bar
, and quit
are my console input). Note you can also use POE::Wheel::ReadLine
instead of POE::Wheel::ReadWrite
if you want advanced features like an input history.
Upvotes: 1