Perl:系统调用成功后,“或死”命令仍然会结束脚本

时间:2014-03-31 13:23:21

标签: perl system-calls die

我使用以下行进行简单的系统调用:

system ("mkdir -p Purged") or die "Failed to mkdir." ;

执行脚本确实会进行系统调用,我可以找到一个名为Purged的目录,但仍然会打印错误消息并且脚本会死掉。我的语法出了什么问题?

7 个答案:

答案 0 :(得分:17)

system返回它调用的命令的退出状态。在shell中,零退出状态意味着成功。你必须颠倒逻辑:

0 == system qw(mkdir -p Purged) or die "Failed to create the dir\n";

答案 1 :(得分:9)

  

那会有点混乱,不是吗? - Leonardo Herrera Ikegami's answer

是的,令人困惑的是system命令在Perl中反转了true和false,并创建了这样有趣的逻辑:

if ( system qw($command) ) {
    die qq(Aw... If failed);
}
else {
    say qq(Hooray! It worked!);
}

但是,system命令执行此操作的原因是可以理解的。在Unix中,退出状态为零表示程序有效,非零状态可以为您提供system调用失败的原因信息。也许你打电话的程序不存在。也许这个程序按预期工作。例如,当grep有效时,1会返回退出代码grep,但没有匹配的行。您可能希望区分grep何时返回零,一,或返回大于一的代码。 (是的,我知道在Perl程序中使用grep的系统调用很愚蠢,但这是我能想到的第一个例子。)

为了防止偶然混淆,我创建了一个变量来保存system命令的退出状态,而不是直接测试system的输出:

my $error = system qw($command);
if ($error) {
    die qq(Aw... It failed);
}
else {
    say qq(Hooray! It worked!);
}

这完全没必要了,与Perl合作的人应该知道system会颠倒Perl对 true false 的定义,但如果我没有早上喝咖啡,我可能会错过它,因为我会查看其他人的代码。做这个小步骤只会使程序看起来更合乎逻辑。

system的Perldoc为您提供的代码允许您测试系统命令的输出,以确切了解发生的情况。 (如果出现错误,或系统中断信号导致system呼叫死亡)。很高兴知道您是否需要询问system返回值以找出问题所在。

答案 2 :(得分:6)

system成功返回0,因此您需要and而不是or

另请参阅:use autodie qw( system );

答案 3 :(得分:2)

添加未提及但未提及的内容,可以悄悄搞砸。

system返回'zero-or-not'只能通过shell或execvp告诉命令本身是否成功执行; 0 意味着该命令成功完成了它的工作。

如果返回非零,您需要解压缩$?以获取更多信息;请参阅system了解如何执行此操作。例如,命令的实际退出代码是$? >> 8,执行程序的设计目的是在其出口处进行通信。

总而言之,您可能希望对

的影响做些什么
sub run_system {
    my ($cmd, @other_agrs) = @_;             # may do more via @other_args

    my ($cmdref, $sys_ret) = (ref $cmd, 0);  # LIST or scalar invocation?
    if ($cmdref eq 'ARRAY') {
        $sys_ret = system(@$cmd);
    }
    elsif (not $cmdref) {
        $sys_ret = system($cmd);
    }
    else { Carp::carp "Invocation error, got $cmdref" }

    return 1 if $sys_ret == 0;

    # Still here? The rest is error handling.
    Carp::carp "Trouble with 'system($cmd)': $?";
    print "Got exit " . ($? >> 8) . " from $cmd\n";
    return 0;  # or die (but then design changes)
}

这可以仅仅作为system == 0 or do { ... };或者在代码中这样做,但是这样有一个小库,我们可以更容易地改进错误处理,最终决定切换到模块来管理外部命令,此外,作为一个用户级设施现在有意义的是它在成功时返回true(我们没有改变system的设计)。

此外,在这种情况下,mkdir -p意味着安静,在某些情况下,它无法做任何事情。这当然是设计的,但人们应该意识到这一点。

答案 4 :(得分:0)

单行解决方案:

system("if printf '' > tmp.txt; then exit 1; else exit 0; fi ;") or die("unable to clobber tmp.txt");

答案 5 :(得分:0)

最简单 system ("mkdir -p Purged") && die "Failed to mkdir.";

答案 6 :(得分:0)

system 命令传递它运行的程序的退出代码。它不判断或解释它。 Raku, on the other hand, does interpret it 并且您必须与语言作斗争才能绕过它。而且,请记住,Perl 的 system 基本上是 C 的 system(Perl 中的许多内容正是 C 版本所做的)。

您需要根据您对该特定命令的行为的了解,将此值与您预期的值进行比较,如 system 文档中所示:

@args = ("command", "arg1", "arg2");
system(@args) == 0
    or die "system @args failed: $?"

通常,制作精良的 unix 程序使用 0 表示成功,其他所有程序都使用非零表示。这是一个很好的经验法则,但它是一个约定而不是一个要求。某些程序返回非零值以指示其他类型的成功。 system 对此一无所知。不仅如此,许多程序员不知道退出代码应该是什么,而且我经常不得不处理某人对它应该是什么的重新构思(而且我自己可能在某个时候已经这样做了)。

“信任而不验证”的趋势越来越明显,我认为这是没有系统和shell编程背景的新一代程序员。而且,即便如此,世界应该是怎样的,它实际上是怎样的。请记住,您作为程序员的目标是知道发生了什么以及为什么发生,即使世界很混乱。

这里有一些有趣的链接:

不过我最喜欢this statement from Frederick

<块引用>

更现实地说,0 表示成功或可能失败,1 表示一般失败或可能成功,如果 1 和 0 都用于成功,则 2 表示一般失败,但也可能是成功。

考虑grep。这是要搜索的文件:

$ cat original.txt
This is the original
This is from append.txt
This is from append.txt
This is from append.txt

搜索存在的东西:

$ grep original original.txt
This is the original

退出代码为 0(shell 上的 $?,但 Perl 的 $? 更复杂)。按照惯例,这就是您所期望的。您找到了一些东西并且没有错误:

$ echo $?
0

但是现在搜索不存在的东西。您没有找到任何内容,但没有错误:

$ grep foo original.txt

现在退出代码是 1,因为这就是 grep 所做的:

$ echo $?
1

这是非零但不是错误。您正确调用了 grep,它产生了正确且符合预期的输出。

system 来判断这些退出值。它传递退出代码并让您解释它们。

现在,我们变得懒惰并假设 0 表示它有效。我们这样做是因为很多人已经习惯了看到它,而且大部分时间它都有效。但是,“如果有时间的话效果最好”与“不会咬我的屁股”不同。将 system 的结果与您认为成功的结果进行比较:

my $expected = 0;
system('...') == $expected or die ...

如果任务对成功的定义是 grep 找到匹配的行,则该代码可以正常工作,因为在这种情况下退出代码恰好为零。但是,这意外地奏效了,因为两个不同的想法恰好在同一点上对齐。

但是,您接受的成功与退出代码不同。考虑一下 grep 示例。到目前为止,您知道 01 都是正常操作。这两者都意味着 grep 没有遇到错误。这与错误地调用 grep 并获得不同的退出值不同:

$ grep -X foo original.txt
grep: original.txt: Undefined error: 0

$ echo $?
2

那个 2 通常用于表示您错误地调用了程序。例如,您使用的开关存在于一个工具的一个实现中,而在另一个工具上不存在(除了 line 和 gnu 之外还有其他东西,但较少)。

你可能会得到这样的结果:

my %acceptable = map { $_ => 1 } qw(0 1);
my $rc = system(...);
die ... unless exists $acceptable{$rc};

而且,die 可以通过该退出代码(查看它是 method for choosing a value):

$ perl -e 'system( "grep -X foo original.txt" ) == 0 or die'
grep: original.txt: Undefined error: 0
Died at -e line 1.

$ echo $?
2

当然,这不像单个声明那么漂亮,但漂亮不应该胜过正确。

但这还不够好。我的 linux 版本的 grep 说在某些情况下它会以 0 退出:

<块引用>

通常情况下,如果选择了一行,退出状态为 0,如果没有选择任何行,则为 1,如果发生错误,则为 2。但是,如果使用 -q 或 --quiet 或 --silent 并选择了一行,即使发生错误,退出状态也是 0。

更进一步,sysexits.h 中有一个常规退出代码列表,表示非常特殊的情况。例如,退出代码 2 获得符号 EX_USAGE 并表示命令的形成方式存在问题。

当我编写系统代码时,我非常关注正确的退出代码,因此其他程序无需解析错误输出就可以知道发生了什么。用户在 die 中没有失败的情况下调用的 system 可能会返回 255。这对于找出问题所在的其他事情不是很有用。