The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

#!perl
use strict;
# ##### This is the Perl module IPC::Run3::Shell #####
#
# Documentation can be found in the file Shell.pod (or via the perldoc command).
#
# Copyright (c) 2014-2020 Hauke Daempfling (haukex@zero-g.net).
#
# This library is free software; you can redistribute it and/or modify
# it under the same terms as Perl 5 itself.
#
# For more information see the "Perl Artistic License",
# which should have been distributed with your copy of Perl.
# Try the command "perldoc perlartistic" or see
our $VERSION = '0.58';
use Carp;
use Scalar::Util qw/ blessed looks_like_number /;
use Data::Dumper ();
use overload ();
# debugging stuff
# either set env var or set this var externally (may set to a fh / glob ref)
our $DEBUG;
BEGIN { $DEBUG = ! ! $ENV{IPC_RUN3_SHELL_DEBUG} unless $DEBUG }
sub debug { ## no critic (RequireArgUnpacking)
return unless $DEBUG;
return print { ref $DEBUG eq 'GLOB' ? $DEBUG : \*STDERR } "# ", __PACKAGE__, " Debug: ", @_, "\n";
}
my $dumper = Data::Dumper->new([])->Terse(1)->Purity(1)
->Useqq(1)->Quotekeys(0)->Sortkeys(1)->Indent(0)->Pair('=>');
sub _dcopy { # slightly kludgy hack because Dumper with Purity issues warnings about code references (e.g. stdout_filter option)
my $v = shift;
return [ map { _dcopy($_) } @$v ] if ref $v eq 'ARRAY';
return { map { $_ => _dcopy($$v{$_}) } keys %$v } if ref $v eq 'HASH';
return 'CODE' if ref $v eq 'CODE';
return $v;
}
sub pp { return $dumper->Values(_dcopy(\@_))->Reset->Dump } ## no critic (RequireArgUnpacking)
use IPC::Run3 ();
my @RUN3_OPTS = qw/ binmode_stdin binmode_stdout binmode_stderr append_stdout append_stderr return_if_system_error /;
my %KNOWN_OPTS = map { $_=>1 } @RUN3_OPTS,
qw/ show_cmd allow_exit irs chomp stdin stdout stderr fail_on_stderr both stdout_filter /;
our $OBJECT_PACKAGE;
{
package ## no critic (ProhibitMultiplePackages)
IPC::Run3::Shell::Autoload; # hide from PAUSE by splitting onto two lines
BEGIN { $IPC::Run3::Shell::OBJECT_PACKAGE = __PACKAGE__ }
our $AUTOLOAD;
sub AUTOLOAD { ## no critic (ProhibitAutoloading)
my $cmd = $AUTOLOAD;
IPC::Run3::Shell::debug "Autoloading '$cmd'" if $IPC::Run3::Shell::DEBUG;
$cmd =~ s/^.*:://;
no strict 'refs'; ## no critic (ProhibitNoStrict)
*$AUTOLOAD = IPC::Run3::Shell::make_cmd($cmd);
goto &$AUTOLOAD;
}
sub DESTROY {} # so AUTOLOAD isn't called on destruction
}
sub new {
my ($class, %opt) = @_;
return bless \%opt, $OBJECT_PACKAGE;
}
my %EXPORTABLE = map {$_=>1} qw/ make_cmd /; # "run" gets special handling
# this run() is for calling via IPC::Run3::Shell::run(), note that we don't export this below
*run = make_cmd();
sub import {
my ($class, @export) = @_;
my ($callpack) = caller;
return import_into($class, $callpack, @export);
}
sub import_into {
my ($class, $callpack, @export) = @_;
my %opt;
%opt = ( %opt, %{shift @export} ) while ref $export[0] eq 'HASH';
for my $exp (@export) {
if (!defined $exp) {
warnings::warnif('uninitialized','Use of uninitialized value in import');
next;
}
elsif ( !ref($exp) && $exp && ( my ($sym) = $exp=~/^:(\w+)$/ ) ) {
if ($sym eq 'run') {
# instead of exporting 'run', we actually export a make_cmd closure (with default options but *no* arguments)
debug "Exporting '${callpack}::$sym' => make_cmd("._cmd2str(\%opt).")" if $DEBUG;
no strict 'refs'; ## no critic (ProhibitNoStrict)
*{"${callpack}::$sym"} = make_cmd(\%opt);
}
elsif ($sym eq 'AUTOLOAD') {
debug "Exporting '${callpack}::$sym'" if $DEBUG;
no strict 'refs'; ## no critic (ProhibitNoStrict)
*{"${callpack}::AUTOLOAD"} = \&{"${OBJECT_PACKAGE}::AUTOLOAD"};
}
elsif ($sym eq 'FATAL') {
debug "Enabling fatal warnings";
warnings->import(FATAL=>'IPC::Run3::Shell');
}
else {
croak "$class can't export \"$sym\"" unless $EXPORTABLE{$sym};
my $target = __PACKAGE__."::$sym";
debug "Exporting '${callpack}::$sym' => '$target'" if $DEBUG;
no strict 'refs'; ## no critic (ProhibitNoStrict)
*{"${callpack}::$sym"} = \&{$target};
}
}
else {
my ($sym, @cmd) = ref $exp eq 'ARRAY' ? @$exp : ($exp, $exp);
croak "$class: no function name specified" unless $sym;
$sym = _strify($sym); # warn on refs
croak "$class: empty command for function \"$sym\"" unless @cmd;
debug "Exporting '${callpack}::$sym' => make_cmd("._cmd2str(\%opt, @cmd).")" if $DEBUG;
no strict 'refs'; ## no critic (ProhibitNoStrict)
*{"${callpack}::$sym"} = make_cmd(\%opt, @cmd);
}
}
return;
}
sub make_cmd { ## no critic (ProhibitExcessComplexity)
my @omcmd = @_;
warnings::warnif(__PACKAGE__."::make_cmd() may have been called as a method")
if $omcmd[0] && $omcmd[0] eq __PACKAGE__ ;
return sub {
my @acmd = @_; # args to this function call
my @mcmd = @omcmd; # copy of args to make_cmd
# if we are a method, get default options from the object
my %opt = blessed($acmd[0]) && $acmd[0]->isa($OBJECT_PACKAGE) ? %{shift @acmd} : ();
# hashrefs as the first argument of make_cmd and this method override current options
%opt = ( %opt, %{shift @mcmd} ) while ref $mcmd[0] eq 'HASH';
%opt = ( %opt, %{shift @acmd} ) while ref $acmd[0] eq 'HASH';
# now look at the back of @acmd
my @tmp_opts;
push @tmp_opts, pop @acmd while ref $acmd[-1] eq 'HASH';
%opt = ( %opt, %{pop @tmp_opts} ) while @tmp_opts;
# this is for the tests that test the option inheritance mechanism
if (exists $opt{__TEST_OPT_A} || exists $opt{__TEST_OPT_B}) {
return join ',', (
exists $opt{__TEST_OPT_A} ? 'A='.(defined $opt{__TEST_OPT_A} ? $opt{__TEST_OPT_A} : 'undef') : (),
exists $opt{__TEST_OPT_B} ? 'B='.(defined $opt{__TEST_OPT_B} ? $opt{__TEST_OPT_B} : 'undef') : () );
}
# check options for validity
for (keys %opt) {
warnings::warnif(__PACKAGE__.": unknown option \"$_\"")
unless $KNOWN_OPTS{$_};
}
if (defined $opt{stdout_filter}) {
croak __PACKAGE__.": option stdout_filter must be a coderef"
unless ref $opt{stdout_filter} eq 'CODE'}
my $allow_exit = defined $opt{allow_exit} ? $opt{allow_exit} : [0];
if ($allow_exit ne 'ANY') {
$allow_exit = [$allow_exit] unless ref $allow_exit eq 'ARRAY';
warnings::warnif(__PACKAGE__.": allow_exit is empty") unless @$allow_exit;
for (@$allow_exit) {
# We throw our own custom warning instead of Perl's regular warning because Perl's warning
# would be reported in this module instead of the calling code.
warnings::warnif('numeric','Argument "'.(defined($_)?$_:"(undef)").'" isn\'t numeric in allow_exit')
unless defined && looks_like_number($_);
no warnings 'numeric', 'uninitialized'; ## no critic (ProhibitNoWarnings)
$_ = 0+$_; # so later usage as a number isn't a warning
}
}
# Possible To-Do for Later: Define priorities for incompatible options so we can carp instead of croaking?
# Also maybe look at some other places where we croak at runtime to see if there is any way to carp there instead.
croak __PACKAGE__.": can't use options stderr and fail_on_stderr at the same time"
if exists $opt{stderr} && $opt{fail_on_stderr};
croak __PACKAGE__.": can't use options both and stdout at the same time"
if $opt{both} && exists $opt{stdout};
croak __PACKAGE__.": can't use options both and stderr at the same time"
if $opt{both} && exists $opt{stderr};
croak __PACKAGE__.": can't use options both and fail_on_stderr at the same time"
if $opt{both} && $opt{fail_on_stderr};
# assemble command (after having processed any option hashes etc.)
my @fcmd = (@mcmd, @acmd);
croak __PACKAGE__.": empty command" unless @fcmd;
# stringify the stringifiable things, handle undef, and warn on refs
@fcmd = map {_strify($_)} @fcmd;
# prepare STDOUT redirection
my ($out, $stdout) = ('');
if (exists $opt{stdout}) ## no critic (ProhibitCascadingIfElse)
{ $stdout = $opt{stdout} }
elsif ($opt{both})
{ $stdout = defined(wantarray) ? \$out : undef }
elsif (wantarray)
{ $stdout = $out = [] }
elsif (defined(wantarray))
{ $stdout = \$out }
else
{ $stdout = undef }
# prepare STDERR redirection
my ($err, $stderr) = ('');
if (exists $opt{stderr})
{ $stderr = $opt{stderr} }
elsif ($opt{fail_on_stderr})
{ $stderr = \$err }
elsif ($opt{both})
{ $stderr = wantarray ? \$err : ( defined(wantarray) ? \$out : undef ) }
else
{ $stderr = undef }
# prepare options hash
my %r3o = ( return_if_system_error=>1 );
for (@RUN3_OPTS) { $r3o{$_} = $opt{$_} if exists $opt{$_} }
# execute and process
debug "run3("._cmd2str(@fcmd).") ".pp(\%opt) if $DEBUG;
print { ref $opt{show_cmd} eq 'GLOB' ? $opt{show_cmd} : \*STDERR } '$ '._cmd2str(@fcmd)."\n" if $opt{show_cmd};
local $/ = exists $opt{irs} ? $opt{irs} : $/;
# NOTE that we've documented that the user can rely on $?, so don't mess with it
IPC::Run3::run3( \@fcmd, $opt{stdin}, $stdout, $stderr, \%r3o )
or croak __PACKAGE__." (internal): run3 \"$fcmd[0]\" failed";
my $exitcode = $?>>8;
croak "Command \"$fcmd[0]\" failed: process wrote to STDERR: \"$err\""
if $opt{fail_on_stderr} && $err ne '' && $err ne $/;
if ($? == -1) {
warnings::warnif("Command \"$fcmd[0]\" failed: $!");
return
}
elsif ($?&127) {
warnings::warnif(sprintf("Command \"%s\" failed: signal %d, %s coredump",
$fcmd[0], ($?&127), ($?&128)?'with':'without' ))
}
else {
# allow_exit is checked for validity above
warnings::warnif("Command \"$fcmd[0]\" failed: exit status $exitcode")
unless $allow_exit eq 'ANY' || grep {$_==$exitcode} @$allow_exit;
}
return unless defined wantarray;
if (exists $opt{stdout})
{ return $exitcode }
elsif ($opt{both}) {
chomp($out,$err) if $opt{chomp};
if ($opt{stdout_filter}) { for ($out) { $opt{stdout_filter}->() } }
return wantarray ? ($out, $err, $exitcode) : $out
}
elsif (wantarray) {
chomp(@$out) if $opt{chomp};
if ($opt{stdout_filter}) { for (@$out) { $opt{stdout_filter}->() } }
return @$out
}
else {
chomp($out) if $opt{chomp};
if ($opt{stdout_filter}) { for ($out) { $opt{stdout_filter}->() } }
return $out
}
}
}
# This function attempts to behave like normal Perl stringification, but it adds two things:
# 1. Warnings on undef, in Perl's normal "uninitialized" category, the difference being that
# with "warnif", they will appear to originate in the calling code, and not in this function.
# 2. Warn if we are passed a reference that is not an object with overloaded stringification,
# since that is much more likely to be a mistake on the part of the user instead of intentional.
sub _strify {
my ($x) = @_;
if (!defined $x) {
warnings::warnif('uninitialized','Use of uninitialized value in argument list');
return "" }
elsif (blessed($x) && overload::Overloaded($x)) { # an object with overloading
if (overload::Method($x,'""')) # stringification explicitly defined, it'll work
{ return "$x" }
# Else, stringification is not explicitly defined - stringification *may* work through autogeneration, but it may also die.
# There doesn't seem to be a way to ask Perl if stringification will die or not other than trying it out with eval.
# Reminder to self: "$x" will always be defined; even if overloaded stringify returns undef;
# undef interpolated into the string will cause warning, but the resulting empty string is still defined.
elsif (defined(my $rv = eval { "$x" }))
{ return $rv }
elsif ($@=~/\bno method found\b/) { # overloading failed, throw custom error
# Note: as far as I can tell the message "no method found"
# hasn't changed since its intoduction in Perl 5.000
# (e.g. git log -p -S 'no method found' gv.c )
# Perl bug #31793, which relates to overload::StrVal, apparently also caused problems with Carp
if (!$overload::VERSION || $overload::VERSION<1.04)
{ die "Package ".ref($x)." doesn't overload stringification: $@" } ## no critic (RequireCarping)
else
{ croak "Package ".ref($x)." doesn't overload stringification: $@" }
}
# something other than overloading failed, just re-throw
else { die $@ } ## no critic (RequireCarping)
# Remember that Perl's normal behavior should stringification not be
# available is to die; we're just propagating that behavior outward.
}
else {
# Note that objects without any overloading will stringify using Perl's default mechanism
ref($x) and warnings::warnif(__PACKAGE__.": argument list contains references/objects");
return "$x" }
}
# function for sorta-pretty-printing commands
sub _cmd2str {
my @c = @_;
my $o = '';
for my $c (@c) {
$o .= ' ' if $o;
if (ref $c eq 'HASH') { # options
# note we don't pay attention to where in the argument list we are
# (I don't expect hashrefs to appear as arguments, the user is even warned about them)
$o .= pp($c);
}
else {
my $s = defined $c ? "$c" : '';
$s = pp($s) if $s=~/[^\w\-\=\/\.]/;
$o .= $s;
}
}
return $o;
}
1;