5

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
  • Slightly less obfuscated (second version): perl -e 'open($fh, "|-", @ARGV); close $fh;' "/bin/bash -c myfunc" These both return "OK" on OS X. – Kusalananda Aug 1 '16 at 15:19
  • 1
    Same behaviour on OpenBSD 6.0-beta as on Ubuntu 16.04.1, BTW. – Kusalananda Aug 1 '16 at 15:23
  • perl -e 'system @ARGV' would be even more concise, and also removes the confusing backwhacks. – thrig Aug 1 '16 at 15:29
  • @Kusalananda Can I ask you to test the workaround on OpenBSD? – Ole Tange Aug 1 '16 at 17:12
  • 1
    The workaround in "Edit3" returns OK on OpenBSD (I had to change the path to bash as there's no bash in /bin). – Kusalananda Aug 1 '16 at 17:17
1

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"'
  • $ENV{SHELL} is not guaranteed to preserve those env vars though. The only shell that is guaranteed to is bash as that's a bash feature. I'd say the correct way would be to do perl -e 'system @ARGV' bash -c 'foo a' here. – Stéphane Chazelas Apr 30 at 7:30
  • Which is just right, because the user may not care about bash's exported functions, but may expect to use other features of their shell transparently in the commands passed as arguments to that perl program. – mosvy Apr 30 at 8:41
0

You're having trouble with portability of an exported bash function across systems. Yes, that makes sense.

To improve portability simply

$ touch myfunc
$ chmod a+x myfunc

and put the desired bash code in that file, starting with a #! /usr/bin/env bash shebang line. (env obeys $PATH, you see.)

Then you're relying on the ability of programs (including gnu parallel) to fork+exec, rather than on how well variants like sh or dash conform to bash behavior.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.