简体   繁体   中英

Capture the output of Perl's 'system()'

I need to run a shell command with system() in Perl. For example,

system('ls')

The system call will print to STDOUT , but I want to capture the output into a variable so that I can do future processing with my Perl code.

That's what backticks are for. From perldoc perlfaq8 :

Why can't I get the output of a command with system() ?

You're confusing the purpose of system() and backticks (``). system() runs a command and returns exit status information (as a 16 bit value: the low 7 bits are the signal the process died from, if any, and the high 8 bits are the actual exit value). Backticks (``) run a command and return what it sent to STDOUT.

 my $exit_status = system("mail-users"); my $output_string = `ls`;

See perldoc perlop for more details.

IPC::Run is my favourite module for this kind of task. Very powerful and flexible, and also trivially simple for small cases.

use IPC::Run 'run';

run [ "command", "arguments", "here" ], ">", \my $stdout;

# Now $stdout contains output

Simply use similar to the Bash example:

    $variable=`some_command some args`;

That's all. Notice, you will not see any printings to STDOUT on the output because this is redirected to a variable.

This example is unusable for a command that interact with the user, except when you have prepared answers. For that, you can use something like this using a stack of shell commands:

    $variable=`cat answers.txt|some_command some args`;

Inside the answers.txt file you should prepare all answers for some_command to work properly.

I know this isn't the best way for programming:) But this is the simplest way how to achieve the goal, specially for Bash programmers.

Of course, if the output is bigger ( ls with subdirectory), you shouldn't get all output at once. Read the command by the same way as you read a regular file:

open CMD,'-|','your_command some args' or die $@;
my $line;
while (defined($line=<CMD>)) {
    print $line; # Or push @table,$line or do whatever what you want processing line by line
}
close CMD;

An additional extended solution for processing a long command output without extra Bash calling:

my @CommandCall=qw(find / -type d); # Some example single command
my $commandSTDOUT; # File handler
my $pid=open($commandSTDOUT),'-|'); # There will be an implicit fork!
if ($pid) {
    #parent side
    my $singleLine;
    while(defined($singleline=<$commandSTDOUT>)) {
        chomp $line; # Typically we don't need EOL
        do_some_processing_with($line);
    };
    close $commandSTDOUT; # In this place $? will be set for capture
    $exitcode=$? >> 8;
    do_something_with_exit_code($exitcode);
} else {
    # Child side, there you really calls a command
    open STDERR, '>>&', 'STDOUT'; # Redirect stderr to stdout if needed. It works only for child - remember about fork
    exec(@CommandCall); # At this point the child code is overloaded by an external command with parameters
    die "Cannot call @CommandCall"; # Error procedure if the call will fail
}

If you use a procedure like that, you will capture all procedure output, and you can do everything processing line by line. Good luck:)

I wanted to run system() instead of backticks because I wanted to see the output of rsync --progress . However, I also wanted to capture the output in case something goes wrong depending on the return value. (This is for a backup script). This is what I am using now:

use File::Temp qw(tempfile);
use Term::ANSIColor qw(colored colorstrip);

sub mysystem {
    my $cmd = shift; # "rsync -avz --progress -h $fullfile $copyfile";
    my ($fh, $filename) = tempfile();
    # http://stackoverflow.com/a/6872163/2923406
    # I want to have rsync progress output on the terminal AND capture it in case of error.
    # Need to use pipefail because 'tee' would be the last cmd otherwise and hence $? would be wrong.
    my @cmd = ("bash", "-c", "set -o pipefail && $cmd 2>&1 | tee $filename");
    my $ret = system(@cmd);
    my $outerr = join('', <$fh>);
    if ($ret != 0) {
        logit(colored("ERROR: Could not execute command: $cmd", "red"));
        logit(colored("ERROR: stdout+stderr = $outerr", "red"));
        logit(colored("ERROR: \$? = $?, \$! = $!", "red"));
    }
    close $fh;
    unlink($filename);
    return $ret;
}

# And logit() is something like:
sub logit {
    my $s = shift;
    my ($logsec, $logmin, $loghour, $logmday, $logmon, $logyear, $logwday, $logyday, $logisdst) = localtime(time);
    $logyear += 1900;
    my $logtimestamp = sprintf("%4d-%02d-%02d %02d:%02d:%02d", $logyear, $logmon+1, $logmday, $loghour, $logmin, $logsec);
    my $msg = "$logtimestamp $s\n";
    print $msg;
    open LOG, ">>$LOGFILE";
    print LOG colorstrip($msg);
    close LOG;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM