[comp.lang.perl] How can I capture STDERR from a command executed externally

rpaul@hopi.intel.COM (Richard Paul~) (05/22/91)

I want to be able to capture STDERR from an external command and do some 
substitution and reprint it back out.  Following is an experiment I was doing
to get this to work.  The ls command used is the GNU ls, which with the -h
option, prints its options to stderr:

#!/stor/gnu/bin/perl

open(STDERR, ">&STDOUT");
open(STDOUT);
select(STDERR); $| = 1;         # make unbuffered

open(TMP, "ls -h >&STDERR");

while(<STDOUT>)
{
	s/\+/\%/g;
	print $_;
}

close(TMP);
close(STDERR);
close(STDOUT);

The stderr is still going to the tty and not the STDERR filehandle.  Is there
a way to capture the STDERR output of the TMP filehandle.  Any help would be
greatly appreciated.  I am using the following perl:

$RCSfile: perl.c,v $$Revision: 4.0.1.1 $$Date: 91/04/11 17:49:05 $
Patch level: 3

Thanx
-- 
--
___________________________________________________________________________
| Name:  Richard Paul                  | US-Mail: 5000 W. Chandler Blvd.  |
| Dept:  AME Methodology, SQA          | M-stop:  CH3-36                  |
| Group: Arizona Microcomputer Eng.    |          Chandler  AZ, 85226     |
| Tel: 602 554 2793, Fax: 602 554 7281 | E-mail: rpaul@sedona.intel.com   |
| UUCP:  decwrl!apple!oliveb!orc!inews!rpaul@sedona.intel.com             |
| Org:   Intel Corporation, ASIC in the Desert,                           |
---------------------------------------------------------------------------

lwall@jpl-devvax.jpl.nasa.gov (Larry Wall) (05/23/91)

In article <4352@inews.intel.com> rpaul@sedona.intel.com writes:
: 
: I want to be able to capture STDERR from an external command and do some 
: substitution and reprint it back out.  Following is an experiment I was doing
: to get this to work.  The ls command used is the GNU ls, which with the -h
: option, prints its options to stderr:
: 
: #!/stor/gnu/bin/perl
: 
: open(STDERR, ">&STDOUT");

Fine.

: open(STDOUT);

What's this supposed to do?

: select(STDERR); $| = 1;         # make unbuffered

You mean, make STDERR command buffered.  Note that this has no effect
on the buffering of subprocesses.

: open(TMP, "ls -h >&STDERR");

This is just plain wrong.  You're trying to open a file called "ls -h >&STDERR",
which I suspect doesn't exist in your current directory.  You have to use a
vertical bar to indicate a piped command, and even if you do that, the string
STDERR will certainly not be recognized by /bin/sh, which only knows about
numeric file descriptors.

: while(<STDOUT>)

Why are you reading from STDOUT?  I'd think you'd want to read from TMP.

: {
: 	s/\+/\%/g;
: 	print $_;
: }
: 
: close(TMP);
: close(STDERR);
: close(STDOUT);
: 
: The stderr is still going to the tty and not the STDERR filehandle.  Is there
: a way to capture the STDERR output of the TMP filehandle.

You're making this all much too hard.  Just say

open(TMP, "ls -h 2>&1 |");
while (<TMP>) {
    tr/+/%/;
    print;
}

Larry

ziegast@eng.umd.edu (Eric W. Ziegast) (05/24/91)

(Larry Wall) writes:
> You're making this all much too hard.  Just say
> 
> open(TMP, "ls -h 2>&1 |");
> while (<TMP>) {
>     tr/+/%/;
>     print;
> }

The above method will merge STDERR into STDOUT.  In his book, Larry also
shows another way of doing this where STDERR and STDOUT are still handled
seperately.

	# Before your command: redirects STDERR
	open(SAVEERR, ">&STDERR");
	open(STDERR, "> stderr_output_filename");
	
	# Commands you would have used normally
	# Ex: open(TMP, "ls -h");
	
	#After your command: restores STDERR
	close STDERR;
	open(STDERR, ">&SAVEERR");

After this, you can open the file "stderr_output_filename" and
read what was sent to STDERR.  If you just want to get rid of STDERR, use
/dev/null for a filename.

The same process would work with STDOUT with a few minor file handle changes.
	s/ERR/OUT/g

+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+
| Eric W. Ziegast      Internet: ziegast@eng.umd.edu |
| Univ. of Merryland   Phonenet: Eric@[301.405.3689] |
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+

Tom Christiansen <tchrist@convex.COM> (05/24/91)

And for those who want to read STDERR but send STDOUT to 
a file (let's call it "kid.out"), do this:


    open (CMD_ERR, "cmd args 3>kid.out 2>&1 1>&3 3>&- |");
    while (<CMD_ERR>) {
	print "line from stderr: ", $_;
    }
    close CMD_ERR;
    warn "cmd exited $?" if $? >>= 8;

    open (KID_OUT, "< kit.out") || die "can't open kid.out: $!";
    while (<KID_OUT>) {
	print "line from stdout: ", $_;
    } 


Hmm, I just had another odd idea.  Let's not use any temp files:

 open (CMD, "3>&1 (cmd args 2>&1 1>&3 3>&- | sed 's/^/STDERR:/' 3>&-) 3>&- |");
 while (<CMD>) {
   if (s/^STDERR://)  {
     print "line from stderr: ", $_;
   } else {
     print "line from stdout: ", $_;
   }
 } 

Now Aren't you glad you had the shell to do that all for you?  Of course,
you didn't *have* to call sed, or the shell either for that matter.  The
perl-only solution I leave up as an exercise to the reader, who is
presumably less tired and more ambitious than I at 3am. :-)


--tom
--
Tom Christiansen		tchrist@convex.com	convex!tchrist
		"So much mail, so little time."