Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

exec() and redirecting STDOUT/STDERR

Status
Not open for further replies.

antred

Programmer
Jul 26, 2004
4
DE
Hi all,

I'm trying to write a function that runs a command line (like say .... ls -l) and captures:

* its STDOUT and STDERR output
* its exit status
* the time it took to execute

The last two objectives are fairly easy but I'm having trouble with the first one. I know that running a command thu a pipe ( open( H_PROGRAM, "ls -l |" ); ) only captures its STDOUT but not its STDERR, so here is how I've been trying to work around that:

1) Create a unidirectional pipe.
2) Fork.
3) In the child process, close STDOUT/STDERR and make them synonyms for the write-end of my pipe.
4) In the child process, exec() the command line.
5) In the parent process, wait for the child to finish and then read whatever got written to the pipe.

However, this does not seem to work the way I want it, and nothing gets written to the pipe. I'm thinking maybe exec() doesn't work the way I thought it would. Could it be that exec(), before it runs the actual command, nullifies my redirection of STDOUT/STDERR so they're not actually associated with the write-end of my pipe, as I thought they would be?
Any helpful replies welcome.

Kind regards,

antred (Markus)

P.S. Code in second post.
 
#***********************************************************************************************************************************
# Function : captureTimeAndOutputAndExitValueOfCommand
# Args : $strCommandLine [in] - String containing the full command to be executed.
# $rvOutput [out] - A reference to a vector that is to take the output of the command. Any previous content
# will be lost.
# $rnExitValue [out] - A reference to a scalar that is to take the command's exit value.
# $rvTimeDiff [out] - A reference to a vector that is to take the duration of the executed command in seconds and
# micro seconds. The array will be filled with two elements with the first element denoting
# the number of seconds and the second denoting the number of microseconds elapsed.
# If this is not passed (or passed as undef), the duration of the command simply
# isn't measured.
#
# Return : In case of an error this function returns 1. Otherwise it returns 0.
# Created : Tuesday, 06/29/2004 by Markus Kemp
# Last change: Monday, 07/26/2004 by Markus Kemp
#
#************************************************************************************************************************************
sub captureTimeAndOutputAndExitValueOfCommand
{
my ( $strCommandLine, $rvOutput, $rnExitValue, $rvTimeDiff ) = @_;

my $strThisFunction = "captureTimeAndOutputAndExitValueOfCommand";
my $rvStartTime;
my $rvEndTime;

if ( defined( $rvTimeDiff ) )
{
$rvStartTime = [ Time::HiRes::gettimeofday() ];
}

if ( ! defined( pipe( H_READEND, H_WRITEEND ) ) )
{
warn "ERROR: [$gStrModuleName:\:$strThisFunction] <$0> Failed to create pipe loop!\n";
return 1;
}

print "DEBUG: [$gStrModuleName:\:$strThisFunction] <$0> Past open pipe.\n";

my $nCPid = fork();

if ( ! defined( $nCPid ) )
{
warn "ERROR: [$gStrModuleName:\:$strThisFunction] <$0> Failed to fork()!\n";
return 1;
}

if ( $nCPid == 0 )
{
# Child process.
close( H_READEND );
close( STDOUT );
close( STDERR );

*STDOUT = *H_WRITEEND;
*STDERR = *H_WRITEEND;

# The most precise way of taking the command's time is to get the start time in the child process and the end time in
# the parent process. The problem this imposes is that we need to somehow communicate the start time to the parent process.
# Well, we got an open pipe to the parent right here, don't we ... so why not use it? We'll simply push one line that
# contains the start time (format: secs/microsecs\n) onto the write-end of the loop and pop it off again in the parent process.

$rvStartTime = [ Time::HiRes::gettimeofday() ];
print H_WRITEEND $$rvStartTime[ 0 ] . "/" . $$rvStartTime[ 1 ] . "\n";
exec( $strCommandLine );

# If exec() works, we'll never reach this line.
warn "ERROR: [$gStrModuleName:\:$strThisFunction] <$0> (pid=$$) exec() failed!\n";

# Exit here. Simply returning won't do, because we need to dispose of the child process.
exit( 1 );
}
else
{
# Parent process.
close( H_WRITEEND );

# Wait for the child process to finish.
if ( waitpid( $nCPid, 0 ) == -1 )
{
warn "ERROR: [$gStrModuleName:\:$strThisFunction] <$0> (pid=$$) waitpid( $nCPid ) failed. No such child process!\n";
close( H_READEND );
return 1;
}

$$rnExitValue = $?;


# Read whatever got sent through the pipe.
@$rvOutput = <H_READEND>;

print "DEBUG: [$gStrModuleName:\:$strThisFunction] <$0> Past read pipe.\n";

close( H_READEND );

print "DEBUG: [$gStrModuleName:\:$strThisFunction] <$0> Past close pipe.\n";

# If we're supposed to take the time ...
if ( defined( $rvTimeDiff ) )
{
# Take the end time.
$rvEndTime = [ Time::HiRes::gettimeofday() ];

# Extract start time from our output vector (the child process wrote it to the pipe).

# Compute the difference.
computeTimeDiff( $rvStartTime, $rvEndTime, $rvTimeDiff );

print "DEBUG: [$gStrModuleName:\:$strThisFunction] <$0> start = @$rvStartTime ... stop = @$rvEndTime ... diff = @$rvTimeDiff\n";
}

close( H_READEND );

print "DEBUG: [$gStrModuleName:\:$strThisFunction] <$0> Past close pipe.\n";

return 0;
}
}
 
Sorry, I just discovered several bugs in my code that I have corrected since. Say if you want me to repost my latest version. I'm new here and I haven't figured out how to edit posts yet.
 
When using your command, you can just redirect STDERR to STDOUT to get them together.

open( H_PROGRAM, "ls -l 2>&1 |");
 
Yeah, but that's using the shell, and I'd like to avoid doing that because I wanna be as platform-independent as possible. Thanks anyways. :)
 
platform independent (-:

I don't think Windows has a separate stream for Errors thought....
You can also try to use the IPC:Open3 module...
It works fine on UNIX...

use IPC::Open3;
open3($in,$out,$err,"ls -l|");
close $in;

@b=<$out>;
@c=<$err>;

print STDOUT "OUT:", @b, "\n\n\n";
print STDOUT "Error:",@c, "\n\n\n";
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top