Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPC::Open2 and IPC::Open3 documentation updates #16688

Closed
p5pRT opened this issue Sep 10, 2018 · 10 comments · Fixed by #17374
Closed

IPC::Open2 and IPC::Open3 documentation updates #16688

p5pRT opened this issue Sep 10, 2018 · 10 comments · Fixed by #17374

Comments

@p5pRT
Copy link

p5pRT commented Sep 10, 2018

Migrated from rt.perl.org#133509 (status was 'open')

Searchable as RT133509$

@p5pRT
Copy link
Author

p5pRT commented Sep 10, 2018

From @Grinnz

[Perl] IPC​::Open2 and IPC​::Open3 docs contain some out of date practices
and confusing terminology, which makes them more difficult for newcomers to
make use of. Attached will be a patch to update it in several ways​:

* Remove direct usage of bareword filehandles and use lexical filehandles
for the first example in each synopsis.
* Add examples using STDIN and STDOUT filehandles, and examples using
already open filehandles, with explicit examples of where these handles
come from.
* Declare variables with 'my' where appropriate and condense declarations
inline.
* Add comments in synopsis describing the purpose of each example.
* Consistency of referencing synopsis variables from the description.
* Replace ambiguous phrase 'null string' with 'empty string or undefined'
which is also more correct here.
* Add links to referenced CPAN modules and manpages.
* Better describe the reason for using gensym in IPC​::Open3 and how to use
it.

-Dan

@p5pRT
Copy link
Author

p5pRT commented Sep 10, 2018

From @Grinnz

Patch is attached.

@p5pRT
Copy link
Author

p5pRT commented Sep 10, 2018

From @Grinnz

ipc_open23_docs.patch
diff --git a/ext/IPC-Open3/lib/IPC/Open2.pm b/ext/IPC-Open3/lib/IPC/Open2.pm
index 9e27144571..9c70e5b455 100644
--- a/ext/IPC-Open3/lib/IPC/Open2.pm
+++ b/ext/IPC-Open3/lib/IPC/Open2.pm
@@ -18,38 +18,41 @@ IPC::Open2 - open a process for both reading and writing using open2()
 
     use IPC::Open2;
 
-    $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some cmd and args');
-      # or without using the shell
-    $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some', 'cmd', 'and', 'args');
+    my $pid = open2(my $chld_out, my $chld_in, 'some', 'cmd', 'and', 'args');
+    # or passing the command through the shell
+    my $pid = open2(my $chld_out, my $chld_in, 'some cmd and args');
 
-    # or with handle autovivification
-    my($chld_out, $chld_in);
-    $pid = open2($chld_out, $chld_in, 'some cmd and args');
-      # or without using the shell
-    $pid = open2($chld_out, $chld_in, 'some', 'cmd', 'and', 'args');
+    # read from parent STDIN and write to already open handle
+    open my $outfile, '>', 'outfile.txt' or die "open failed: $!";
+    my $pid = open2($outfile, '<&STDIN', 'some', 'cmd', 'and', 'args');
 
+    # read from already open handle and write to parent STDOUT
+    open my $infile, '<', 'infile.txt' or die "open failed: $!";
+    my $pid = open2('>&STDOUT', $infile, 'some', 'cmd', 'and', 'args');
+
+    # reap zombie and retrieve exit status
     waitpid( $pid, 0 );
     my $child_exit_status = $? >> 8;
 
 =head1 DESCRIPTION
 
-The open2() function runs the given $cmd and connects $chld_out for
+The open2() function runs the given command and connects $chld_out for
 reading and $chld_in for writing.  It's what you think should work 
 when you try
 
-    $pid = open(HANDLE, "|cmd args|");
+    my $pid = open(my $fh, "|cmd args|");
 
-The write filehandle will have autoflush turned on.
+The $chld_in filehandle will have autoflush turned on.
 
 If $chld_out 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 $chld_in is a string that begins with
 C<< <& >>, then $chld_in 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.
+read from it directly.  In both cases, there will be a L<dup(2)> instead of a
+L<pipe(2)> made.
 
-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
+If either reader or writer is the empty string or undefined, 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.
 
@@ -66,10 +69,10 @@ Failing to do this can result in an accumulation of defunct or "zombie"
 processes.  See L<perlfunc/waitpid> for more information.
 
 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
+assumes it's going to talk to something like L<bc(1)>, 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
+"know" that commands like L<bc(1)> will read a line at a time and
+output a line at a time.  Programs like L<sort(1)> 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 
@@ -77,8 +80,8 @@ 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
+The L<IO::Pty> and L<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 
diff --git a/ext/IPC-Open3/lib/IPC/Open3.pm b/ext/IPC-Open3/lib/IPC/Open3.pm
index e5924a30a6..58b92b8948 100644
--- a/ext/IPC-Open3/lib/IPC/Open3.pm
+++ b/ext/IPC-Open3/lib/IPC/Open3.pm
@@ -19,37 +19,47 @@ IPC::Open3 - open a process for reading, writing, and error handling using open3
 
 =head1 SYNOPSIS
 
-    $pid = open3(\*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR,
-		    'some cmd and args', 'optarg', ...);
-
-    my($wtr, $rdr, $err);
-    use Symbol 'gensym'; $err = gensym;
-    $pid = open3($wtr, $rdr, $err,
-		    'some cmd and args', 'optarg', ...);
-
+    use Symbol 'gensym'; # vivify a separate handle for STDERR
+    my $pid = open3(my $chld_in, my $chld_out, my $chld_err = gensym,
+		    'some', 'cmd', 'and', 'args');
+    # or pass the command through the shell
+    my $pid = open3(my $chld_in, my $chld_out, my $chld_err = gensym,
+		    'some cmd and args');
+
+    # read from parent STDIN, send STDOUT and STDERR to already open handle
+    open my $outfile, '>>', 'output.txt' or die "open failed: $!";
+    my $pid = open3('<&STDIN', $outfile, undef,
+		    'some', 'cmd', 'and', 'args');
+
+    # pass command through the shell, write to parent STDOUT and STDERR
+    my $pid = open3(my $chld_in, '>&STDOUT', '>&STDERR',
+		    'some', 'cmd', 'and', 'args');
+
+    # reap zombie and retrieve exit status
     waitpid( $pid, 0 );
     my $child_exit_status = $? >> 8;
 
 =head1 DESCRIPTION
 
-Extremely similar to open2(), open3() spawns the given $cmd and
-connects CHLD_OUT for reading from the child, CHLD_IN for writing to
-the child, and CHLD_ERR for errors.  If CHLD_ERR is false, or the
-same file descriptor as CHLD_OUT, then STDOUT and STDERR of the child
-are on the same filehandle (this means that an autovivified lexical
-cannot be used for the STDERR filehandle, see SYNOPSIS).  The CHLD_IN
+Extremely similar to open2(), open3() spawns the given command and
+connects $chld_out for reading from the child, $chld_in for writing to
+the child, and $chld_err for errors.  If $chld_err is false, or the
+same file descriptor as $chld_out, then STDOUT and STDERR of the child
+are on the same filehandle.  This means that an autovivified lexical
+cannot be used for the STDERR filehandle, but gensym from L<Symbol> can
+be used to vivify a new glob reference, see L</SYNOPSIS>.  The $chld_in
 will have autoflush turned on.
 
-If CHLD_IN begins with C<< <& >>, then CHLD_IN will be closed in the
-parent, and the child will read from it directly.  If CHLD_OUT or
-CHLD_ERR begins with 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 $chld_in begins with C<< <& >>, then $chld_in will be closed in the
+parent, and the child will read from it directly.  If $chld_out or
+$chld_err begins with C<< >& >>, then the child will send output
+directly to that filehandle.  In both cases, there will be a L<dup(2)>
+instead of a L<pipe(2)> made.
 
-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.
+If either reader or writer is the empty string or undefined, 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.
 
 The filehandles may also be integers, in which case they are understood
 as file descriptors.
@@ -57,15 +67,15 @@ as file descriptors.
 open3() returns the process ID of the child process.  It doesn't return on
 failure: it just raises an exception matching C</^open3:/>.  However,
 C<exec> failures in the child (such as no such file or permission denied),
-are just reported to CHLD_ERR under Windows and OS/2, as it is not possible
+are just reported to $chld_err under Windows and OS/2, as it is not possible
 to trap them.
 
-If the child process dies for any reason, the next write to CHLD_IN is
+If the child process dies for any reason, the next write to $chld_in is
 likely to generate a SIGPIPE in the parent, which is fatal by default.
 So you may wish to handle this signal.
 
 Note if you specify C<-> as the command, in an analogous fashion to
-C<open(FOO, "-|")> the child process will just be the forked Perl
+C<open(my $fh, "-|")> the child process will just be the forked Perl
 process rather than an external command.  This feature isn't yet
 supported on Win32 platforms.
 
@@ -78,14 +88,14 @@ processes.  See L<perlfunc/waitpid> for more information.
 
 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
+to use select() or L<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
+going to talk to something like L<bc(1)>, 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,
+like L<bc(1)> will read a line at a time and output a line at a time.
+Programs like L<sort(1)> 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

@p5pRT
Copy link
Author

p5pRT commented Sep 10, 2018

From @Grinnz

There was a mistake in a synopsis comment. Fixed patch attached.

@p5pRT
Copy link
Author

p5pRT commented Sep 10, 2018

From @Grinnz

ipc_open23_docs.patch
diff --git a/ext/IPC-Open3/lib/IPC/Open2.pm b/ext/IPC-Open3/lib/IPC/Open2.pm
index 9e27144571..9c70e5b455 100644
--- a/ext/IPC-Open3/lib/IPC/Open2.pm
+++ b/ext/IPC-Open3/lib/IPC/Open2.pm
@@ -18,38 +18,41 @@ IPC::Open2 - open a process for both reading and writing using open2()
 
     use IPC::Open2;
 
-    $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some cmd and args');
-      # or without using the shell
-    $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some', 'cmd', 'and', 'args');
+    my $pid = open2(my $chld_out, my $chld_in, 'some', 'cmd', 'and', 'args');
+    # or passing the command through the shell
+    my $pid = open2(my $chld_out, my $chld_in, 'some cmd and args');
 
-    # or with handle autovivification
-    my($chld_out, $chld_in);
-    $pid = open2($chld_out, $chld_in, 'some cmd and args');
-      # or without using the shell
-    $pid = open2($chld_out, $chld_in, 'some', 'cmd', 'and', 'args');
+    # read from parent STDIN and write to already open handle
+    open my $outfile, '>', 'outfile.txt' or die "open failed: $!";
+    my $pid = open2($outfile, '<&STDIN', 'some', 'cmd', 'and', 'args');
 
+    # read from already open handle and write to parent STDOUT
+    open my $infile, '<', 'infile.txt' or die "open failed: $!";
+    my $pid = open2('>&STDOUT', $infile, 'some', 'cmd', 'and', 'args');
+
+    # reap zombie and retrieve exit status
     waitpid( $pid, 0 );
     my $child_exit_status = $? >> 8;
 
 =head1 DESCRIPTION
 
-The open2() function runs the given $cmd and connects $chld_out for
+The open2() function runs the given command and connects $chld_out for
 reading and $chld_in for writing.  It's what you think should work 
 when you try
 
-    $pid = open(HANDLE, "|cmd args|");
+    my $pid = open(my $fh, "|cmd args|");
 
-The write filehandle will have autoflush turned on.
+The $chld_in filehandle will have autoflush turned on.
 
 If $chld_out 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 $chld_in is a string that begins with
 C<< <& >>, then $chld_in 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.
+read from it directly.  In both cases, there will be a L<dup(2)> instead of a
+L<pipe(2)> made.
 
-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
+If either reader or writer is the empty string or undefined, 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.
 
@@ -66,10 +69,10 @@ Failing to do this can result in an accumulation of defunct or "zombie"
 processes.  See L<perlfunc/waitpid> for more information.
 
 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
+assumes it's going to talk to something like L<bc(1)>, 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
+"know" that commands like L<bc(1)> will read a line at a time and
+output a line at a time.  Programs like L<sort(1)> 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 
@@ -77,8 +80,8 @@ 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
+The L<IO::Pty> and L<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 
diff --git a/ext/IPC-Open3/lib/IPC/Open3.pm b/ext/IPC-Open3/lib/IPC/Open3.pm
index e5924a30a6..58b92b8948 100644
--- a/ext/IPC-Open3/lib/IPC/Open3.pm
+++ b/ext/IPC-Open3/lib/IPC/Open3.pm
@@ -19,37 +19,47 @@ IPC::Open3 - open a process for reading, writing, and error handling using open3
 
 =head1 SYNOPSIS
 
-    $pid = open3(\*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR,
-		    'some cmd and args', 'optarg', ...);
-
-    my($wtr, $rdr, $err);
-    use Symbol 'gensym'; $err = gensym;
-    $pid = open3($wtr, $rdr, $err,
-		    'some cmd and args', 'optarg', ...);
-
+    use Symbol 'gensym'; # vivify a separate handle for STDERR
+    my $pid = open3(my $chld_in, my $chld_out, my $chld_err = gensym,
+		    'some', 'cmd', 'and', 'args');
+    # or pass the command through the shell
+    my $pid = open3(my $chld_in, my $chld_out, my $chld_err = gensym,
+		    'some cmd and args');
+
+    # read from parent STDIN, send STDOUT and STDERR to already open handle
+    open my $outfile, '>>', 'output.txt' or die "open failed: $!";
+    my $pid = open3('<&STDIN', $outfile, undef,
+		    'some', 'cmd', 'and', 'args');
+
+    # pass command through the shell, write to parent STDOUT and STDERR
+    my $pid = open3(my $chld_in, '>&STDOUT', '>&STDERR',
+		    'some', 'cmd', 'and', 'args');
+
+    # reap zombie and retrieve exit status
     waitpid( $pid, 0 );
     my $child_exit_status = $? >> 8;
 
 =head1 DESCRIPTION
 
-Extremely similar to open2(), open3() spawns the given $cmd and
-connects CHLD_OUT for reading from the child, CHLD_IN for writing to
-the child, and CHLD_ERR for errors.  If CHLD_ERR is false, or the
-same file descriptor as CHLD_OUT, then STDOUT and STDERR of the child
-are on the same filehandle (this means that an autovivified lexical
-cannot be used for the STDERR filehandle, see SYNOPSIS).  The CHLD_IN
+Extremely similar to open2(), open3() spawns the given command and
+connects $chld_out for reading from the child, $chld_in for writing to
+the child, and $chld_err for errors.  If $chld_err is false, or the
+same file descriptor as $chld_out, then STDOUT and STDERR of the child
+are on the same filehandle.  This means that an autovivified lexical
+cannot be used for the STDERR filehandle, but gensym from L<Symbol> can
+be used to vivify a new glob reference, see L</SYNOPSIS>.  The $chld_in
 will have autoflush turned on.
 
-If CHLD_IN begins with C<< <& >>, then CHLD_IN will be closed in the
-parent, and the child will read from it directly.  If CHLD_OUT or
-CHLD_ERR begins with 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 $chld_in begins with C<< <& >>, then $chld_in will be closed in the
+parent, and the child will read from it directly.  If $chld_out or
+$chld_err begins with C<< >& >>, then the child will send output
+directly to that filehandle.  In both cases, there will be a L<dup(2)>
+instead of a L<pipe(2)> made.
 
-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.
+If either reader or writer is the empty string or undefined, 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.
 
 The filehandles may also be integers, in which case they are understood
 as file descriptors.
@@ -57,15 +67,15 @@ as file descriptors.
 open3() returns the process ID of the child process.  It doesn't return on
 failure: it just raises an exception matching C</^open3:/>.  However,
 C<exec> failures in the child (such as no such file or permission denied),
-are just reported to CHLD_ERR under Windows and OS/2, as it is not possible
+are just reported to $chld_err under Windows and OS/2, as it is not possible
 to trap them.
 
-If the child process dies for any reason, the next write to CHLD_IN is
+If the child process dies for any reason, the next write to $chld_in is
 likely to generate a SIGPIPE in the parent, which is fatal by default.
 So you may wish to handle this signal.
 
 Note if you specify C<-> as the command, in an analogous fashion to
-C<open(FOO, "-|")> the child process will just be the forked Perl
+C<open(my $fh, "-|")> the child process will just be the forked Perl
 process rather than an external command.  This feature isn't yet
 supported on Win32 platforms.
 
@@ -78,14 +88,14 @@ processes.  See L<perlfunc/waitpid> for more information.
 
 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
+to use select() or L<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
+going to talk to something like L<bc(1)>, 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,
+like L<bc(1)> will read a line at a time and output a line at a time.
+Programs like L<sort(1)> 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

@p5pRT
Copy link
Author

p5pRT commented Sep 10, 2018

From @Grinnz

Sorry... one more time.

@p5pRT
Copy link
Author

p5pRT commented Sep 10, 2018

From @Grinnz

ipc_open23_docs.patch
diff --git a/ext/IPC-Open3/lib/IPC/Open2.pm b/ext/IPC-Open3/lib/IPC/Open2.pm
index 9e27144571..9c70e5b455 100644
--- a/ext/IPC-Open3/lib/IPC/Open2.pm
+++ b/ext/IPC-Open3/lib/IPC/Open2.pm
@@ -18,38 +18,41 @@ IPC::Open2 - open a process for both reading and writing using open2()
 
     use IPC::Open2;
 
-    $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some cmd and args');
-      # or without using the shell
-    $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some', 'cmd', 'and', 'args');
+    my $pid = open2(my $chld_out, my $chld_in, 'some', 'cmd', 'and', 'args');
+    # or passing the command through the shell
+    my $pid = open2(my $chld_out, my $chld_in, 'some cmd and args');
 
-    # or with handle autovivification
-    my($chld_out, $chld_in);
-    $pid = open2($chld_out, $chld_in, 'some cmd and args');
-      # or without using the shell
-    $pid = open2($chld_out, $chld_in, 'some', 'cmd', 'and', 'args');
+    # read from parent STDIN and write to already open handle
+    open my $outfile, '>', 'outfile.txt' or die "open failed: $!";
+    my $pid = open2($outfile, '<&STDIN', 'some', 'cmd', 'and', 'args');
 
+    # read from already open handle and write to parent STDOUT
+    open my $infile, '<', 'infile.txt' or die "open failed: $!";
+    my $pid = open2('>&STDOUT', $infile, 'some', 'cmd', 'and', 'args');
+
+    # reap zombie and retrieve exit status
     waitpid( $pid, 0 );
     my $child_exit_status = $? >> 8;
 
 =head1 DESCRIPTION
 
-The open2() function runs the given $cmd and connects $chld_out for
+The open2() function runs the given command and connects $chld_out for
 reading and $chld_in for writing.  It's what you think should work 
 when you try
 
-    $pid = open(HANDLE, "|cmd args|");
+    my $pid = open(my $fh, "|cmd args|");
 
-The write filehandle will have autoflush turned on.
+The $chld_in filehandle will have autoflush turned on.
 
 If $chld_out 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 $chld_in is a string that begins with
 C<< <& >>, then $chld_in 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.
+read from it directly.  In both cases, there will be a L<dup(2)> instead of a
+L<pipe(2)> made.
 
-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
+If either reader or writer is the empty string or undefined, 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.
 
@@ -66,10 +69,10 @@ Failing to do this can result in an accumulation of defunct or "zombie"
 processes.  See L<perlfunc/waitpid> for more information.
 
 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
+assumes it's going to talk to something like L<bc(1)>, 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
+"know" that commands like L<bc(1)> will read a line at a time and
+output a line at a time.  Programs like L<sort(1)> 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 
@@ -77,8 +80,8 @@ 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
+The L<IO::Pty> and L<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 
diff --git a/ext/IPC-Open3/lib/IPC/Open3.pm b/ext/IPC-Open3/lib/IPC/Open3.pm
index e5924a30a6..4260604226 100644
--- a/ext/IPC-Open3/lib/IPC/Open3.pm
+++ b/ext/IPC-Open3/lib/IPC/Open3.pm
@@ -19,37 +19,47 @@ IPC::Open3 - open a process for reading, writing, and error handling using open3
 
 =head1 SYNOPSIS
 
-    $pid = open3(\*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR,
-		    'some cmd and args', 'optarg', ...);
-
-    my($wtr, $rdr, $err);
-    use Symbol 'gensym'; $err = gensym;
-    $pid = open3($wtr, $rdr, $err,
-		    'some cmd and args', 'optarg', ...);
-
+    use Symbol 'gensym'; # vivify a separate handle for STDERR
+    my $pid = open3(my $chld_in, my $chld_out, my $chld_err = gensym,
+		    'some', 'cmd', 'and', 'args');
+    # or pass the command through the shell
+    my $pid = open3(my $chld_in, my $chld_out, my $chld_err = gensym,
+		    'some cmd and args');
+
+    # read from parent STDIN, send STDOUT and STDERR to already open handle
+    open my $outfile, '>>', 'output.txt' or die "open failed: $!";
+    my $pid = open3('<&STDIN', $outfile, undef,
+		    'some', 'cmd', 'and', 'args');
+
+    # write to parent STDOUT and STDERR
+    my $pid = open3(my $chld_in, '>&STDOUT', '>&STDERR',
+		    'some', 'cmd', 'and', 'args');
+
+    # reap zombie and retrieve exit status
     waitpid( $pid, 0 );
     my $child_exit_status = $? >> 8;
 
 =head1 DESCRIPTION
 
-Extremely similar to open2(), open3() spawns the given $cmd and
-connects CHLD_OUT for reading from the child, CHLD_IN for writing to
-the child, and CHLD_ERR for errors.  If CHLD_ERR is false, or the
-same file descriptor as CHLD_OUT, then STDOUT and STDERR of the child
-are on the same filehandle (this means that an autovivified lexical
-cannot be used for the STDERR filehandle, see SYNOPSIS).  The CHLD_IN
+Extremely similar to open2(), open3() spawns the given command and
+connects $chld_out for reading from the child, $chld_in for writing to
+the child, and $chld_err for errors.  If $chld_err is false, or the
+same file descriptor as $chld_out, then STDOUT and STDERR of the child
+are on the same filehandle.  This means that an autovivified lexical
+cannot be used for the STDERR filehandle, but gensym from L<Symbol> can
+be used to vivify a new glob reference, see L</SYNOPSIS>.  The $chld_in
 will have autoflush turned on.
 
-If CHLD_IN begins with C<< <& >>, then CHLD_IN will be closed in the
-parent, and the child will read from it directly.  If CHLD_OUT or
-CHLD_ERR begins with 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 $chld_in begins with C<< <& >>, then $chld_in will be closed in the
+parent, and the child will read from it directly.  If $chld_out or
+$chld_err begins with C<< >& >>, then the child will send output
+directly to that filehandle.  In both cases, there will be a L<dup(2)>
+instead of a L<pipe(2)> made.
 
-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.
+If either reader or writer is the empty string or undefined, 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.
 
 The filehandles may also be integers, in which case they are understood
 as file descriptors.
@@ -57,15 +67,15 @@ as file descriptors.
 open3() returns the process ID of the child process.  It doesn't return on
 failure: it just raises an exception matching C</^open3:/>.  However,
 C<exec> failures in the child (such as no such file or permission denied),
-are just reported to CHLD_ERR under Windows and OS/2, as it is not possible
+are just reported to $chld_err under Windows and OS/2, as it is not possible
 to trap them.
 
-If the child process dies for any reason, the next write to CHLD_IN is
+If the child process dies for any reason, the next write to $chld_in is
 likely to generate a SIGPIPE in the parent, which is fatal by default.
 So you may wish to handle this signal.
 
 Note if you specify C<-> as the command, in an analogous fashion to
-C<open(FOO, "-|")> the child process will just be the forked Perl
+C<open(my $fh, "-|")> the child process will just be the forked Perl
 process rather than an external command.  This feature isn't yet
 supported on Win32 platforms.
 
@@ -78,14 +88,14 @@ processes.  See L<perlfunc/waitpid> for more information.
 
 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
+to use select() or L<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
+going to talk to something like L<bc(1)>, 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,
+like L<bc(1)> will read a line at a time and output a line at a time.
+Programs like L<sort(1)> 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

@p5pRT
Copy link
Author

p5pRT commented Oct 9, 2018

From @jkeenan

On Mon, 10 Sep 2018 22​:18​:06 GMT, grinnz@​gmail.com wrote​:

Sorry... one more time.

There is a lot to this patch. In what follows I'm responding only to the sections dealing with IPC​::Open2.

diff --git a/ext/IPC-Open3/lib/IPC/Open2.pm b/ext/IPC-Open3/lib/IPC/Open2.pm
index 9e27144571..9c70e5b455 100644
--- a/ext/IPC-Open3/lib/IPC/Open2.pm
+++ b/ext/IPC-Open3/lib/IPC/Open2.pm
@​@​ -18,38 +18,41 @​@​ IPC​::Open2 - open a process for both reading and writing using open2()

 use IPC&#8203;::Open2;

- $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some cmd and args');
- # or without using the shell
- $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some', 'cmd', 'and', 'args');
+ my $pid = open2(my $chld_out, my $chld_in, 'some', 'cmd', 'and', 'args');
+ # or passing the command through the shell
+ my $pid = open2(my $chld_out, my $chld_in, 'some cmd and args');

In the section above you have reversed the order in which the variants are
presented, i.e., from "shell-first" to "shell-second". Was that intentional?
(It made reviewing the patch a bit more difficult for me.)

- # or with handle autovivification
- my($chld_out, $chld_in);
- $pid = open2($chld_out, $chld_in, 'some cmd and args');
- # or without using the shell
- $pid = open2($chld_out, $chld_in, 'some', 'cmd', 'and', 'args');
+ # read from parent STDIN and write to already open handle
+ open my $outfile, '&gt;', 'outfile.txt' or die "open failed​: $!";
+ my $pid = open2($outfile, '<&STDIN', 'some', 'cmd', 'and', 'args');

+ # read from already open handle and write to parent STDOUT
+ open my $infile, '&lt;', 'infile.txt' or die "open failed​: $!";
+ my $pid = open2('>&STDOUT', $infile, 'some', 'cmd', 'and', 'args');
+

In the section above you have removed the "using shell" example. Was that
intentional?

+ # reap zombie and retrieve exit status
waitpid( $pid, 0 );
my $child_exit_status = $? >> 8;

=head1 DESCRIPTION

-The open2() function runs the given $cmd and connects $chld_out for
+The open2() function runs the given command and connects $chld_out for
reading and $chld_in for writing. It's what you think should work
when you try

I think your substitution of 'command' for '$cmd' is good, because what the
doc is trying to do here is to provide an overall description of the
functionality. I would recommend taking that one step farther​: replacing
$chld_out and $chld_in with descriptions of those two filehandles which don't
depend on the variable names used in the SYNOPSIS.

- $pid = open(HANDLE, "|cmd args|");
+ my $pid = open(my $fh, "|cmd args|");

-The write filehandle will have autoflush turned on.
+The $chld_in filehandle will have autoflush turned on.

I think it would be better if you left "write filehandle" in, then went on to
provide a code snippet that illustrates that $chld_in is the write filehandle.

Also, shouldn't '$chld_in' be 'C<$chld_in>'?

If $chld_out 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 $chld_in is a string that begins with
C<< <& >>, then $chld_in 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.
+read from it directly. In both cases, there will be a L<dup(2)> instead of a
+L<pipe(2)> made.

-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
+If either reader or writer is the empty string or undefined, 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.

@​@​ -66,10 +69,10 @​@​ Failing to do this can result in an accumulation of defunct or "zombie"
processes. See L<perlfunc/waitpid> for more information.

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
+assumes it's going to talk to something like L<bc(1)>, 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
+"know" that commands like L<bc(1)> will read a line at a time and
+output a line at a time. Programs like L<sort(1)> 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
@​@​ -77,8 +80,8 @​@​ 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
+The L<IO​::Pty> and L<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

--
James E Keenan (jkeenan@​cpan.org)

@p5pRT
Copy link
Author

p5pRT commented Oct 9, 2018

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Oct 9, 2018

From @Grinnz

Thanks for reviewing, and let me know if there are any parts that should be
extracted to a separate patch, I wasn't sure any changes were unrelated
enough to each other.

On Tue, Oct 9, 2018 at 10​:29 AM James E Keenan via RT <
perlbug-followup@​perl.org> wrote​:

On Mon, 10 Sep 2018 22​:18​:06 GMT, grinnz@​gmail.com wrote​:

diff --git a/ext/IPC-Open3/lib/IPC/Open2.pm
b/ext/IPC-Open3/lib/IPC/Open2.pm
index 9e27144571..9c70e5b455 100644
--- a/ext/IPC-Open3/lib/IPC/Open2.pm
+++ b/ext/IPC-Open3/lib/IPC/Open2.pm
@​@​ -18,38 +18,41 @​@​ IPC​::Open2 - open a process for both reading and
writing using open2()

 use IPC&#8203;::Open2;

- $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some cmd and args');
- # or without using the shell
- $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'some', 'cmd', 'and', 'args');
+ my $pid = open2(my $chld_out, my $chld_in, 'some', 'cmd', 'and',
'args');
+ # or passing the command through the shell
+ my $pid = open2(my $chld_out, my $chld_in, 'some cmd and args');

In the section above you have reversed the order in which the variants are
presented, i.e., from "shell-first" to "shell-second". Was that
intentional?
(It made reviewing the patch a bit more difficult for me.)

Yes, as for most users the shell is the less safe but sometimes necessary
form.

- # or with handle autovivification
- my($chld_out, $chld_in);
- $pid = open2($chld_out, $chld_in, 'some cmd and args');
- # or without using the shell
- $pid = open2($chld_out, $chld_in, 'some', 'cmd', 'and', 'args');
+ # read from parent STDIN and write to already open handle
+ open my $outfile, '&gt;', 'outfile.txt' or die "open failed​: $!";
+ my $pid = open2($outfile, '<&STDIN', 'some', 'cmd', 'and', 'args');

+ # read from already open handle and write to parent STDOUT
+ open my $infile, '&lt;', 'infile.txt' or die "open failed​: $!";
+ my $pid = open2('>&STDOUT', $infile, 'some', 'cmd', 'and', 'args');
+

In the section above you have removed the "using shell" example. Was that
intentional?

I didn't think the variation needed to be represented again and distract
from the other examples being shown.

+ # reap zombie and retrieve exit status

 waitpid\( $pid\, 0 \);
 my $child\_exit\_status = $? >> 8;

=head1 DESCRIPTION

-The open2() function runs the given $cmd and connects $chld_out for
+The open2() function runs the given command and connects $chld_out for
reading and $chld_in for writing. It's what you think should work
when you try

I think your substitution of 'command' for '$cmd' is good, because what the
doc is trying to do here is to provide an overall description of the
functionality. I would recommend taking that one step farther​: replacing
$chld_out and $chld_in with descriptions of those two filehandles which
don't
depend on the variable names used in the SYNOPSIS.

I substituted out '$cmd' because it was no longer being used in the
synopsis (or never was). I still think '$chld_in' and '$chld_out' are the
most concise way to indicate which parameter is being referred to in those
cases.

- $pid = open(HANDLE, "|cmd args|");

+ my $pid = open(my $fh, "|cmd args|");

-The write filehandle will have autoflush turned on.
+The $chld_in filehandle will have autoflush turned on.

I think it would be better if you left "write filehandle" in, then went on
to
provide a code snippet that illustrates that $chld_in is the write
filehandle.

Also, shouldn't '$chld_in' be 'C<$chld_in>'?

I feel it's confusing once you start calling them 'write' or 'read'
filehandles, because they can both be the writer or reader depending if you
are talking about the parent's or child's perspective, that's why I
normalized them to $chld_in and $chld_out.

Variables weren't in C<> so I kept that consistent but I can update them
all to that format as well.

-Dan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant