http://www.perlmonks.org?node_id=861446

gnosti has asked for the wisdom of the Perl Monks concerning the following question:

I would like to talk to an external program (midish) from perl using Expect (or Expect::Simple). I want to spawn this program once, keeping the child process loaded while I do other stuff. AFAIK Midish is well-behaved, using STDIN and STDOUT for I/O. However I'm having a hard time getting a reponse reliably after sending a command.

Update 1: I will investigate approaches other than Expect.

Update 2: Zentara's solution using IPC::Open3 is *way* simpler.

My first experiment was a simple command loop:

- get a prompt ("+ready")

- send a command

- get a reply

I thought I would try with Expect::Simple, however some buffering or command-ordering problems arise.

------ test-expect-simple ---- use Modern::Perl; use Expect::Simple; $| = 1; my $obj = Expect::Simple->new({ Cmd => [ midish => '-v'], Prompt => '+ready', DisconnectCmd => 'exit', Verbose => 0, Debug => 0, Timeout => 100 }); sub prompt { print "midish> " } while ( prompt(), my $cmd = <STDIN> ){ exit if $cmd =~ 'quit'; chomp $cmd; $cmd .= "\r"; $obj->send( $cmd ); my @lines = split "\n", $obj->before; splice(@lines, 0, 2); # throw away the first two lines say join "\n", @lines; } __END__ $perl test-expect-simple midish> asdf asdf: no such proc midish> 1234 midish> ffff 3.5: statement or proc definition expected midish> ^C

Comment: I'm not getting the full message after sending a command. Maybe it's a problem with Expect::Simple.

So here is the next try, a command loop using Expect.

------ test-expect-loop ----- use Expect; use Modern::Perl; my $exp = Expect->spawn("midish","-v") or die "Couldn't start program: $!\n"; # don't copy program output to STDOUT $exp->log_stdout(0); $exp->expect(1, '+ready', \&do_cmd); sub do_cmd { map{say "midish: $_"} split "\n", $exp->before; print "enter command >> "; my $cmd = <STDIN>; $exp->send($cmd); exp_continue; } $exp->soft_close(); __END__ program output: $ perl test-expect-loop enter command >> adsfsd midish: midish: adsfsd midish: adsfsd: no such proc enter command >> 1234 midish: midish: 1234 midish: 2.5: statement or proc definition expected enter command >> ffff midish: midish: ffff midish: ffff: no such proc enter command >> exit $

Good, I'm getting output after every command. But my application is structured more like this:

call_to_expect($one_command); execute_other_code(); call_to_expect($another_command); ------ test-expect-unrolled ----- use Expect; use Modern::Perl; my $exp = Expect->spawn("midish","-v") or die "Couldn't start program: $!\n"; # don't copy program output to STDOUT $exp->log_stdout(0); $exp->expect(1, '+ready', \&do_cmd); print_output(); $exp->expect(1, '+ready', \&do_cmd); print_output(); $exp->expect(1, '+ready', \&do_cmd); print_output(); sub do_cmd { print "enter command >> "; my $cmd = <STDIN>; $exp->send($cmd); } sub print_output { map{say "midish: $_"} split "\n", $exp->before; } $exp->soft_close(); __END__ program output: $ perl test-expect-unrolled enter command >> asdf enter command >> 1234 midish: midish: asdf midish: asdf: no such proc enter command >> ffff midish: midish: 1234 midish: 2.5: statement or proc definition expected <hang> ^C $

So, I still have a problem getting a one-to-one correspondence between the command string and the result string.

Any ideas how I could make one call to send a command and get a response, in a way that I can repeat indefinitely? Thanks for reading!