support for autovivified handles (from Tom Christiansen)
Gurusamy Sarathy [Mon, 6 Mar 2000 07:05:34 +0000 (07:05 +0000)]
p4raw-id: //depot/perl@5575

lib/IPC/Open2.pm
lib/IPC/Open3.pm

index 32282d6..5e1386f 100644 (file)
@@ -1,7 +1,7 @@
 package IPC::Open2;
 
 use strict;
-use vars qw($VERSION @ISA @EXPORT);
+our ($VERSION, @ISA, @EXPORT);
 
 require 5.000;
 require Exporter;
@@ -17,47 +17,64 @@ IPC::Open2, open2 - open a process for both reading and writing
 =head1 SYNOPSIS
 
     use IPC::Open2;
-    $pid = open2(\*RDR, \*WTR, 'some cmd and args');
-      # or
-    $pid = open2(\*RDR, \*WTR, 'some', 'cmd', 'and', 'args');
+
+    $pid = open2(\*RDRFH, \*WTRFH, 'some cmd and args');
+      # or without using the shell
+    $pid = open2(\*RDRFH, \*WTRFH, 'some', 'cmd', 'and', 'args');
+
+    # or with handle autovivification
+    my($rdrfh, $wtrfh);
+    $pid = open2($rdrfh, $wtrfh, 'some cmd and args');
+      # or without using the shell
+    $pid = open2($rdrfh, $wtrfh, 'some', 'cmd', 'and', 'args');
 
 =head1 DESCRIPTION
 
-The open2() function spawns the given $cmd and connects $rdr for
-reading and $wtr for writing.  It's what you think should work 
+The open2() function runs the given $cmd and connects $rdrfh for
+reading and $wtrfh for writing.  It's what you think should work 
 when you try
 
-    open(HANDLE, "|cmd args|");
+    $pid = open(HANDLE, "|cmd args|");
 
 The write filehandle will have autoflush turned on.
 
-If $rdr is a string (that is, a bareword filehandle rather than a glob
-or a reference) and it begins with ">&", then the child will send output
-directly to that file handle.  If $wtr is a string that begins with
-"<&", then WTR will be closed in the parent, and the child will read
+If $rdrfh is a string (that is, a bareword filehandle rather than a glob
+or a reference) and it begins with C<< >& >>, then the child will send output
+directly to that file handle.  If $wtrfh is a string that begins with
+C<< <& >>", then $wtrfh will be closed in the parent, and the child will read
 from it directly.  In both cases, there will be a dup(2) instead of a
 pipe(2) made.
 
-open2() returns the process ID of the child process.  It doesn't return on
-failure: it just raises an exception matching C</^open2:/>.
-
-=head1 WARNING 
+If either reader or writer is the null string, this will be replaced
+by an autogenerated filehandle.  If so, you must pass a valid lvalue
+in the parameter slot so it can be overwritten in the caller, or
+an exception will be raised.
 
-It will not create these file handles for you.  You have to do this yourself.
-So don't pass it empty variables expecting them to get filled in for you.
+open2() returns the process ID of the child process.  It doesn't return on
+failure: it just raises an exception matching C</^open2:/>.  However,
+C<exec> failures in the child are not detected.  You'll have to
+trap SIGPIPE yourself.
 
-Additionally, this is very dangerous as you may block forever.
-It assumes it's going to talk to something like B<bc>, both writing to
-it and reading from it.  This is presumably safe because you "know"
-that commands like B<bc> will read a line at a time and output a line at
-a time.  Programs like B<sort> that read their entire input stream first,
-however, are quite apt to cause deadlock.  
+This whole affair is quite dangerous, as you may block forever.  It
+assumes it's going to talk to something like B<bc>, both writing
+to it and reading from it.  This is presumably safe because you
+"know" that commands like B<bc> will read a line at a time and
+output a line at a time.  Programs like B<sort> that read their
+entire input stream first, however, are quite apt to cause deadlock.
 
 The big problem with this approach is that if you don't have control 
 over source code being run in the child process, you can't control
 what it does with pipe buffering.  Thus you can't just open a pipe to
 C<cat -v> and continually read and write a line from it.
 
+The IO::Pty and Expect modules from CPAN can help with this, as they
+provide a real tty (well, a pseudo-tty, actually), which gets you
+back to line buffering in the invoked command again.
+
+=head1 WARNING 
+
+The order of arguments differs from that of open3().
+
 =head1 SEE ALSO
 
 See L<IPC::Open3> for an alternative that handles STDERR as well.  This
@@ -86,10 +103,9 @@ function is really just a wrapper around open3().
 require IPC::Open3;
 
 sub open2 {
-    my ($read, $write, @cmd) = @_;
     local $Carp::CarpLevel = $Carp::CarpLevel + 1;
     return IPC::Open3::_open3('open2', scalar caller,
-                               $write, $read, '>&STDERR', @cmd);
+                               $_[1], $_[0], '>&STDERR', @_[2 .. $#_]);
 }
 
 1
index d079041..d43f1bd 100644 (file)
@@ -2,9 +2,8 @@ package IPC::Open3;
 
 use strict;
 no strict 'refs'; # because users pass me bareword filehandles
-use vars qw($VERSION @ISA @EXPORT $Me);
+our ($VERSION, @ISA, @EXPORT);
 
-require 5.001;
 require Exporter;
 
 use Carp;
@@ -23,37 +22,43 @@ IPC::Open3, open3 - open a process for reading, writing, and error handling
     $pid = open3(\*WTRFH, \*RDRFH, \*ERRFH,
                    'some cmd and args', 'optarg', ...);
 
+    my($wtr, $rdr, $err);
+    $pid = open3($wtr, $rdr, $err,
+                   'some cmd and args', 'optarg', ...);
+
 =head1 DESCRIPTION
 
 Extremely similar to open2(), open3() spawns the given $cmd and
 connects RDRFH for reading, WTRFH for writing, and ERRFH for errors.  If
-ERRFH is '', or the same as RDRFH, then STDOUT and STDERR of the child are
-on the same file handle.  The WTRFH will have autoflush turned on.
+ERRFH is false, or the same file descriptor as RDRFH, then STDOUT and 
+STDERR of the child are on the same filehandle.  The WTRFH will have
+autoflush turned on.
 
-If WTRFH begins with "E<lt>&", then WTRFH will be closed in the parent, and
+If WTRFH begins with C<< <& >>, then WTRFH will be closed in the parent, and
 the child will read from it directly.  If RDRFH or ERRFH begins with
-"E<gt>&", then the child will send output directly to that file handle.
+C<< >& >>, then the child will send output directly to that filehandle.
 In both cases, there will be a dup(2) instead of a pipe(2) made.
 
-If you try to read from the child's stdout writer and their stderr
-writer, you'll have problems with blocking, which means you'll
-want to use select(), which means you'll have to use sysread() instead
-of normal stuff.
+If either reader or writer is the null string, this will be replaced
+by an autogenerated filehandle.  If so, you must pass a valid lvalue
+in the parameter slot so it can be overwritten in the caller, or 
+an exception will be raised.
 
 open3() returns the process ID of the child process.  It doesn't return on
-failure: it just raises an exception matching C</^open3:/>.
-
-=head1 WARNING
-
-It will not create these file handles for you.  You have to do this
-yourself.  So don't pass it empty variables expecting them to get filled
-in for you.
+failure: it just raises an exception matching C</^open3:/>.  However,
+C<exec> failures in the child are not detected.  You'll have to 
+trap SIGPIPE yourself.
 
-Additionally, this is very dangerous as you may block forever.  It
-assumes it's going to talk to something like B<bc>, both writing to it
-and reading from it.  This is presumably safe because you "know" that
-commands like B<bc> will read a line at a time and output a line at a
-time.  Programs like B<sort> that read their entire input stream first,
+If you try to read from the child's stdout writer and their stderr
+writer, you'll have problems with blocking, which means you'll want
+to use select() or the IO::Select, which means you'd best use
+sysread() instead of readline() for normal stuff.
+
+This is very dangerous, as you may block forever.  It assumes it's
+going to talk to something like B<bc>, both writing to it and reading
+from it.  This is presumably safe because you "know" that commands
+like B<bc> will read a line at a time and output a line at a time.
+Programs like B<sort> that read their entire input stream first,
 however, are quite apt to cause deadlock.
 
 The big problem with this approach is that if you don't have control
@@ -61,12 +66,17 @@ over source code being run in the child process, you can't control
 what it does with pipe buffering.  Thus you can't just open a pipe to
 C<cat -v> and continually read and write a line from it.
 
+=head1 WARNING
+
+The order of arguments differs from that of open2().
+
 =cut
 
 # &open3: Marc Horowitz <marc@mit.edu>
 # derived mostly from &open2 by tom christiansen, <tchrist@convex.com>
 # fixed for 5.001 by Ulrich Kunitz <kunitz@mai-koeln.com>
 # ported to Win32 by Ron Schmidt, Merrill Lynch almost ended my career
+# fixed for autovivving FHs, tchrist again
 #
 # $Id: open3.pl,v 1.1 1993/11/23 06:26:15 marc Exp $
 #
@@ -94,7 +104,7 @@ C<cat -v> and continually read and write a line from it.
 #   rdr or wtr are null
 #   a system call fails
 
-$Me = 'open3 (bug)';   # you should never see this, it's always localized
+our $Me = 'open3 (bug)';       # you should never see this, it's always localized
 
 # Fatal.pm needs to be fixed WRT prototypes.
 
@@ -126,15 +136,27 @@ sub _open3 {
     my($package, $dad_wtr, $dad_rdr, $dad_err, @cmd) = @_;
     my($dup_wtr, $dup_rdr, $dup_err, $kidpid);
 
-    $dad_wtr                   or croak "$Me: wtr should not be null";
-    $dad_rdr                   or croak "$Me: rdr should not be null";
-    $dad_err = $dad_rdr if ($dad_err eq '');
+    # simulate autovivification of filehandles because
+    # it's too ugly to use @_ throughout to make perl do it for us
+    # tchrist 5-Mar-00
+
+    unless (eval  {
+       $dad_wtr = $_[1] = gensym unless defined $dad_wtr && length $dad_wtr;
+       $dad_rdr = $_[2] = gensym unless defined $dad_rdr && length $dad_rdr;
+       1; }) 
+    {
+       # must strip crud for croak to add back, or looks ugly
+       $@ =~ s/(?<=value attempted) at .*//s;
+       croak "$Me: $@";
+    } 
+
+    $dad_err ||= $dad_rdr;
 
     $dup_wtr = ($dad_wtr =~ s/^[<>]&//);
     $dup_rdr = ($dad_rdr =~ s/^[<>]&//);
     $dup_err = ($dad_err =~ s/^[<>]&//);
 
-    # force unqualified filehandles into callers' package
+    # force unqualified filehandles into caller's package
     $dad_wtr = qualify $dad_wtr, $package;
     $dad_rdr = qualify $dad_rdr, $package;
     $dad_err = qualify $dad_err, $package;
@@ -185,7 +207,7 @@ sub _open3 {
            xopen \*STDERR, ">&STDOUT" if fileno(STDERR) != fileno(STDOUT);
        }
        local($")=(" ");
-       exec @cmd
+       exec @cmd # XXX: wrong process to croak from
            or croak "$Me: exec of @cmd failed";
     } elsif ($do_spawn) {
        # All the bookkeeping of coincidence between handles is