1

My goal is to capture the exit code of external command issued from perl. The trick is that the external command is actually composed of two commands where first command is piped to second command. For my purposes I need exit code of first command. Bash uses for this purpose several possible ways e.g.

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

After reading about this problematic I've found that perl system command is able to get exit code of process so I've tried following:

$ 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

As you can see I am getting weird return codes (256, 512, 2560) from system command. I think it is related to my another question. So far the only possible way that I am able to access return code of first command is using qw or backtickss ``. This seems as a a little overkill for me since I need one echo to capture the PIPESTATUS[0] value.

$ 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

I am also wondering why this approach is even working because here is mentioned that perl does not invoke a shell to execute external command. So where does PIPESTATUS (which is a pure bash variable) comes from? Also I would expect that following command will work since bash is explicitly issued, but it returns nothing:

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

$

The third miss understanding is based on this answer that I can directly access PIPESTATUS variable via assigning it to variable and then access as a regular perl variable e.g.

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

However the following command does not work for me.

$ 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 in reply to ThisSuitIsBlackNot, tjd and CapEnt

Return codes and bit shifting explained:

$ 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

Some systems have /bin/sh pointed to 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

some not ...

# 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

a little hack that will use bash regardless of what /bin/sh is pointing to:

# 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

Here you can see that bash is invoked in both cases (in 2nd case bash is children of sh)

# 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

As ThisSuitIsBlackNot pointed out, probably the best solution is to use IPC::Run but I am wondering if this is OK in case when I need pure perl (without modules)

Community
  • 1
  • 1
Wakan Tanka
  • 7,542
  • 16
  • 69
  • 122
  • 1
    Re. *"perl does not invoke a shell to execute external command"* Not true. See the documentation for [`system`](http://perldoc.perl.org/functions/system.html): "If there is only one scalar argument, the argument is checked for shell metacharacters, and if there are any, the entire argument is passed to the system's command shell for parsing (this is `/bin/sh -c` on Unix platforms, but varies on other platforms). If there are no shell metacharacters in the argument, it is split into words and passed directly to `execvp`, which is more efficient." – ThisSuitIsBlackNot Feb 09 '15 at 15:57
  • *(continued)* Note that this is exactly what the accepted answer says on the question you linked. – ThisSuitIsBlackNot Feb 09 '15 at 15:59
  • 1
    The answer you reference states: `Perl does not *necessarily* invoke a shell.` Emphasis is mine. – tjd Feb 09 '15 at 16:05
  • The second answer you reference (re: assigning PIPESTATUS to variable) is clearly Bash code, not Perl. – tjd Feb 09 '15 at 16:10
  • Thank you guys. You helped a lot. @ThisSuitIsBlackNot So the another question is what are those shell metacharacters? – Wakan Tanka Feb 09 '15 at 18:43
  • 1
    @WakanTanka `&;\`'\"|*?~<>^()[]{}$\n\r`(from the [W3C's FAQ on CGI security](http://www.w3.org/Security/Faq/wwwsf4.html#CGI-Q7)) – ThisSuitIsBlackNot Feb 09 '15 at 19:05
  • A couple of points regarding your latest edit: I don't know why you want to avoid using modules, but you're really limiting yourself if you do that. This may not be your particular issue, but just in case it is, [you don't need admin rights to install modules](http://stackoverflow.com/questions/3735836/how-can-i-install-perl-modules-without-root-privileges). Also, you make your command more readable using `q` and single quotes: `my $cmd = q{ bash -c 'exit 255 | true 2>&1 >/dev/null; echo ${PIPESTATUS[0]}' };` – ThisSuitIsBlackNot Feb 10 '15 at 19:10
  • Also note that [ikegami's solution](http://stackoverflow.com/a/28414533/176646) to the linked duplicate uses [`IPC::Open3`](http://perldoc.perl.org/IPC/Open3.html), which is a core module, so you could use it without having to install anything. – ThisSuitIsBlackNot Feb 10 '15 at 19:13

1 Answers1

0

The return value of system must be shifted right by 8, as perldoc says. Like:

perl -e 'my $code = system("false | true"); print $? >> 8 . "\n"'
CapEnt
  • 287
  • 1
  • 6
  • 1
    This returns the exit code of the last command in the pipeline. The OP wants the exit code of the first command. – ThisSuitIsBlackNot Feb 09 '15 at 16:19
  • @ThisSuitIsBlackNot for me this return 0 which actually is exit code of first command. – Wakan Tanka Feb 09 '15 at 18:42
  • @WakanTanka You're still getting the exit code of the last command; it just may coincidentally be the same as the exit code of the first command in some cases. You can't get the exit code of the first command in the pipeline this way. Note that the above snippet prints `0`, while `false` returns `1`. See the linked duplicate question for ways around this, especially [ikegami's answer](http://stackoverflow.com/a/28414533/176646), which was originally written in answer to *this* question. – ThisSuitIsBlackNot Feb 09 '15 at 19:02
  • @ThisSuitIsBlackNot ah I see you are absolutely right. This answer from the very first link (How can I check the status of the first program in pipeline in Perl's system()? ) seems also correct. `perl -e 'system("set -o pipefail;false | true");print $?>>8,"\n"'` – Wakan Tanka Feb 09 '15 at 19:07
  • 1
    @WakanTanka That will only work on systems where `/bin/sh` points to `bash`, which is not the case everywhere. Relying on the shell is unportable enough as it is; expecting `bash` to be the default shell on every system restricts you even more. Much better to use [`IPC::Run`](https://metacpan.org/pod/IPC::Run) or similar as recommended in the other answers. – ThisSuitIsBlackNot Feb 09 '15 at 19:10
  • 2
    @WakanTanka You've changed your question into a completely different question. Please don't do that, since it invalidates existing answers and is confusing for future visitors. As to whether your "hack" is good enough: that's entirely up to you. I avoid `system` and backticks (especially if a shell is involved) if at all possible, since they are unportable, error prone, difficult to debug, and less efficient than pure Perl. But I don't know what constraints you have, and maybe that hack is good enough for what you're doing. Entirely up to you. – ThisSuitIsBlackNot Feb 10 '15 at 19:05