代码之家  ›  专栏  ›  技术社区  ›  zigdon

在Perl中捕获STDIN的退出状态

  •  2
  • zigdon  · 技术社区  · 16 年前

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

    脚本对二进制文件的输出执行有用的操作,然后在STDIN用完时退出<&燃气轮机;返回undef)。这一切都是好的,除非二进制存在一个非零代码。从脚本的角度来看,它认为脚本结束得很干净,所以它清理干净,然后退出, .

    有没有办法让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脚本实时操作二进制命令的输出,因此将其全部缓冲到一个文件中不是一个可行的解决方案。但是,在脚本结束之前,它不需要知道退出代码。

    5 回复  |  直到 16 年前
        1
  •  1
  •   DaveE    16 年前

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

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

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

        2
  •  8
  •   intuited    16 年前

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

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

    因此,与其重构perl脚本,不如只需要运行管道命令的脚本来检查 $管道状态 . 使用 没有 [@] 提供数组第一个元素的值。

    如果需要检查初始可执行文件和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;
    

    你必须通过一个temp变量来实现这一点,因为下一个语句,即使是一个 if 语句,将重置 ${PIPESTATUS[@]} 在下面的陈述之前,即使是 elif 声明,可以查一下。

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

    #!/bin/bash
    

    而不是 #!/bin/sh .

        3
  •  4
  •   DVK    16 年前

    为了详细说明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文件

    • 失去 非零退出代码的值(在上例中为255)

    • 读入名为 $ENV{TF} (使用 File::Slurp::read_file() ),说成 my $rc
    • chomp $rc ;
    • 取消链接 $ENV{TF}
    • exit $rc
    • TF=/tmp/rc_$$; (/bin/true; echo $?>$TF) | /your/perl/script

    这是一个有点小侵入性的变化相比,以太的脚本使用的变化 system() call-只需在脚本末尾添加4行(包括 exit

        4
  •  3
  •   Ether    16 年前

    我看到两种选择:

    • 您可以重写脚本,以便它调用命令本身,以便它可以检测其退出状态,并在未成功退出时采取不同的操作
    • 脚本,它检查退出值,然后以不同的方式调用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!";
    }
    
        5
  •  2
  •   hobbs    16 年前

    您只能获得自己子进程的退出状态。连接到STDIN的不是perl的子进程;是贝壳的。可悲的是,你想要的是不可能的。