Bash 导出函数的横向可见性

Léa*_*ris 7 bash shell function environment-variables

我进行了一项实验,看看在进程链之间使用不同的 shell 时,Bash 的导出函数是否仍然可见。

令我惊讶的是,导出的函数有时仍然可见。

我正在寻找关于如何以及为什么通过不同的 shell 在环境中维护 Bash 的特定导出函数的解释,而它不是 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
Run Code Online (Sandbox Code Playgroud)

我的系统上的结果表明,除了和 之外fn,所有测试的 shell 都保持了导出的横向可见性:ashdash

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

Run Code Online (Sandbox Code Playgroud)

Gor*_*son 6

环境实际上只能包含字符串,因此当您export -f在 bash 中使用时,它实际上只是创建一个常规环境变量(具有奇怪的名称)并将其设置为函数定义的文本。例如:

$ fn(){ printf 'Hello World\n';}
$ export -f fn
$ printenv BASH_FUNC_fn%%
() {  printf 'Hello World\n'
}
Run Code Online (Sandbox Code Playgroud)

在 bash 初始化期间,它会扫描环境中是否存在具有此类名称的变量,并将找到的任何变量转换为函数。

由于这只是一个常规环境变量(与其名称不同),因此它将通过各种其他程序(包括 shell)持续存在,除非程序执行某些操作将它们从环境中删除。看来 dash 和 ash 都可以做到这一点(尽管我没有方便测试的 ash):

$ dash -c 'printenv BASH_FUNC_fn%% || echo "not found"'
not found
Run Code Online (Sandbox Code Playgroud)

您可以通过其他名称奇怪的环境变量看到相同的效果:

$ 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
Run Code Online (Sandbox Code Playgroud)

  • 它只是内存中的一个数组;你可以在那里放置任何你想要的字节。[`putenv`(3) 库函数](https://www.man7.org/linux/man-pages/man3/putenv.3.html) 可能会进行一些健全性检查,但您可以绕过它并直接写入内存缓冲区(如果您知道它是如何布局的)。 (2认同)