bash:导出的函数不可见,但变量是

bash:导出的函数不可见,但变量是

多年来,我收集了 shell 和脚本引用的 bash 函数库。为了减少导入样板文件,我正在探索如何在脚本中合理地包含该库的选项。

我的解决方案有两个部分 - 首先导入配置(环境变量),然后获取函数库。

〜/ bash_envs:(配置)

export SOME_VAR=VALUE
export SHELL_LIB=/path/to/library.sh

# convenience funtion, so scripts who source env_vars file (ie this file) can
# simply call it, instead of including the same block in each file themselves.
function _load_common() {
    # import common library/functions:
    source $SHELL_LIB
}
export -f _load_common

# marker var used to detect whether env vars (ie this file) have been loaded:
export __ENV_VARS_LOADED_MARKER_VAR=loaded

现在从脚本运行以下代码:

if [[ "$__ENV_VARS_LOADED_MARKER_VAR" != loaded ]]; then  # in our case $__ENV_VARS_LOADED_MARKER_VAR=loaded, ie this block is not executed
    USER_ENVS=/home/laur/bash_envs

    if [[ -r "$USER_ENVS" ]]; then
        source "$USER_ENVS"
    else
        echo -e "\n    ERROR: env vars file [$USER_ENVS] not found! Abort."
        exit 1
    fi
fi

_load_common

这会产生_load_common: command not found异常。这是为什么?注释__ENV_VARS_LOADED_MARKER_VAR=loaded可以很好地导出和可见,这就是为什么没有理由来源$USER_ENVS;但未_load_common()找到函数,尽管它是从与 __ENV_VARS_LOADED_MARKER_VAR 相同的位置导出的。

答案1

问题

观察:

$ bash -c 'foobar () { :; }; export -f foobar; dash -c env' |grep foobar
$ bash -c 'foobar () { :; }; export -f foobar; bash -c env' |grep foobar
BASH_FUNC_foobar%%=() {  :
$ bash -c 'foobar () { :; }; export -f foobar; ksh93 -c env' |grep foobar
BASH_FUNC_foobar%%=() {  :
$ bash -c 'foobar () { :; }; export -f foobar; mksh -c env' |grep foobar
$ bash -c 'foobar () { :; }; export -f foobar; zsh -c env' |grep foobar
BASH_FUNC_foobar%%=() {  :
$ bash -c 'foobar () { :; }; export -f foobar; busybox sh -c env' |grep foobar
BASH_FUNC_foobar%%=() {  :

环境变量是 Unix 操作系统的一个功能。对它们的支持一直延伸到内核:当一个程序调用另一个程序时(使用execve系统调用),调用的参数之一是新程序的环境。

sh 风格的 shell(dash、bash、ksh 等)中的内置命令export会导致 shell 变量用作环境变量,该变量会传输到 shell 调用的进程。相反,当调用 shell 时,所有环境变量都成为该 shell 实例中的 shell 变量。

导出函数是 bash 的一项功能。 Bash 通过创建一个环境变量来“导出”函数,该环境变量的名称源自函数的名称,其值是函数的主体(加上标头和标尾)。您可以在上面看到环境变量的名称是如何构造的:BASH_FUNC_然后是函数的名称 then %%

此名称不是 shell 变量的有效名称。回想一下,shell 在启动时将环境变量导入为 shell 变量。当环境变量的名称不是有效的 shell 变量时,不同的 shell 有不同的行为。有些将变量传递给它们的子进程(上图:bash、ksh93、zsh、BusyBox),而另一些则只将导出的 shell 变量传递给它们的子进程(上图:dash、mksh),这有效地删除了名称不是 a 的环境变量。有效的 shell 变量(非空 ASCII 字母、数字和 序列_)。

最初,bash 使用与函数同名的环境变量,这基本上可以避免这个问题。 (仅在大多数情况下:函数名称可以包含 shell 变量名称中不允许的字符,例如-。)但这还有其他缺点,例如不允许导出 shell 变量和具有相同名称的函数(以最后导出的一个为准)会覆盖环境中的另一个)。至关重要的是,bash 发生了变化发现原来的实现导致了一个重大的安全漏洞。 (也可以看看env x='() { :;}; 是什么意思?命令' bash 执行的操作以及为什么它不安全?,shellshock (CVE-2014-6271/7169) 错误是什么时候引入的?完全修复该错误的补丁是什么?,Shellshock Bash 漏洞是如何发现的?)此更改的一个缺点是导出的函数不再通过某些程序,包括 dash 和 mksh。

您的系统可能有破折号/bin/sh。这是一个非常受欢迎的选择。被大量使用,因此很有可能在调用路径中的某个地方/bin/sh调用了从运行到尝试使用该函数的 bash 实例的原始 bash 实例。通过,因为它有一个有效的变量名,但没有通过。shexport -f _load_common__ENV_VARS_LOADED_MARKER_VARBASH_FUNC__load_common%%

解决方案

不要使用导出的函数。首先它们没什么用处,对你来说它们完全没用。导出函数的唯一优点是调用 bash 时不需要 bash 实例从某处读取函数的定义,例如在脚本中定义函数并将其传递给从find -execxargs调用的 bash 实例parallel。但就您而言,您已经有了读取函数定义的代码。所以无条件地阅读函数定义就可以了。删除export -f _load_common,删除__ENV_VARS_LOADED_MARKER_VAR,然后调用source "$USER_ENVS"

相关内容