Bash导出函数的横向可见性

7
我进行了一个实验,以查看在进程链中是否仍然可以看到Bash的导出函数,即使在不同的shell之间。
令我惊讶的是,有时候导出的函数仍然可见。
我正在寻找一个解释,即为什么Bash的特定导出函数可以在不同的shell中保持在环境中,而它并不是一个POSIX标准的环境变量。
不知何故,我有一个线索,即导出函数声明被存储为一个(标准的?)环境变量中的字符串。但是它如何在不兼容的shell中作为环境变量不可见,并且如何在环境中保持?
以下是我的实验代码:
#!/usr/bin/env bash

LANG=C

# Declare function and export it
fn(){ printf 'Hello World\n';}
export -f fn

# Standard usage, call exported function within a child bash
bash -c fn

# Available shells for tests
available_shells=()
for sh in ash bash dash ksh tcsh zsh
do
  if shell_path=$(command -v "$sh" 2> /dev/null)
  then
    available_shells+=("$shell_path")
    real_shell_path=$(realpath "$shell_path")
    shell_version=$(
      read -r pkg < <(apt-file search "${real_shell_path#/usr}" | awk -F: '{print $1}')
      dpkg -s "$pkg" | awk '/Version:/{print $2}'
    )
    printf 'Found: %s\tversion: %s\n' "$shell_path" "$shell_version"
  fi
done

# Test transversal visibility of exported fn through different shells
for sh in "${available_shells[@]}"; do
  printf "Testing %s -c 'bash -c fn':\\n" "$sh"
  "$sh" -c 'bash -c fn'
  printf \\n
done

我的系统结果显示,除了ashdash之外,所有测试的外壳都保持了导出的fn横向可见性。
Hello World
Found: /usr/bin/ash     version: 0.5.12-2ubuntu1
Found: /usr/bin/bash   version: 5.2.15-2ubuntu1
Found: /usr/bin/dash   version: 0.5.12-2ubuntu1
Found: /usr/bin/ksh     version: 1.0.4-3
Found: /usr/bin/tcsh    version: 6.24.07-1
Found: /usr/bin/zsh     version: 5.9-4
Testing /usr/bin/ash -c 'bash -c fn':
bash: line 1: fn: command not found

Testing /usr/bin/bash -c 'bash -c fn':
Hello World

Testing /usr/bin/dash -c 'bash -c fn':
bash: line 1: fn: command not found

Testing /usr/bin/ksh -c 'bash -c fn':
Hello World

Testing /usr/bin/tcsh -c 'bash -c fn':
Hello World

Testing /usr/bin/zsh -c 'bash -c fn':
Hello World


有趣!你是否期望环境变量是否提供了一些线索? - undefined
1
@UlrichEckhardt 我没有测试环境变量,因为它们是POSIX标准,预期会在环境中继承,除非像Dash这样的shell限制了环境。令人惊讶的是,在非Bash shell之间,一个外来的Bash特定导出函数在环境中得以保留。 - undefined
1个回答

6
环境实际上只能包含字符串,所以当你在bash中使用export -f时,它实际上只是创建一个普通的环境变量(带有奇怪的名称),并将其设置为函数定义的文本。例如:
$ fn(){ printf 'Hello World\n';}
$ export -f fn
$ printenv BASH_FUNC_fn%%
() {  printf 'Hello World\n'
}

在bash初始化过程中,它会扫描环境中的变量,将找到的变量转换为函数。这些变量的命名方式与普通环境变量相同,因此除了名称之外,它们会在各种其他程序(包括shell)中持久存在,除非这些程序采取措施将它们从环境中移除。似乎dash和ash都会这样做(尽管我没有ash可用来测试)。
$ dash -c 'printenv BASH_FUNC_fn%% || echo "not found"'
not found

你可以在其他奇怪命名的环境变量中看到相同的效果。
$ env test%="still here" bash -c 'printenv test% || echo "not found"'
still here
$ env test%="still here" ksh -c 'printenv test% || echo "not found"'
still here
$ env test%="still here" zsh -c 'printenv test% || echo "not found"'
still here
$ env test%="still here" dash -c 'printenv test% || echo "not found"'
not found

谢谢你!你准确地指出了它的运作方式,并且解释得非常清楚。 - undefined
这就提出了另一个问题。这些环境变量名是非法的shell变量名。环境怎么允许包含非法的变量名呢? - undefined
2
这只是内存中的一个数组;你可以在那里放任何字节。putenv(3)库函数可能会进行一些合理性检查,但如果你知道它的布局,你可以绕过它,直接写入内存缓冲区。 - undefined
1
由于shell语法的限制,shell变量名受到严格限制,但环境变量是操作系统的特性,完全独立于您可能使用的任何shell,并且不受相同的限制。查尔斯·达菲(Charles Duffy)在这里提供了更多信息,特别是关于POSIX规范和/或允许的内容。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接