捕获 Perl 的 'system()' 的输出

Posted

技术标签:

【中文标题】捕获 Perl 的 \'system()\' 的输出【英文标题】:Capture the output of Perl's 'system()'捕获 Perl 的 'system()' 的输出 【发布时间】:2012-07-15 22:17:18 【问题描述】:

我需要在 Perl 中使用 system() 运行 shell 命令。例如,

system('ls')

系统调用将打印到STDOUT,但我想将输出捕获到一个变量中,以便将来使用我的 Perl 代码进行处理。

【问题讨论】:

不要使用系统,使用带有管道的 open()。 另见What's the difference between Perl's backticks, system, and exec? 【参考方案1】:

只需使用类似于 Bash 示例:

    $variable=`some_command some args`;

就是这样。请注意,您不会在输出中看到任何打印到 STDOUT 的内容,因为它被重定向到了一个变量。

此示例不适用于与用户交互的命令,除非您已准备好答案。为此,您可以使用一堆 shell 命令来使用类似这样的东西:

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

answers.txt 文件中,您应该准备所有答案,以便 some_command 正常工作。

我知道这不是最好的编程方式 :) 但这是实现目标的最简单方式,特别是对于 Bash 程序员而言。

当然,如果输出更大(ls 带有子目录),您不应该一次获得所有输出。像读取普通文件一样读取命令:

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;

无需额外 Bash 调用即可处理长命令输出的额外扩展解决方案:

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

如果您使用这样的程序,您将捕获所有程序输出,并且您可以逐行处理所有操作。祝你好运:)

【讨论】:

【参考方案2】:

我想运行 system() 而不是反引号,因为我想查看 rsync --progress 的输出。但是,我还想根据返回值捕获输出以防出现问题。 (这是用于备份脚本)。这是我现在使用的:

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://***.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 sth 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;

编辑:使用unlink 而不是system("rm ...")

【讨论】:

为什么要调用 system("rm $filename") ?你有内部过程 unlink $filename ,它不需要额外调用 sh 或 bash 解释器。 @Znik:如果$filename 包含shell 元字符或以- 开头,system("rm $filename") 也会导致严重问题。 unlink $filename 直接删除命名文件。【参考方案3】:

这就是反引号的用途。来自perldoc perlfaq8

为什么我无法获得带有system() 的命令的输出?

您混淆了 system() 和反引号 (``) 的用途。 system() 运行命令并返回退出状态信息(作为 16 位值: 低 7 位是进程死亡的信号(如果有的话),并且 高 8 位是实际退出值)。反引号 (``) 运行命令 并将它发送给 STDOUT 的内容返回。

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

更多详情请见perldoc perlop

【讨论】:

实际上,我认为提出这个问题是有效的:system() 在将参数作为数组传递(在“不安全”参数的情况下)时具有其他替代方案都没有的优势(AFAIK)。跨度> 【参考方案4】:

IPC::Run 是我最喜欢完成此类任务的模块。非常强大和灵活,对于小型案例也非常简单。

use IPC::Run 'run';

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

# Now $stdout contains output

【讨论】:

我一直在寻找一种简单的方法,既可以在没有 shell 插值的情况下传递命令参数,又可以从所谓的命令中捕获标准输出。很好的答案,谢谢!

以上是关于捕获 Perl 的 'system()' 的输出的主要内容,如果未能解决你的问题,请参考以下文章

Perl 的 system() 如何打印它正在运行的命令?

如何在 Perl 中捕获输出和退出代码时执行外部脚本?

如何将 Perl 的 system() 的输出重定向到文件句柄?

在 Perl 中从 STDIN 捕获退出状态

捕获 find 的输出。 -print0 到 bash 数组中

以最佳方式从 system() 命令捕获标准输出 [重复]