Seitenanfang

Capture STDOUT and STDERR of external command

Dieser Post wurde aus meiner alten WordPress-Installation importiert. Sollte es Darstellungsprobleme, falsche Links oder fehlende Bilder geben, bitte einfach hier einen Kommentar hinterlassen. Danke.


IO::CaptureOutput is a great module but provides limited control. Here is a quick hack to replace the capture_exec function while getting more control about what is being done.

The executed script may have a huge output which ended up filling a variable when using IO::Capture from CPAN but that variable was never used.

Here is my solution capturing standard error and the external command's exit code but ignoring standard output:

use IPC::Open3 qw(open3);use Symbol qw(gensym);use Fcntl;use POSIX ":sys_wait_h";

sub capture_exec {

# Create handles and launch child task my ($wtr, $rdr); my $err = gensym; my $pid = open3($wtr, $rdr, $err, @_); close $wtr; # Child won't expect STDIN

# Set child's STDOUT and STDERR to non-blocking mode. for my $fh ($rdr, $err) { my $flags = ''; fcntl($fh, F_GETFL, $flags) or die "Couldn't get flags for HANDLE : $!\n"; $flags |= O_NONBLOCK; fcntl($fh, F_SETFL, $flags) or die "Couldn't set flags for HANDLE: $!\n"; }

# Prepare vectors for select my $rin = ''; vec($rin, fileno($rdr), 1) = 1; vec($rin, fileno($err), 1) = 1;

# Prepare STDERR buffer my $errors;

while (1) {

# Wait until the child has data to read for us or 1 sec. timeout my $rout; if (select($rout = $rin, undef, undef, 1)) { my $buffer; # Buffer for reading one data block

# Discard any STDOUT data while (sysread($rdr, $buffer, 65535) == 65535) { } # Read 64k blocks as long as we get 64k

# Read STDERR 64k blocks and push then onto the STDERR buffer while (1) { sysread($err, $buffer, 65535); $errors .= $buffer; last if length($buffer) < 65535; } }

# Clean up child process zombie(s) waitpid -1, WNOHANG;

last unless kill 0, $pid; # Child is still running }

return \$errors, $? >> 8;}

It's mostly copy and paste stuff from IPC::Open3, Perldoc SELECT and a Perl cookbook snippet. There are some uncommon things like pre-preparing the $err handle, $rout = $rin syntax within select or usage of sysread. Many of them are explained in the documentation linked above, the others are mistakes I made or might have better alternates (remember TIMTOWTDI).
 

2 Kommentare. Schreib was dazu

  1. dagolden

    use Capture::Tiny 'capture_stderr'; ($stderr, $exit) = capture_stderr { system(@cmd) };

  2. dagolden

    Actually, that snippet above won't avoid passing through lots of output. If you really want to discard output, pass a custom capture() handle to /dev/null and throw away the first return value (which will be empty anyway):


    use Capture::Tiny 'capture'; use File::Spec; use IO::File; (undef, $stderr, $exit) = capture { system(@cmd) } stdout => IO::File->new( File::Spec->devnull );

Schreib was dazu

Die folgenden HTML-Tags sind erlaubt:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>