Bash – Exported bash functions sometimes visible from Perl

bashenvironment-variablesfunction

My Redhat 9, OpenBSD 4.9, FreeBSD 10, Macos X, LinuxMint 17.3, and Ubuntu 14.04.4 all print OK when running this:

myfunc() { echo OK; }
export -f myfunc
perl -e open\(\$fh,\"\|-\",\"@ARGV\"\)\;close\$fh\; /bin/bash\ -c\ myfunc\\\ a

My Ubuntu 16.04.1 gives:

bash: myfunc: command not found

But if I remove \\\ a it works.

perl -e open\(\$fh,\"\|-\",\"@ARGV\"\)\;close\$fh\; /bin/bash\ -c\ myfunc

I have the feeling something is configured wrongly on my system, but what should I look for?

Edit

thrig found a shorter version that also fails. Using that I straced on a failing and a non-failing system:

stdout strace -ff perl -e 'system @ARGV' /bin/bash\ -c\ myfunc\\\ a|grep bash

Failing:

execve("/usr/bin/perl", ["perl", "-e", "system @ARGV", "/bin/bash -c myfunc\\ a"], [/* 71 vars */]) = 0
[pid  7728] execve("/bin/sh", ["sh", "-c", "/bin/bash -c myfunc\\ a"], [/* 71 vars */]) = 0
[pid  7729] execve("/bin/bash", ["/bin/bash", "-c", "myfunc a"], [/* 70 vars */]) = 0

Non-failing:

execve("/usr/bin/perl", ["perl", "-e", "system @ARGV", "/bin/bash -c myfunc\\ a"], [/* 20 vars */]) = 0
[pid 26497] execve("/bin/sh", ["sh", "-c", "/bin/bash -c myfunc\\ a"], [/* 20 vars */]) = 0
[pid 26498] execve("/bin/bash", ["/bin/bash", "-c", "myfunc a"], [/* 20 vars */]) = 0

That looks awfully similar. Removing the \\\ a gives on both systems:

execve("/usr/bin/perl", ["perl", "-e", "system @ARGV", "/bin/bash -c myfunc"], [/* 71 vars */]) = 0
[pid  7826] execve("/bin/bash", ["/bin/bash", "-c", "myfunc"], [/* 71 vars */]) = 0

So Perl drops the sh -c if there is only a single command. Maybe sh -c eats the function on the Ubuntu 16.04?

/bin/sh is dash on both systems.

Edit2

env shows the function. This displays the function as part of the environment on both systems:

perl -e 'system @ARGV' /bin/bash\ -c\ env

Ubuntu 16.04 and one working system:

BASH_FUNC_myfunc%%=() {  echo OK
}

Other working system:

BASH_FUNC_myfunc()=() {  echo OK
}

But this shows only the definition on the working systems:

perl -e 'system @ARGV' /bin/bash\ -c\ env';true'

Edit3

Workaround:

myfunc() { echo OK; }
export -f myfunc
perl -e open\(\$fh,\"\|-\",@ARGV\)\;close\$fh\; /bin/bash -c myfunc\ a

Best Answer

The problem is that the /bin/sh from systems like Debian or Ubuntu (dash) or OpenBSD will clear from the environment any variables whose names contain fancy chars like %, and that includes those BASH_FUNC_foo%%=() { ..., which are used to encode bash's exported functions.

If the |- from the open function is followed by a single argument instead of a list of arguments, and that argument contains any shell metacharacters (the backslash is one of those), then perl will pass it as an argument to /bin/sh -c instead of using execvp(2) directly.

The same holds true for the system, exec, open2, etc functions in perl, and is documented in perldoc -f system.

A simpler example:

$ foo(){ echo foo; }; export -f foo

$ perl -e 'system shift' '/bin/bash -c foo\ a'
/bin/bash: foo: command not found
$ perl -e 'system shift' '/bin/bash -c foo'
foo
$ perl -e 'system shift' '/bin/bash -c "foo"'
/bin/bash: foo: command not found

$ perl -e 'open F, "|-", shift' '/bin/bash -c foo\ a'
/bin/bash: foo: command not found
$ perl -e 'open F, "|-", shift' '/bin/bash -c foo'
foo

The work-around is to run external commands with the user's $ENV{SHELL}, which will let them use any shell features seamlessly:

perl -e 'open F, "|-", $ENV{SHELL}, "-c", "@ARGV"' '/bin/bash -c "foo a"'
Related Question