【问题标题】:Capturing exit status from STDIN in Perl在 Perl 中从 STDIN 捕获退出状态
【发布时间】:2010-06-07 21:44:45
【问题描述】:

我有一个使用如下命令运行的 perl 脚本:

/path/to/binary/executable | /path/to/perl/script.pl

脚本对二进制文件的输出做了一些有用的事情,然后在 STDIN 用完时退出( 返回 undef)。这一切都很好,除非二进制文件以非零代码退出。从脚本的 POV 来看,它认为脚本刚刚结束,所以它清理并退出,代码为 0

有没有办法让 perl 脚本查看退出代码是什么?理想情况下,我希望这样的工作:

# close STDIN, and if there was an error, exit with that same error.
unless (close STDIN) {
   print "error closing STDIN: $! ($?)\n";
   exit $?;
}

但不幸的是,这似乎不起作用:

$ (date; sleep 3; date; exit 1) | /path/to/perl/script.pl /tmp/test.out
Mon Jun  7 14:43:49 PDT 2010
Mon Jun  7 14:43:52 PDT 2010
$ echo $?
0

有没有办法让它按照我的意思去做?

编辑添加:

perl 脚本正在实时处理二进制命令的输出,因此将其全部缓冲到一个文件中并不是一个可行的解决方案。但是,在脚本结束之前它不需要知道退出代码。

【问题讨论】:

    标签: perl pipe exit-code


    【解决方案1】:

    bash 环境变量$PIPESTATUS 是一个数组,其中包含最后一个命令管道的每个部分的状态。例如:

    $ false | true; echo "PIPESTATUS: ${PIPESTATUS[@]};  ?: $?"
    PIPESTATUS: 1 0;  ?: 0
    

    所以听起来不像重构你的 perl 脚本,你只需要运行该管道命令的脚本来检查$PIPESTATUS。使用不带[@]$PIPESTATUS 可以得到数组第一个元素的值。

    如果您需要检查初始可执行文件和 perl 脚本的状态,您需要先将 $PIPESTATUS 分配给另一个变量:

    status=(${PIPESTATUS[@]})
    

    然后你可以单独检查它们,比如

    if (( ${status[0]} )); then echo "main reactor core breach!"; exit 1;
    elif (( ${status[1]} )); then echo "perls poisoned by toxic spill!"; exit 2;
    fi;
    

    您必须通过临时变量执行此操作,因为下一条语句,即使它是 if 语句,也会在以下语句之前重置 ${PIPESTATUS[@]},即使它是 elif 语句,也可以检查它。

    请注意,这些东西只适用于 bash 而不是原始的 bourne shell(通常是 sh,尽管许多系统将 /bin/sh 链接到 /bin/bash,因为它具有向后兼容性)。所以如果你把它放在一个shell脚本中,第一行应该是

    #!/bin/bash
    

    而不是#!/bin/sh

    【讨论】:

    • 从技术上讲,您应该使用echo "whatever" >&2; 将那些echo es 发送到stderr
    • 不幸的是,我试图避免 BASHisms(试图让它在 dash 上工作,原因我无法控制)。在 bash 上,我目前正在使用“set -o pipefail”,它使整个管道返回第一个不成功命令的 RC。
    • 嗯。我猜你需要使用一个临时文件,然后,类似于@DVK 的答案,但是在你从标准输入获得 EOF 之后,只需从 perl 中读取临时文件的内容。我曾考虑过使用 fifo,但这不起作用,因为 perl 在读取 fifo 之前不会从 stdin 看到 EOF,并且可能在输入完成之前不会检查 fifo,即 deadlock。虽然我猜你可以循环从标准输入和先进先出读取超时读取,直到你使用先进先出(然后从标准输入得到 EOF)。这有点复杂,无论如何你仍然必须制作先进先出,但它比临时文件更容易。
    【解决方案2】:

    为了详细说明 Ether 的提议,这是 shell 变通方法:

    bash-2.03$ TF=/tmp/rc_$$; (/bin/false; echo $?>$TF) | 
               perl5.8 -e 'print "OUT\n"'; test `cat $TF|tr -d "\012"` -eq 0 
    OUT
    bash-2.03$ echo $?
    1
    bash-2.03$ TF=/tmp/rc_$$; (/bin/true; echo $?>$TF) | 
               perl5.8 -e 'print "OUT\n"'; test `cat $TF|tr -d "\012"` -eq 0     
    OUT
    bash-2.03$ echo $?
    0
    bash-2.03$ 
    

    缺点:

    • 一般的丑陋

    • 留下一堆 /tmp/rc_* 文件

    • 丢失非零退出代码的 exact 值(在上面的示例中为 255)

    您可以通过将 Perl 脚本轻微编辑为:

    • 读入名为$ENV{TF}的文件的内容(使用File::Slurp::read_file()),说成my $rc
    • chomp $rc;
    • 取消链接$ENV{TF}
    • exit $rc
    • 然后你的命令行变成: TF=/tmp/rc_$$; (/bin/true; echo $?>$TF) | /your/perl/script

    与 Ether 使用 system() 调用的脚本更改相比,这是一个侵入性较小的更改 - 您只需在脚本的最后添加 4 行(包括 exit);但是一旦你改变了脚本,我可能会建议你全力以赴,首先做 Ether 建议的改变。

    【讨论】:

    • 谢谢 - 额外的缺点(在这种情况下是一个关键的缺点)是在命令运行期间输出将不可用,只有在它完成后才可用。
    • @zigdon - 抱歉,没有看到您的最后一次编辑。你是对的 - 据我所知,子 shell 调用(括号)确实会缓冲命令的输出。
    【解决方案3】:

    我看到两个选项:

    • 你可以重写脚本,让它自己调用命令,这样它就可以检测到它的退出状态,并在它没有成功退出时采取不同的行动
    • 您可以将命令的调用包装在 shell 脚本中,该脚本检查退出值,然后以不同的方式调用 Perl 脚本(与选项 1 基本相同,只是不需要更改Perl 脚本)。

    但是,由于您是在命令退出之前从命令读取 Perl 脚本中的输入,因此您显然还没有返回码。您只能在命令完成后才能访问它,因此您需要同时将其输出缓冲到其他地方,例如文件:

    use IPC::System::Simple qw(system $EXITVAL);
    use File::Temp;
    
    my $tempfile = File::Temp->new->filename;
    system("/path/to/binary/executable > $tempfile");
    if ($EXITVAL == 0)
    {
         system("/path/to/perl/script.pl < $tempfile");
    }
    else
    {
         die "oh noes!";
    }
    

    【讨论】:

    • +1 用于 Perl 修复,我还添加了一个详细说明纯 shell 解决方案的答案,以及一个 shell 解决方案与对脚本稍有“剧烈”变化的组合 :)
    【解决方案4】:

    您只能获得您自己的子进程的退出状态。连接到您的 STDIN 的东西不是 perl 的子进程;这是贝壳的。很遗憾,你想要的都是不可能的。

    【讨论】:

      【解决方案5】:

      不幸的是,bash 似乎抛弃了管道上的退出状态。运行 "sleep 3 | echo hi" 会在 sleep 完成之前启动 echo,因此它绝对没有机会捕获第一个命令的退出状态。

      您可以(理论上)通过将 bash 命令更改为命令列表来运行它——bash 会将值保存在 $? (就像 Perl 一样),但是您必须以某种方式将其传递给 Perl 脚本,这意味着您的 Perl 脚本将需要在(例如)命令行上接受前一个程序的退出状态。

      或者,您可以只重写 Perl 脚本来运行命令,并捕获退出状态,或者将整个内容包装在另一个脚本中。

      【讨论】:

      • “bash 抛弃了管道上的退出状态”:这不是真的——请看我的回答。
      猜你喜欢
      • 2012-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-16
      • 2018-06-24
      • 2011-10-24
      • 2014-11-01
      相关资源
      最近更新 更多