我有一个使用如下命令运行的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脚本实时操作二进制命令的输出,因此将其全部缓冲到文件中是不可行的解决方案。但是,它不需要知道退出代码,直到脚本结束。
答案 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$
缺点:
一般丑陋
在
丢失非零退出代码的完全值(在上例中为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)
我看到两个选项:
但是,由于您在退出之前从命令中读取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脚本来运行命令,捕获退出状态,或将整个事物包装在另一个脚本中。