499 lines
14 KiB
Perl
499 lines
14 KiB
Perl
use strict;
|
|
use warnings;
|
|
|
|
package IO::CaptureOutput;
|
|
# ABSTRACT: (DEPRECATED) capture STDOUT and STDERR from Perl code, subprocesses or XS
|
|
|
|
our $VERSION = '1.1105';
|
|
|
|
use vars qw/@ISA @EXPORT_OK %EXPORT_TAGS $CarpLevel/;
|
|
use Exporter;
|
|
use Carp qw/croak/;
|
|
@ISA = 'Exporter';
|
|
@EXPORT_OK = qw/capture capture_exec qxx capture_exec_combined qxy/;
|
|
%EXPORT_TAGS = (all => \@EXPORT_OK);
|
|
$CarpLevel = 0; # help capture report errors at the right level
|
|
|
|
sub _capture (&@) { ## no critic
|
|
my ($code, $output, $error, $output_file, $error_file) = @_;
|
|
|
|
# check for valid combinations of input
|
|
{
|
|
local $Carp::CarpLevel = 1;
|
|
my $error = _validate($output, $error, $output_file, $error_file);
|
|
croak $error if $error;
|
|
}
|
|
|
|
# if either $output or $error are defined, then we need a variable for
|
|
# results; otherwise we only capture to files and don't waste memory
|
|
if ( defined $output || defined $error ) {
|
|
for ($output, $error) {
|
|
$_ = \do { my $s; $s = ''} unless ref $_;
|
|
$$_ = '' if $_ != \undef && !defined($$_);
|
|
}
|
|
}
|
|
|
|
# merge if same refs for $output and $error or if both are undef --
|
|
# i.e. capture \&foo, undef, undef, $merged_file
|
|
# this means capturing into separate files *requires* at least one
|
|
# capture variable
|
|
my $should_merge =
|
|
(defined $error && defined $output && $output == $error) ||
|
|
( !defined $output && !defined $error ) ||
|
|
0;
|
|
|
|
my ($capture_out, $capture_err);
|
|
|
|
# undef means capture anonymously; anything other than \undef means
|
|
# capture to that ref; \undef means skip capture
|
|
if ( !defined $output || $output != \undef ) {
|
|
$capture_out = IO::CaptureOutput::_proxy->new(
|
|
'STDOUT', $output, undef, $output_file
|
|
);
|
|
}
|
|
if ( !defined $error || $error != \undef ) {
|
|
$capture_err = IO::CaptureOutput::_proxy->new(
|
|
'STDERR', $error, ($should_merge ? 'STDOUT' : undef), $error_file
|
|
);
|
|
}
|
|
|
|
# now that output capture is setup, call the subroutine
|
|
# results get read when IO::CaptureOutput::_proxy objects go out of scope
|
|
&$code();
|
|
}
|
|
|
|
# Extra indirection for symmetry with capture_exec, etc. Gets error reporting
|
|
# to the right level
|
|
sub capture (&@) { ## no critic
|
|
return &_capture;
|
|
}
|
|
|
|
sub capture_exec {
|
|
my @args = @_;
|
|
my ($output, $error);
|
|
my $exit = _capture sub { system _shell_quote(@args) }, \$output, \$error;
|
|
my $success = ($exit == 0 ) ? 1 : 0 ;
|
|
$? = $exit;
|
|
return wantarray ? ($output, $error, $success, $exit) : $output;
|
|
}
|
|
|
|
*qxx = \&capture_exec;
|
|
|
|
sub capture_exec_combined {
|
|
my @args = @_;
|
|
my $output;
|
|
my $exit = _capture sub { system _shell_quote(@args) }, \$output, \$output;
|
|
my $success = ($exit == 0 ) ? 1 : 0 ;
|
|
$? = $exit;
|
|
return wantarray ? ($output, $success, $exit) : $output;
|
|
}
|
|
|
|
*qxy = \&capture_exec_combined;
|
|
|
|
# extra quoting required on Win32 systems
|
|
*_shell_quote = ($^O =~ /MSWin32/) ? \&_shell_quote_win32 : sub {@_};
|
|
sub _shell_quote_win32 {
|
|
my @args;
|
|
for (@_) {
|
|
if (/[ \"]/) { # TODO: check if ^ requires escaping
|
|
(my $escaped = $_) =~ s/([\"])/\\$1/g;
|
|
push @args, '"' . $escaped . '"';
|
|
next;
|
|
}
|
|
push @args, $_
|
|
}
|
|
return @args;
|
|
}
|
|
|
|
# detect errors and return an error message or empty string;
|
|
sub _validate {
|
|
my ($output, $error, $output_file, $error_file) = @_;
|
|
|
|
# default to "ok"
|
|
my $msg = q{};
|
|
|
|
# \$out, \$out, $outfile, $errfile
|
|
if ( defined $output && defined $error
|
|
&& defined $output_file && defined $error_file
|
|
&& $output == $error
|
|
&& $output != \undef
|
|
&& $output_file ne $error_file
|
|
) {
|
|
$msg = "Merged STDOUT and STDERR, but specified different output and error files";
|
|
}
|
|
# undef, undef, $outfile, $errfile
|
|
elsif ( !defined $output && !defined $error
|
|
&& defined $output_file && defined $error_file
|
|
&& $output_file ne $error_file
|
|
) {
|
|
$msg = "Merged STDOUT and STDERR, but specified different output and error files";
|
|
}
|
|
|
|
return $msg;
|
|
}
|
|
|
|
# Captures everything printed to a filehandle for the lifetime of the object
|
|
# and then transfers it to a scalar reference
|
|
package IO::CaptureOutput::_proxy;
|
|
use File::Temp 0.16 'tempfile';
|
|
use File::Basename qw/basename/;
|
|
use Symbol qw/gensym qualify qualify_to_ref/;
|
|
use Carp;
|
|
|
|
sub _is_wperl { $^O eq 'MSWin32' && basename($^X) eq 'wperl.exe' }
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my ($orig_fh, $capture_var, $merge_fh, $capture_file) = @_;
|
|
$orig_fh = qualify($orig_fh); # e.g. main::STDOUT
|
|
my $fhref = qualify_to_ref($orig_fh); # e.g. \*STDOUT
|
|
|
|
# Duplicate the filehandle
|
|
my $saved_fh;
|
|
{
|
|
no strict 'refs'; ## no critic - needed for 5.005
|
|
if ( defined fileno($orig_fh) && ! _is_wperl() ) {
|
|
$saved_fh = gensym;
|
|
open $saved_fh, ">&$orig_fh" or croak "Can't redirect <$orig_fh> - $!";
|
|
}
|
|
}
|
|
|
|
# Create replacement filehandle if not merging
|
|
my ($newio_fh, $newio_file);
|
|
if ( ! $merge_fh ) {
|
|
$newio_fh = gensym;
|
|
if ($capture_file) {
|
|
$newio_file = $capture_file;
|
|
} else {
|
|
(undef, $newio_file) = tempfile;
|
|
}
|
|
open $newio_fh, "+>$newio_file" or croak "Can't write temp file for $orig_fh - $!";
|
|
}
|
|
else {
|
|
$newio_fh = qualify($merge_fh);
|
|
}
|
|
|
|
# Redirect (or merge)
|
|
{
|
|
no strict 'refs'; ## no critic -- needed for 5.005
|
|
open $fhref, ">&".fileno($newio_fh) or croak "Can't redirect $orig_fh - $!";
|
|
}
|
|
|
|
bless [$$, $orig_fh, $saved_fh, $capture_var, $newio_fh, $newio_file, $capture_file], $class;
|
|
}
|
|
|
|
sub DESTROY {
|
|
my $self = shift;
|
|
|
|
my ($pid, $orig_fh, $saved_fh, $capture_var, $newio_fh,
|
|
$newio_file, $capture_file) = @$self;
|
|
return unless $pid eq $$; # only cleanup in the process that is capturing
|
|
|
|
# restore the original filehandle
|
|
my $fh_ref = Symbol::qualify_to_ref($orig_fh);
|
|
select((select ($fh_ref), $|=1)[0]);
|
|
if (defined $saved_fh) {
|
|
open $fh_ref, ">&". fileno($saved_fh) or croak "Can't restore $orig_fh - $!";
|
|
}
|
|
else {
|
|
close $fh_ref;
|
|
}
|
|
|
|
# transfer captured data to the scalar reference if we didn't merge
|
|
# $newio_file is undef if this file handle is merged to another
|
|
if (ref $capture_var && $newio_file) {
|
|
# some versions of perl complain about reading from fd 1 or 2
|
|
# which could happen if STDOUT and STDERR were closed when $newio
|
|
# was opened, so we just squelch warnings here and continue
|
|
local $^W;
|
|
seek $newio_fh, 0, 0;
|
|
$$capture_var = do {local $/; <$newio_fh>};
|
|
}
|
|
close $newio_fh if $newio_file;
|
|
|
|
# Cleanup
|
|
return unless defined $newio_file && -e $newio_file;
|
|
return if $capture_file; # the "temp" file was explicitly named
|
|
unlink $newio_file or carp "Couldn't remove temp file '$newio_file' - $!";
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=pod
|
|
|
|
=encoding UTF-8
|
|
|
|
=head1 NAME
|
|
|
|
IO::CaptureOutput - (DEPRECATED) capture STDOUT and STDERR from Perl code, subprocesses or XS
|
|
|
|
=head1 VERSION
|
|
|
|
version 1.1105
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use IO::CaptureOutput qw(capture qxx qxy);
|
|
|
|
# STDOUT and STDERR separately
|
|
capture { noisy_sub(@args) } \$stdout, \$stderr;
|
|
|
|
# STDOUT and STDERR together
|
|
capture { noisy_sub(@args) } \$combined, \$combined;
|
|
|
|
# STDOUT and STDERR from external command
|
|
($stdout, $stderr, $success) = qxx( @cmd );
|
|
|
|
# STDOUT and STDERR together from external command
|
|
($combined, $success) = qxy( @cmd );
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
B<This module is no longer recommended by the maintainer> - see
|
|
L<Capture::Tiny> instead.
|
|
|
|
This module provides routines for capturing STDOUT and STDERR from perl
|
|
subroutines, forked system calls (e.g. C<system()>, C<fork()>) and from XS
|
|
or C modules.
|
|
|
|
=head1 NAME
|
|
|
|
=head1 FUNCTIONS
|
|
|
|
The following functions will be exported on demand.
|
|
|
|
=head2 capture()
|
|
|
|
capture \&subroutine, \$stdout, \$stderr;
|
|
|
|
Captures everything printed to C<STDOUT> and C<STDERR> for the duration of
|
|
C<&subroutine>. C<$stdout> and C<$stderr> are optional scalars that will
|
|
contain C<STDOUT> and C<STDERR> respectively.
|
|
|
|
C<capture()> uses a code prototype so the first argument can be specified
|
|
directly within brackets if desired.
|
|
|
|
# shorthand with prototype
|
|
capture C< print __PACKAGE__ > \$stdout, \$stderr;
|
|
|
|
Returns the return value(s) of C<&subroutine>. The sub is called in the
|
|
same context as C<capture()> was called e.g.:
|
|
|
|
@rv = capture C< wantarray > ; # returns true
|
|
$rv = capture C< wantarray > ; # returns defined, but not true
|
|
capture C< wantarray >; # void, returns undef
|
|
|
|
C<capture()> is able to capture output from subprocesses and C code, which
|
|
traditional C<tie()> methods of output capture are unable to do.
|
|
|
|
B<Note:> C<capture()> will only capture output that has been written or
|
|
flushed to the filehandle.
|
|
|
|
If the two scalar references refer to the same scalar, then C<STDERR> will
|
|
be merged to C<STDOUT> before capturing and the scalar will hold the
|
|
combined output of both.
|
|
|
|
capture \&subroutine, \$combined, \$combined;
|
|
|
|
Normally, C<capture()> uses anonymous, temporary files for capturing
|
|
output. If desired, specific file names may be provided instead as
|
|
additional options.
|
|
|
|
capture \&subroutine, \$stdout, \$stderr, $out_file, $err_file;
|
|
|
|
Files provided will be clobbered, overwriting any previous data, but will
|
|
persist after the call to C<capture()> for inspection or other
|
|
manipulation.
|
|
|
|
By default, when no references are provided to hold STDOUT or STDERR,
|
|
output is captured and silently discarded.
|
|
|
|
# Capture STDOUT, discard STDERR
|
|
capture \&subroutine, \$stdout;
|
|
|
|
# Discard STDOUT, capture STDERR
|
|
capture \&subroutine, undef, \$stderr;
|
|
|
|
However, even when using C<undef>, output can be captured to specific
|
|
files.
|
|
|
|
# Capture STDOUT to a specific file, discard STDERR
|
|
capture \&subroutine, \$stdout, undef, $outfile;
|
|
|
|
# Discard STDOUT, capture STDERR to a specific file
|
|
capture \&subroutine, undef, \$stderr, undef, $err_file;
|
|
|
|
# Discard both, capture merged output to a specific file
|
|
capture \&subroutine, undef, undef, $mergedfile;
|
|
|
|
It is a fatal error to merge STDOUT and STDERR and request separate,
|
|
specific files for capture.
|
|
|
|
# ERROR:
|
|
capture \&subroutine, \$stdout, \$stdout, $out_file, $err_file;
|
|
capture \&subroutine, undef, undef, $out_file, $err_file;
|
|
|
|
If either STDOUT or STDERR should be passed through to the terminal instead
|
|
of captured, provide a reference to undef -- C<\undef> -- instead of a
|
|
capture variable.
|
|
|
|
# Capture STDOUT, display STDERR
|
|
capture \&subroutine, \$stdout, \undef;
|
|
|
|
# Display STDOUT, capture STDERR
|
|
capture \&subroutine, \undef, \$stderr;
|
|
|
|
=head2 capture_exec()
|
|
|
|
($stdout, $stderr, $success, $exit_code) = capture_exec(@args);
|
|
|
|
Captures and returns the output from C<system(@args)>. In scalar context,
|
|
C<capture_exec()> will return what was printed to C<STDOUT>. In list
|
|
context, it returns what was printed to C<STDOUT> and C<STDERR> as well as
|
|
a success flag and the exit value.
|
|
|
|
$stdout = capture_exec('perl', '-e', 'print "hello world"');
|
|
|
|
($stdout, $stderr, $success, $exit_code) =
|
|
capture_exec('perl', '-e', 'warn "Test"');
|
|
|
|
C<capture_exec> passes its arguments to C<system()> and on MSWin32 will
|
|
protect arguments with shell quotes if necessary. This makes it a handy
|
|
and slightly more portable alternative to backticks, piped C<open()> and
|
|
C<IPC::Open3>.
|
|
|
|
The C<$success> flag returned will be true if the command ran successfully
|
|
and false if it did not (if the command could not be run or if it ran and
|
|
returned a non-zero exit value). On failure, the raw exit value of the
|
|
C<system()> call is available both in the C<$exit_code> returned and in the
|
|
C<$?> variable.
|
|
|
|
($stdout, $stderr, $success, $exit_code) =
|
|
capture_exec('perl', '-e', 'warn "Test" and exit 1');
|
|
|
|
if ( ! $success ) {
|
|
print "The exit code was " . ($exit_code >> 8) . "\n";
|
|
}
|
|
|
|
See L<perlvar> for more information on interpreting a child process exit
|
|
code.
|
|
|
|
=head2 capture_exec_combined()
|
|
|
|
($combined, $success, $exit_code) = capture_exec_combined(
|
|
'perl', '-e', 'print "hello\n"', 'warn "Test\n"
|
|
);
|
|
|
|
This is just like C<capture_exec()>, except that it merges C<STDERR> with
|
|
C<STDOUT> before capturing output.
|
|
|
|
B<Note:> there is no guarantee that text printed to C<STDOUT> and C<STDERR>
|
|
in the subprocess will be appear in order. The actual order will depend on
|
|
how IO buffering is handled in the subprocess.
|
|
|
|
=head2 qxx()
|
|
|
|
This is an alias for C<capture_exec()>.
|
|
|
|
=head2 qxy()
|
|
|
|
This is an alias for C<capture_exec_combined()>.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
=over 4
|
|
|
|
=item *
|
|
|
|
L<Capture::Tiny>
|
|
|
|
=item *
|
|
|
|
L<IPC::Open3>
|
|
|
|
=item *
|
|
|
|
L<IO::Capture>
|
|
|
|
=item *
|
|
|
|
L<IO::Utils>
|
|
|
|
=item *
|
|
|
|
L<IPC::System::Simple>
|
|
|
|
=back
|
|
|
|
=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
|
|
|
|
=head1 SUPPORT
|
|
|
|
=head2 Bugs / Feature Requests
|
|
|
|
Please report any bugs or feature requests through the issue tracker
|
|
at L<https://github.com/dagolden/IO-CaptureOutput/issues>.
|
|
You will be notified automatically of any progress on your issue.
|
|
|
|
=head2 Source Code
|
|
|
|
This is open source software. The code repository is available for
|
|
public review and contribution under the terms of the license.
|
|
|
|
L<https://github.com/dagolden/IO-CaptureOutput>
|
|
|
|
git clone https://github.com/dagolden/IO-CaptureOutput.git
|
|
|
|
=head1 AUTHORS
|
|
|
|
=over 4
|
|
|
|
=item *
|
|
|
|
Simon Flack <simonflk@cpan.org>
|
|
|
|
=item *
|
|
|
|
David Golden <dagolden@cpan.org>
|
|
|
|
=back
|
|
|
|
=head1 CONTRIBUTORS
|
|
|
|
=for stopwords David Golden José Joaquín Atria Mike Latimer Olivier Mengué Tony Cook
|
|
|
|
=over 4
|
|
|
|
=item *
|
|
|
|
David Golden <xdg@xdg.me>
|
|
|
|
=item *
|
|
|
|
José Joaquín Atria <jjatria@gmail.com>
|
|
|
|
=item *
|
|
|
|
Mike Latimer <mlatimer@suse.com>
|
|
|
|
=item *
|
|
|
|
Olivier Mengué <dolmen@cpan.org>
|
|
|
|
=item *
|
|
|
|
Tony Cook <tony@develop-help.com>
|
|
|
|
=back
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
This software is copyright (c) 2019 by Simon Flack and David Golden.
|
|
|
|
This is free software; you can redistribute it and/or modify it under
|
|
the same terms as the Perl 5 programming language system itself.
|
|
|
|
=cut
|