从Perl中的STDIN捕获退出状态

时间:2010-06-07 21:44:45

标签: perl pipe exit-code

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

5 个答案:

答案 0 :(得分:8)

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

答案 1 :(得分:4)

详细说明以太的提议,这是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)

您可以通过稍微编辑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);但是一旦你改变了剧本,我可能会建议全力以赴,先做以太建议的改变。

答案 2 :(得分: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!";
}

答案 3 :(得分:2)

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

答案 4 :(得分:1)

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

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

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