【问题标题】:Bash like PIPESTATUS equivalent for perl [duplicate]Bash 类似于 perl 的 PIPESTATUS [重复]
【发布时间】:2015-04-09 10:04:20
【问题描述】:

我的目标是捕获从 perl 发出的外部命令的退出代码。诀窍是外部命令实际上由两个命令组成,其中第一个命令通过管道传输到第二个命令。出于我的目的,我需要第一个命令的退出代码。 Bash 为此目的使用 several possible 方式,例如

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

在阅读了这个问题后,我发现 perl system 命令能够获取进程的退出代码,所以我尝试了以下操作:

$ exit 10 | true
$ echo ${PIPESTATUS[0]}
10
$ true | false
$ echo $?
1
$ perl -e 'my $code = system("true | false"); print $code . "\n"'
256
$ perl -e 'my $code = system("bash -c \"true | false\""); print $code . "\n"'
256
$ perl -e 'my $code = system("bash -c \"exit 2\""); print $code . "\n"'
512
$ perl -e 'my $code = system("bash 2>&1 >/dev/null"); print $code . "\n"'
$ exit 10
exit
2560

如您所见,我从system 命令收到了奇怪的返回码(256、512、2560)。我认为这与我的另一个question 有关。到目前为止,我能够访问第一个命令的返回码的唯一可能方法是使用 qw 或反引号 ``。这对我来说似乎有点矫枉过正,因为我需要一个 echo 来捕获 PIPESTATUS[0] 值。

$ perl -e 'my $res = qx/true | false 2>&1 >\/dev\/null; echo \${PIPESTATUS[0]}/; print $res'
0
$ perl -e 'my $res = qx/false | true 2>&1 >\/dev\/null; echo \${PIPESTATUS[0]}/; print $res'
1
$ perl -e 'my $res = qx"false | true 2>&1 >/dev/null; echo \${PIPESTATUS[0]}"; print $res'
1
$ perl -e 'my $res = qx"true | false 2>&1 >/dev/null; echo \${PIPESTATUS[0]}"; print $res'
0
$ perl -e 'my $res = qx(false | true 2>&1 >/dev/null; echo \${PIPESTATUS[0]}); print $res'
1
$ perl -e 'my $res = `false | true 2>&1 >/dev/null; echo \${PIPESTATUS[0]}`; print $res'
1

我也想知道为什么这种方法甚至可以工作,因为here 提到 perl 不会调用 shell 来执行外部命令。那么PIPESTATUS(这是一个纯 bash 变量)从何而来?另外我希望以下命令可以工作,因为 bash 是明确发出的,但它什么也不返回:

$ perl -e 'my $res = `bash -c "false | true 2>&1 >/dev/null; echo \${PIPESTATUS[0]}"`; print $res'

$

第三次错过的理解是基于this的答案,我可以通过将PIPESTATUS变量分配给变量来直接访问它,然后作为常规perl变量访问,例如

status=(${PIPESTATUS[@]})
print $status

但是下面的命令对我不起作用。

$ perl -e 'my $res = `false | true 2>&1 >/dev/null;`; status=(${PIPESTATUS[@]})'
syntax error at -e line 1, near "@]}"
Missing right curly or square bracket at -e line 1, at end of line
Execution of -e aborted due to compilation errors.

@EDIT 回复 ThisSuitIsBlackNot、tjd 和 CapEnt

返回码和位移说明:

$ perl -e 'my $code = system("true | false"); print $code >> 8; print "\n"'
1
$ perl -e 'my $code = system("bash -c \"true | false\""); print $code >> 8; print  "\n"'
1
$ perl -e 'my $code = system("bash -c \"exit 2\""); print $code >> 8; print "\n"'
2

某些系统将/bin/sh 指向bash ...

# Linux arch 3.18.6-1-ARCH #1 SMP PREEMPT Sat Feb 7 08:59:29 CET 2015 i686 GNU/Linux
$ ls -la `which sh`
lrwxrwxrwx 1 root root 4 Dec 30 23:11 /usr/bin/sh -> bash

$ perl -e 'my $cmd = "exit 255 | true 2>&1 >/dev/null; echo \${PIPESTATUS[0]}"; system($cmd);'
255

有些不是...

# SunOS andromeda 5.9 Generic_122300-61 sun4u sparc SUNW,Sun-Fire-V490
$ ls -la `which sh`
-r-xr-xr-x   4 root     root       95504 Jul 16  2009 /bin/sh

$ perl -e 'my $cmd = "exit 255 | true 2>&1 >/dev/null; echo \${PIPESTATUS[0]}"; system($cmd);'
sh: bad substitution

无论/bin/sh 指向什么,都会使用bash 的小技巧:

# Linux arch 3.18.6-1-ARCH #1 SMP PREEMPT Sat Feb 7 08:59:29 CET 2015 i686 GNU/Linux
# SunOS andromeda 5.9 Generic_122300-61 sun4u sparc SUNW,Sun-Fire-V490
$ perl -e 'my $cmd = "bash -c \"exit 255 | true 2>&1 >/dev/null; echo \\\${PIPESTATUS[0]}\""; system($cmd);'
255

在这里您可以看到在两种情况下都调用了 bash(在第二种情况下,bashsh 的子级)

# Linux arch 3.18.6-1-ARCH #1 SMP PREEMPT Sat Feb 7 08:59:29 CET 2015 i686 GNU/Linux
$ perl -e 'my $code = system("bash -c \"ps -elf | grep $$\"");'
0 S wakatana     1576   282  0  80   0 -  1606 wait   16:46 pts/1    00:00:00 perl -e my $code = system("bash -c \"ps -elf | grep $$\"");
0 S wakatana     1577  1576  0  80   0 -  1333 wait   16:46 pts/1    00:00:00 bash -c ps -elf | grep 1576
0 S wakatana     1579  1577  0  80   0 -  1167 pipe_w 16:46 pts/1    00:00:00 grep 1576

# SunOS andromeda 5.9 Generic_122300-61 sun4u sparc SUNW,Sun-Fire-V490
$ perl -e 'my $code = system("bash -c \"ps -elf | grep $$\"");'
 8 S wakatana 24641 24640  0  60 20        ?    314        ? 16:01:45 pts/196  0:00 bash -c ps -elf | grep 24639
 8 S wakatana 24640 24639  0  50 20        ?    139        ? 16:01:45 pts/196  0:00 sh -c bash -c "ps -elf | grep 24639
 8 S wakatana 24643 24641  0  50 20        ?    128        ? 16:01:45 pts/196  0:00 grep 24639
 8 S wakatana 24639 24633  0  50 20        ?    383        ? 16:01:45 pts/196  0:00 perl -e my $code = system("bash -c

正如 ThisSuitIsBlackNot 指出的那样,最好的解决方案可能是使用IPC::Run,但我想知道这是否可以,以防我需要纯 perl(不带模块)

【问题讨论】:

  • 回复。 “perl 不会调用 shell 来执行外部命令” 不正确。请参阅system 的文档:“如果只有一个标量参数,则检查该参数的 shell 元字符,如果有,则将整个参数传递给系统的命令 shell 进行解析(这是 /bin/sh -c on Unix平台,但在其他平台上有所不同)。如果参数中没有shell元字符,则将其拆分为单词并直接传递给execvp,这样效率更高。”
  • (续) 请注意,这正是您链接的问题上接受的答案所说的内容。
  • 您引用的答案指出:Perl does not *necessarily* invoke a shell. 重点是我的。
  • 您引用的第二个答案(re:将 PIPESTATUS 分配给变量)显然是 Bash 代码,而不是 Perl。
  • @WakanTanka &;`'\"|*?~<>^()[]{}$\n\r(来自W3C's FAQ on CGI security

标签: bash perl shell exit-code


【解决方案1】:

system的返回值必须右移8位,如perldoc says。喜欢:

perl -e 'my $code = system("false | true"); print $? >> 8 . "\n"'

【讨论】:

  • 这将返回管道中最后一个命令的退出代码。 OP 想要第一个命令的退出代码。
  • @ThisSuitIsBlackNot for me this return 0 实际上是第一个命令的退出代码。
  • @WakanTanka 你仍然得到最后一个命令的退出代码;在某些情况下,它可能恰好与第一个命令的退出代码相同。您无法通过这种方式获取管道中第一个命令的退出代码。请注意,上面的 sn-p 打印 0,而 false 返回 1。有关解决此问题的方法,请参阅链接的重复问题,尤其是 ikegami's answer,它最初是为回答 this 问题而编写的。
  • @WakanTanka 这仅适用于/bin/sh 指向bash 的系统,并非所有地方都如此。依赖外壳本身就足够不便携了;期望 bash 成为每个系统上的默认 shell 会更加限制您。最好使用IPC::Run 或其他答案中推荐的类似内容。
  • @WakanTanka 你已经把你的问题变成了一个完全不同的问题。请不要这样做,因为它会使现有答案无效,并且会让未来的访问者感到困惑。至于你的“hack”是否足够好:这完全取决于你。如果可能的话,我会避免使用system 和反引号(尤其是在涉及 shell 的情况下),因为它们不可移植、容易出错、难以调试,并且比纯 Perl 效率低。但我不知道你有什么限制,也许这个 hack 对你正在做的事情来说已经足够好了。完全由您决定。
猜你喜欢
  • 1970-01-01
  • 2013-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-18
  • 1970-01-01
  • 2018-04-04
  • 1970-01-01
相关资源
最近更新 更多