X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FShell.pm;h=e9af9c432470d6fe1adc7cde843f909a3ab38baf;hb=4bd7b839c96352aa075f714f4c1aaa963c29018e;hp=0177479de51e9abe19fb1028b56d19e21f6cd57d;hpb=c529f79d594c53d3968d464c57ac24a21137dd09;p=p5sagit%2Fp5-mst-13.2.git diff --git a/lib/Shell.pm b/lib/Shell.pm index 0177479..e9af9c4 100644 --- a/lib/Shell.pm +++ b/lib/Shell.pm @@ -1,108 +1,148 @@ package Shell; -use vars qw($capture_stderr $VERSION); +use 5.006_001; +use strict; +use warnings; +use File::Spec::Functions; -$VERSION = '0.2'; +our($capture_stderr, $raw, $VERSION, $AUTOLOAD); + +$VERSION = '0.7'; + +sub new { bless \my $foo, shift } +sub DESTROY { } sub import { my $self = shift; my ($callpack, $callfile, $callline) = caller; my @EXPORT; if (@_) { - @EXPORT = @_; - } - else { - @EXPORT = 'AUTOLOAD'; + @EXPORT = @_; + } else { + @EXPORT = 'AUTOLOAD'; } - foreach $sym (@EXPORT) { + foreach my $sym (@EXPORT) { + no strict 'refs'; *{"${callpack}::$sym"} = \&{"Shell::$sym"}; } -}; +} + +# NOTE: this is used to enable constant folding in +# expressions like (OS eq 'MSWin32') and +# (OS eq 'os2') just like it happened in 0.6 version +# which used eval "string" to install subs on the fly. +use constant OS => $^O; + +=begin private + +=over + +=item B<_make_cmd> + + $sub = _make_cmd($cmd); + $sub = $shell->_make_cmd($cmd); + +Creates a closure which invokes the system command C<$cmd>. -AUTOLOAD { +=back + +=end private + +=cut + +sub _make_cmd { + shift if ref $_[0] && $_[0]->isa( 'Shell' ); + my $cmd = shift; + my $null = File::Spec::Functions::devnull(); + $Shell::capture_stderr ||= 0; + # closing over $^O, $cmd, and $null + return sub { + shift if ref $_[0] && $_[0]->isa( 'Shell' ); + if (@_ < 1) { + $Shell::capture_stderr == 1 ? `$cmd 2>&1` : + $Shell::capture_stderr == -1 ? `$cmd 2>$null` : + `$cmd`; + } elsif (OS eq 'os2') { + local(*SAVEOUT, *READ, *WRITE); + + open SAVEOUT, '>&STDOUT' or die; + pipe READ, WRITE or die; + open STDOUT, '>&WRITE' or die; + close WRITE; + + my $pid = system(1, $cmd, @_); + die "Can't execute $cmd: $!\n" if $pid < 0; + + open STDOUT, '>&SAVEOUT' or die; + close SAVEOUT; + + if (wantarray) { + my @ret = ; + close READ; + waitpid $pid, 0; + @ret; + } else { + local($/) = undef; + my $ret = ; + close READ; + waitpid $pid, 0; + $ret; + } + } else { + my $a; + my @arr = @_; + unless( $Shell::raw ){ + if (OS eq 'MSWin32') { + # XXX this special-casing should not be needed + # if we do quoting right on Windows. :-( + # + # First, escape all quotes. Cover the case where we + # want to pass along a quote preceded by a backslash + # (i.e., C<"param \""" end">). + # Ugly, yup? You know, windoze. + # Enclose in quotes only the parameters that need it: + # try this: c:> dir "/w" + # and this: c:> dir /w + for (@arr) { + s/"/\\"/g; + s/\\\\"/\\\\"""/g; + $_ = qq["$_"] if /\s/; + } + } else { + for (@arr) { + s/(['\\])/\\$1/g; + $_ = $_; + } + } + } + push @arr, '2>&1' if $Shell::capture_stderr == 1; + push @arr, '2>$null' if $Shell::capture_stderr == -1; + open(SUBPROC, join(' ', $cmd, @arr, '|')) + or die "Can't exec $cmd: $!\n"; + if (wantarray) { + my @ret = ; + close SUBPROC; # XXX Oughta use a destructor. + @ret; + } else { + local($/) = undef; + my $ret = ; + close SUBPROC; + $ret; + } + } + }; + } + +sub AUTOLOAD { + shift if ref $_[0] && $_[0]->isa( 'Shell' ); my $cmd = $AUTOLOAD; $cmd =~ s/^.*:://; - eval <<"*END*"; - sub $AUTOLOAD { - if (\@_ < 1) { - \$Shell::capture_stderr ? `$cmd 2>&1` : `$cmd`; - } - elsif ('$^O' eq 'os2') { - local(\*SAVEOUT, \*READ, \*WRITE); - - open SAVEOUT, '>&STDOUT' or die; - pipe READ, WRITE or die; - open STDOUT, '>&WRITE' or die; - close WRITE; - - my \$pid = system(1, '$cmd', \@_); - die "Can't execute $cmd: \$!\\n" if \$pid < 0; - - open STDOUT, '>&SAVEOUT' or die; - close SAVEOUT; - - if (wantarray) { - my \@ret = ; - close READ; - waitpid \$pid, 0; - \@ret; - } - else { - local(\$/) = undef; - my \$ret = ; - close READ; - waitpid \$pid, 0; - \$ret; - } - } - else { - my \$a; - my \@arr = \@_; - if ('$^O' eq 'MSWin32') { - # XXX this special-casing should not be needed - # if we do quoting right on Windows. :-( - # - # First, escape all quotes. Cover the case where we - # want to pass along a quote preceded by a backslash - # (i.e., C<"param \\""" end">). - # Ugly, yup? You know, windoze. - # Enclose in quotes only the parameters that need it: - # try this: c:\> dir "/w" - # and this: c:\> dir /w - for (\@arr) { - s/"/\\\\"/g; - s/\\\\\\\\"/\\\\\\\\"""/g; - \$_ = qq["\$_"] if /\s/; - } - } - else { - for (\@arr) { - s/(['\\\\])/\\\\\$1/g; - \$_ = "'\$_'"; - } - } - push \@arr, '2>&1' if \$Shell::capture_stderr; - open(SUBPROC, join(' ', '$cmd', \@arr, '|')) - or die "Can't exec $cmd: \$!\\n"; - if (wantarray) { - my \@ret = ; - close SUBPROC; # XXX Oughta use a destructor. - \@ret; - } - else { - local(\$/) = undef; - my \$ret = ; - close SUBPROC; - \$ret; - } - } - } -*END* - - die "$@\n" if $@; + no strict 'refs'; + *$AUTOLOAD = _make_cmd($cmd); goto &$AUTOLOAD; } 1; + __END__ =head1 NAME @@ -111,10 +151,82 @@ Shell - run shell commands transparently within perl =head1 SYNOPSIS -See below. + use Shell qw(cat ps cp); + $passwd = cat('new; + print $sh->ls('-l'); =head1 DESCRIPTION +=head2 Caveats + +This package is included as a show case, illustrating a few Perl features. +It shouldn't be used for production programs. Although it does provide a +simple interface for obtaining the standard output of arbitrary commands, +there may be better ways of achieving what you need. + +Running shell commands while obtaining standard output can be done with the +C operator, or by calling C with a filename expression that +ends with C<|>, giving you the option to process one line at a time. +If you don't need to process standard output at all, you might use C +(in preference of doing a print with the collected standard output). + +Since Shell.pm and all of the aforementioned techniques use your system's +shell to call some local command, none of them is portable across different +systems. Note, however, that there are several built in functions and +library packages providing portable implementations of functions operating +on files, such as: C, C and C, C and C, +C, C, C, C etc. + +Using Shell.pm while importing C creates a subroutine C in the +namespace of the importing package. Calling C with arguments C, +C,... results in a shell command C, where the +function name and the arguments are joined with a blank. (See the subsection +on Escaping magic characters.) Since the result is essentially a command +line to be passed to the shell, your notion of arguments to the Perl +function is not necessarily identical to what the shell treats as a +command line token, to be passed as an individual argument to the program. +Furthermore, note that this implies that C is callable by file name +only, which frequently depends on the setting of the program's environment. + +Creating a Shell object gives you the opportunity to call any command +in the usual OO notation without requiring you to announce it in the +C statement. Don't assume any additional semantics being +associated with a Shell object: in no way is it similar to a shell +process with its environment or current working directory or any +other setting. + +=head2 Escaping Magic Characters + +It is, in general, impossible to take care of quoting the shell's +magic characters. For some obscure reason, however, Shell.pm quotes +apostrophes (C<'>) and backslashes (C<\>) on UNIX, and spaces and +quotes (C<">) on Windows. + +=head2 Configuration + +If you set $Shell::capture_stderr to true, the module will attempt to +capture the standard error output of the process as well. This is +done by adding C<2E&1> to the command line, so don't try this on +a system not supporting this redirection. + +If you set $Shell::raw to true no quoting whatsoever is done. + +=head1 BUGS + +Quoting should be off by default. + +It isn't possible to call shell built in commands, but it can be +done by using a workaround, e.g. shell( '-c', 'set' ). + +Capturing standard error does not work on some systems (e.g. VMS). + +=head1 AUTHOR + Date: Thu, 22 Sep 94 16:18:16 -0700 Message-Id: <9409222318.AA17072@scalpel.netlabs.com> To: perl5-porters@isu.edu @@ -136,7 +248,7 @@ Here's one that'll whack your mind a little out. sub ps; print ps -ww; - cp("/etc/passwd", "/tmp/passwd"); + cp("/etc/passwd", "/etc/passwd.orig"); That's maybe too gonzo. It actually exports an AUTOLOAD to the current package (and uncovered a bug in Beta 3, by the way). Maybe the usual @@ -144,20 +256,14 @@ usage should be use Shell qw(echo cat ps cp); -Larry - - -If you set $Shell::capture_stderr to 1, the module will attempt to -capture the STDERR of the process as well. - -The module now should work on Win32. +Larry Wall - Jenda +Changes by Jenda@Krynicky.cz and Dave Cottle . -=head1 AUTHOR +Changes for OO syntax and bug fixes by Casey West . -Larry Wall +C<$Shell::raw> and pod rewrite by Wolfgang Laun. -Changes by Jenda@Krynicky.cz and Dave Cottle +Rewritten to use closures rather than C by Adriano Ferreira. =cut