运行以下命令时,我的 Redhat 9、OpenBSD 4.9、FreeBSD 10、Macos X、LinuxMint 17.3 和 Ubuntu 14.04.4 都打印 OK:
myfunc() { echo OK; }
export -f myfunc
perl -e open\(\$fh,\"\|-\",\"@ARGV\"\)\;close\$fh\; /bin/bash\ -c\ myfunc\\\ a
我的 Ubuntu 16.04.1 给出:
bash: myfunc: command not found
但如果我删除\\\ a
它就可以了。
perl -e open\(\$fh,\"\|-\",\"@ARGV\"\)\;close\$fh\; /bin/bash\ -c\ myfunc
我感觉我的系统上有些配置错误,但我应该寻找什么?
编辑
thrig 找到了一个较短的版本,但也失败了。使用它我追踪了一个失败的和一个非失败的系统:
stdout strace -ff perl -e 'system @ARGV' /bin/bash\ -c\ myfunc\\\ a|grep bash
失败:
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
无失败:
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
看起来非常相似。删除\\\ a
两个系统上的给定:
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
所以 Perl 会删除sh -c
if 只有一个命令。也许sh -c
吃Ubuntu 16.04上的功能?
/bin/sh
在dash
两个系统上。
编辑2
env
显示该功能。这将函数显示为两个系统上环境的一部分:
perl -e 'system @ARGV' /bin/bash\ -c\ env
Ubuntu 16.04 和一个操作系统:
BASH_FUNC_myfunc%%=() { echo OK
}
其他工作制度:
BASH_FUNC_myfunc()=() { echo OK
}
但这仅显示了工作系统上的定义:
perl -e 'system @ARGV' /bin/bash\ -c\ env';true'
编辑3
解决方法:
myfunc() { echo OK; }
export -f myfunc
perl -e open\(\$fh,\"\|-\",@ARGV\)\;close\$fh\; /bin/bash -c myfunc\ a
答案1
问题是/bin/sh
Debian 或 Ubuntu ( dash
) 或 OpenBSD 等系统将从环境中清除名称包含诸如 、 之类的奇特字符的任何变量%
,其中包括那些BASH_FUNC_foo%%=() { ...
用于对 bash 导出函数进行编码的变量。
如果|-
fromopen
函数后跟单个参数而不是参数列表,并且该参数包含任何 shell 元字符(反斜杠就是其中之一),那么 perl 会将其作为参数传递给/bin/sh -c
而不是execvp(2)
直接使用。
这同样适用于perl 中的system
、exec
、open2
等函数,并在 中进行了记录perldoc -f system
。
一个更简单的例子:
$ 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
解决方法是使用用户的 运行外部命令$ENV{SHELL}
,这将使他们能够无缝地使用任何 shell 功能:
perl -e 'open F, "|-", $ENV{SHELL}, "-c", "@ARGV"' '/bin/bash -c "foo a"'
答案2
您在跨系统导出的 bash 函数的可移植性方面遇到了问题。是的,这是有道理的。
简单地提高便携性
$ touch myfunc
$ chmod a+x myfunc
并将所需的 bash 代码放入该文件中,以#! /usr/bin/env bash
shebang 行开头。 (env
服从$PATH
,你瞧。)
然后,您将依赖于程序(包括 gnu 并行)进行 fork+exec 的能力,而不是依赖于变体对行为的喜欢sh
或dash
符合程度bash
。