如何使用 su -c 从在管道中执行的命令中获取 Perl 中的 STDERR

How to get STDERR in Perl from a command executed in pipe with su -c

我正在尝试使用以下方法捕获以不同用户身份执行的命令的输出:

my $command = qq(sudo su - <username> -c '/usr/bin/whatever');
my $pid = open $cmdOutput, "-|", $command;

如何捕获 /usr/bin/whatever 的 STDERR?

我试过了

$pid = open $cmdOutput, "-|", $command || die " something went wrong: $!";

但看起来这是在捕获 "open" 本身可能的错误。

我也试过了

my $command = qq(sudo su - <username> -c '/usr/bin/whatever' 2>/tmp/error.message);

这会将 STDERR 重定向到我稍后可以解析的文件,但我想要一些更直接的解决方案。

另外,我只想使用核心模块。

in perlfaq8. Since you are using a piped open, the relevant examples are those that go by open3 from the core IPC::Open3 模块涵盖得很透彻。

另一种选择是使用 IPC::Run 来管理您的进程,pump 函数将满足您的需要。 IPC::Open3 文档说 IPC::Run

This is a CPAN module that has better error handling and more facilities than Open3.

使用其中任何一个,您都可以根据需要单独或一起操作 STDOUTSTDERR。为了方便和完整的输出捕获,另请参阅 Capture::Tiny.

除了 2>output 重定向之外,没有更多用于管道打开的基本方法。


如果您不介意混合流或完全丢失 STDOUT,另一种选择是

my $command = 'cmd 2>&1 1>/dev/null'          # Remove 1>/dev/null to have both
my $pid = open my $cmdOutput, "-|", $command;

while (<$cmdOutput>) { print }                # STDERR only

第一个重定向将 STDERR 流与 STDOUT 合并,因此您可以同时获得它们并混合(STDOUT 受缓冲影响,因此事情很可能会出现混乱)。第二个重定向将 STDOUT 发送出去,因此您只从句柄中读取命令的 STDERR


问题是关于 运行 使用 open 的外部命令,但我想提一下规范和简单的 qx (backticks) 可以用同样的方式使用。它 returns STDOUT 因此需要像上面那样重定向才能获得 STDERR。为了完整性:

my $cmd = 'cmd_to_execute';
my $allout = qx($cmd 2>&1);              # Both STDOUT and STDERR in $out, or
my $stderr = qx($cmd 2>&1 1>/dev/null);  # Only STDERR
my $exit_status = $?;

qx 将子进程退出代码(状态)放入 $?。然后可以检查故障模式;请参阅 qx 页面中的摘要或 I/O operators in perlop.

中的非常详尽的讨论

请注意,以这种方式返回的 STDERR 来自命令 ,如果它 运行。如果命令本身不能是 运行(因为命令名称中的拼写错误,或者 fork 由于某种原因失败)那么 $? 将是 -1 并且错误将是在 $!.

所需的目的地,打开写入,可以 dup()'ed 到 FD #2

根据 zdim 的建议,我使用了 IPC::Open3 模块来解决这个问题,我有类似的东西可以帮我完成这项工作

 $instanceCommand = qq(sudo su - <username> -c '<command>');
 my ($infh,$outfh,$errfh,$pid);
 $errfh = gensym();
 $pid = open3($infh, $outfh, $errfh, $instanceCommand);
 my $sel = new IO::Select;
 $sel->add($outfh,$errfh);
 while (my @ready = $sel->can_read){
      foreach my $fh (@ready){
           my $line =<$fh>;
           if (not defined $line){
                $sel->remove($fh);
                next;
                }
           if ($fh == $outfh){
                chomp($line);
                #<----- command output processing ----->
                }
           elsif ($fh == $errfh){                                                                                      
                chomp $line;
                #<----- command error processing ----->
                }
           else {
                die "Reading from something else\n";
                }
            }
      }
waitpid($pid, 0);

也许不是完全防弹,但它对我来说工作得很好。即使在执行有趣的级联脚本时也是 < command > .