2

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

4

5 回答 5

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.

于 2010-06-07T23:06:12.160 回答
4

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

您可以通过轻微编辑您的 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 将脚本更改为使用 call 相比,这是一个侵入性较小的更改system()- 您只需在脚本的最后添加 4 行(包括exit);但是一旦你改变了脚本,我可能会建议你全力以赴,首先做 Ether 建议的改变。

于 2010-06-07T22:22:02.140 回答
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!";
}
于 2010-06-07T22:04:13.833 回答
2

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

于 2010-06-07T21:51:54.950 回答
1

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

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

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

于 2010-06-07T22:20:27.243 回答