develooper Front page | perl.perl5.porters | Postings from March 2000

[PATCH] open2/open3 handle autovivification

From:
Tom Christiansen
Date:
March 5, 2000 18:57
Subject:
[PATCH] open2/open3 handle autovivification
Message ID:
987.952311411@chthon
Tested with:

    $pid = open2( \*Reader, \*Writer, "cat -u -n" );

and 

    my($rdr, $wtr);
    $pid = open2($rdr, $wtr, qw/cat -u -n/)
	or die "can't open2: $!";

And ran it this code:

    print "pid is $pid, wtr is $wtr, rdr is $rdr\n";
    $wtr->autoflush();     # This is default, actually.
    print $wtr "stuff\n";
    $got = <$rdr>;
    print "He said:\t<$got>\n";
    print $wtr "more stuff\n";
    close($wtr);
    $got = <$rdr>;
    print "He said:\t<$got>\n";

Seems to work.  Fixed a few other things, too, mostly
in the docs.

--tom


diff -rub /home/doriath/tchrist/perl-5.5.670/lib/IPC/Open2.pm IPC/Open2.pm
--- /home/doriath/tchrist/perl-5.5.670/lib/IPC/Open2.pm	Tue Jul 20 11:18:00 1999
+++ IPC/Open2.pm	Sun Mar  5 17:49:06 2000
@@ -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 @@
 =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 
-
-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.
+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.
 
-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.  
+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.
+
+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 @@
 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
diff -rub /home/doriath/tchrist/perl-5.5.670/lib/IPC/Open3.pm IPC/Open3.pm
--- /home/doriath/tchrist/perl-5.5.670/lib/IPC/Open3.pm	Tue Jul 20 11:18:00 1999
+++ IPC/Open3.pm	Sun Mar  5 17:49:04 2000
@@ -2,9 +2,8 @@
 
 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 @@
     $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:/>.
+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.
 
-=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.
-
-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 @@
 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 @@
 #   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,28 @@
     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 +208,7 @@
 	    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



nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About