Suppress / catch / trap error message on failed fork open()in Perl

i7uaboj4  于 2023-05-01  发布在  Perl
关注(0)|答案(3)|浏览(186)

考虑:

#!/usr/bin/perl

use strict;
use warnings;

my $command = "nosuchcommand"

my $rc = open( my $fh, "-|", "$command" );

if ( ! defined( $rc ) )
{
    print "My own error message\n";
}
else
{
    system( "ps -p $rc" );
}

预期输出:

My own error message

观察到的输出:

Can't exec "nosuchcommand": No such file or directory at ./mcre line 8.
My own error message

我如何捕捉/捕获/抑制Perl在失败的open-pipe上生成的错误消息(有利于我自己的错误处理)?

进一步的观察:

  • 如果我open( my $fh, "-|", "$command 2>&1" );,我得到预期的输出。但是如果$command成功,我会将$commandSTDERR混合到STDOUT中,我不希望这样。
  • 如果我open( my $fh, "-|", "$command 2>/dev/null" );,我得到这个:
PID TTY          TIME CMD
  15143 pts/0    00:00:00 sh <defunct>

所以2>/dev/nullspecific redirection(而不是2>&1)改变了open的返回值。这有点令人惊讶,但不是我问题的核心。

IPC::运行

到目前为止,已经有两个答案提出了这一点。我知道在一般情况下这可能是“正确”的答案,但我真的不想这样做(因为IPC::Run在一个非常重要的目标平台上不是一个选项,这个平台没有它,也没有提供简单的安装方法)。

1zmg4dgp

1zmg4dgp1#

该消息不是来自Perl,而是来自您启动以执行shell命令的shell。[1]这是因为

open( my $fh, "-|", $shell_command )

相当于

open( my $fh, "-|", "/bin/sh", "-c", $shell_command )

正如您所建议的,您可以使用2>/dev/null重定向STDERR,但这也会使程序中的错误消失(当使用有效程序时)。
你可以这样做:

use IPC::Run qw( run );

my @cmd = ( $prog, @args );

eval { run \@cmd };
if ( $@ ) {
   die( "Problem creating child: $@" );
}
elsif ( $? & 0x7F ) {
   die( "Child died from signal ".( $? & 0x7F )."\n" );
}
elsif ( $? >> 8 ) {
   die( "Child exited with error ".( $? >> 8 )."\n" );
}

如果你想要更多的控制权,你需要自己实现。IPC::Open3的open3源代码将是一个很好的开始。
1.除了Perl优化了shell的使用,使其不再是简单的shell命令,所以消息实际上确实来自Perl。但这在这里不是一个有用的区分。

tjrkku2a

tjrkku2a2#

至于shell中的重定向,我们可以将STDERR单独重定向到文件

command 2> stderr.out

(as以及任何文件描述符) 这会使STDOUT退出,如您所希望的那样在管道中打开,或者重定向它(cmd > stdout.out 2> stderr.out)。
该机制可以在Perl代码中使用,因为命令中的shell元字符(如重定向符号)使用shell。† Perl使用sh,但它通常链接到另一个shell,因此请检查您的发行版中使用的是哪个shell而不是sh。(或者通过bash显式运行。)
举个例子

use warnings;
use strict;
use feature 'say';

my $errfile = 'err.out';
my $pid = open my $fh, '-|', "ls *txt nofile 2> $errfile" 
    // die "fork failed: $!";        
print "stdout: $_" while <$fh>;
close $fh or say "Exit: ", $? >> 8;
    
if (-z $errfile) {
    unlink $errfile or warn "Can't unlink $errfile: $!"
}   
else { # there was STDERR stream printed, see it
    print "error-file:\n", 
        my $err = do { local (@ARGV, $/) = $errfile; <> };
    # investigate $err more closely?
}

所以这确实捕获了STDERR流,而且是单独捕获的,但是还有一个文件要处理。‡
open的返回值是子进程的pid(这里是shell,不管命令是否可以由它启动),除非fork本身失败。§检查close可以了解事情的进展情况,并提示可能出现的问题--命令无法启动,它报告非零状态等
如果文件句柄来自一个管道打开,如果涉及的其他系统调用之一失败,或者如果它的程序以非零状态退出,close将返回false。...
在这里,命令(ls)成功启动,并打印*txt文件(如果有的话)和退出代码(在我的系统上是2)。在STDERR流中出现的关于不存在的nofile的消息将进入err.out文件。
如果命令本身无法启动,则只打印退出代码(127),而STDERR消息再次出现在文件中。
我们不能直接判断重定向到文件的消息是关于程序(不存在或无法启动)还是来自程序本身,因为它打印到STDERR。但是我们确实将该错误保存在一个文件中,以便可以更仔细地调查它,而不像将其发送到/dev/null
最后,我也会推荐IPC::Run。基本的使用是微不足道的,而如果您需要的话,它几乎是一个迷你shell。
†外部命令可以通过启动shell并向其传递带有命令和参数的字符串来启动,或者通过使用execvp函数之一直接启动命令。如果命令没有shell“元字符”(重定向、globbing、引号等),则通常通过避免shell来优化该过程。这可以通过语法确保,参见systemexec。否则,启动shell,以便能够执行指定的操作。
这显然是有区别的:在shell中,命令也经历了它的语法规则,我们可以得到shell的消息,在pipe-open中,一个进程 is forked用于shell(fork通常不会失败),然后另一个用于命令,等等。没有shell,我们可以得到perl关于(失败的)命令的消息。在任何一种情况下,我们也可以从程序本身获得消息。如果不进入细节或使用库(并进入细节),发生的事情通常不容易解开。
‡我用内置工具在一个语句中读取文件(并将其保存到变量中),因为提到了非核心模块的问题。如果安装软件实际上不是一个问题,那么有很好的库可以解决这个问题。比如说

use Path::Tiny;  # path, slurp

my $err = path($errfile)->slurp;

§ open docs中的一个例子暗示了一个无法启动的命令会导致open返回false。这只适用于那些不涉及shell元字符的命令,因此直接启动的命令--因此,如果它们启动失败,则确实没有进程。
另请参阅perlipc中的此主题,示例中有更准确的错误消息。

jgovgodb

jgovgodb3#

Can't exec "nosuchcommand": No such file or directory at ./mcre line 8.

这一行实际上是一个“警告”,由use warnings;启用。perldiag
无法执行“%s”:%s
(W exec)system()、exec()或管道打开调用无法执行指定的程序,原因如下。典型原因包括:文件的权限错误,在$ENV{PATH}中找不到文件,有问题的可执行文件是为另一个体系结构编译的,或者#!脚本中的一行指向由于类似原因而无法运行的解释器。(或者您的系统不支持#!at all.)
现在,use warnings;是 * 强烈 * 推荐的,所以完全删除它并不是一个“解决方案”。但是可以通过no warnings <category>在给定范围内禁用警告。因此,为了抑制该特定消息:

#!/usr/bin/perl

use strict;
use warnings;

my $command = "nosuchcommand";
my @args = ();
my $rc;
my $fh;

{
    no warnings qw( exec );
    my $rc = open( my $fh, "-|", "$command", @args );
}

if ( ! defined( $rc ) )
{
    print "My own error message: $!\n";
}

输出:

My own error message: No such file or directory

底层shell生成的错误消息(“No such file or directory”)部分位于$!特殊变量中。请注意,您应该使用open的四参数版本(如果需要,可以使用空LIST,如上图所示)。Perl有不同的执行外部命令的方法,一种是通过/bin/sh -c(并且不受exec警告的控制),另一种是通过execvp(并且警告的控制)。open的四参数版本确保Perl将使用后者。

相关问题