I think the key paragraph from the docs is this:
open3() returns the process ID of the child process. It doesn't return on failure: it just raises an exception matching "/^open3:/". However, "exec" failures in the child are not detected. You'll have to trap SIGPIPE yourself.
In other words, in case of an invalid command, the exec will fail, but not the fork - so you're left with two processes to handle (the parent with $pid > 0, and the child with $pid == 0).
The following should take care of this situation:
#!/usr/bin/perl use warnings; use IPC::Open3; use IO::Select; use POSIX ":sys_wait_h"; use Symbol; sub run { my ($WRITE, $READ, $ERROR); $ERROR=gensym(); my $command="not_a_command"; my $error=""; my $output=""; eval { $pid=open3($WRITE, $READ, $ERROR, "$command"); }; if (!$pid) { # if we get here, the fork succeeded, but the exec (likely) fail +ed... my $err = $@ ? $@ : "unknown error"; # exit child in any case die "Error: Could not execute $command: $err"; } else { close($WRITE); my $selector=IO::Select->new(); $selector->add($READ, $ERROR); while(@ready=$selector->can_read) { foreach (@ready) { if(fileno($_)==fileno($READ)) { $bytes=sysread($READ, $text, 1024); if($bytes==0) { $selector->remove($_); } else { $output.=$text; } } elsif(fileno($_)==fileno($ERROR)) { $bytes=sysread($ERROR, $text, 1024); if($bytes==0) { $selector->remove($_); } else { $error.=$text; } } } } waitpid($pid, 0); } return($output, $error); } my ($output, $error); ($output, $error)=run(); print "$output, $error\n";
Outputs:
$ ./test_eval.pl , Error: Could not execute not_a_command: open3: exec of not_a_command + failed at ./test_eval.pl line 22
To get a clearer understanding of what's going on in your original code, you might want to add a print "\$pid=$pid, \$\$=$$, out=$output, err=$error\n"; before the return in the subroutine... (you'll find that it executes twice - though the output from parent and child will typically be mixed up).
Also, note that you probably don't want -w here, but rather use warnings, or else you'll get unwanted (duplicated) warnings from the child process...
Update: actually, I played with this some more, and I think I spoke too soon... the issue seems to be trickier than I thought :)
The problem with the given code is that the die ... won't actually return the error via the subroutine, but rather print it out directly... Hmm, so we're left with the problem that the error is occurring in the child (-> $@), but we can't easily communicate it back to the parent, which would have to take care of returning it (at least that's how I understand the issue at the moment). Still trying to find a way around the problem... -- but maybe someone else will post a solution before I do :)
Update-2: ...ah yes, as ikegami pointed out (thanks!), we can simply print to STDERR, as we've already set this up... i.e.
... if (!$pid) { # if we get here, the fork succeeded, but the exec (likely) fail +ed... my $err = $@ ? $@ : "unknown error"; # return error, and exit child print STDERR "Error: Could not execute $command: $err"; POSIX::_exit($!); } ...
(IOW, the die ... I originally suggested did do the right thing (i.e. print to STDERR)... except for the END blocks and stuff)
In reply to Re: Doubled print with eval and open3
by almut
in thread Doubled print with eval and open3
by jlm17
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |