require_once 用于 bash,但如何用于旧的 bash 和 POSIX-shell?

require_once for bash, but how to for older bash and POSIX-shell?

受到 Php require_once 的启发,我想出了如何在此处使用关联数组为现代 Bash 实现它:

a.sh

#!/usr/bin/env bash

declare -gAi __REQUIRES

require_once() {
  for p; do
    r=$(readlink --canonicalize-existing "$p")
    # shellcheck disable=SC1090 # Dynamic source
    [[ -r "$r" && 1 -ne "${__REQUIRES[$r]}" ]] && __REQUIRES[$r]=1 && . "$r"
  done
}

require_once ./b.sh
require_once ./b.sh

hello

b.sh

hello() {
  printf 'I am the hello function in b.sh.\n'
}

这按预期工作:

  1. 获取source的真实路径。
  2. 检查它是否可读,通过查找全局关联数组键检查它是否已经加载。
  3. 如果是,将其注册到全局关联数组中并获取它。

现在我还在想如何:

  1. 以更 portable/standard 的方式获取源的真实路径而不依赖于 Linux Gnu 工具?
  2. 让它与旧的 Bash 一起工作,比如 3.2
  3. 让它在没有数组的情况下使用 POSIX-shell 语法。

Get the real path of the source in a more portable/standard way not depending on Linux Gnu Tools?

readlink -f 在 Busybox 上可用。检查路径是否存在后readlink

无论如何,https://www.google.com/search?q=readlink+POSIX -> https://medium.com/mkdir-awesome/posix-alternatives-for-readlink-21a4bfe0455c , https://github.com/ko1nksm/readlinkf .

Have it work with older Bash like 3.2

Have it work with POSIX-shell grammar without array.

POSIX 无论如何都不喜欢文件名中的换行符,所以只需将文件存储为行:

__REQUIRES=""
if ! printf "%s" "$__REQUIRES" | grep -Fq "$r"; then
   __REQUIRES="$__REQUIRES""$r
"
   . "$r"
fi

或者使用案例,这样你就不会 fork:

__REQUIRES="
"
case "$__REQUIRES" in
*"
$r
"*) ;;
*) 
    __REQUIRES="$__REQUIRES""$r
"
    . "$r"
    ;;
esac

如果你想处理文件名中的换行符,通过 xxdod 转换文件名(两者都在 Busybox 上可用,od 是 POSIX)并存储十六进制文件名表示为变量中的行。

POSIX-shell 语法方法使用临时目录中的符号链接作为已经需要的源标记:

a.sh

#!/usr/bin/env sh

unset __REQUIRES;
trap 'rm -fr -- "$__REQUIRES"' EXIT INT
__REQUIRES=$(mktemp -d)

require_once() {
  for p; do
    # Path is readable or continue
    [ -r "$p" ] || continue

    # Get real path
    r=$(readlink -f -- "$p")

    # New real path in __REQUIRES temp dir
    nr="$__REQUIRES$r"

    # If path __REQUIRES temp dir exists, file is already sourced
    [ -r "$nr" ] && continue

    # Make equivalent path for file in __REQUIRES temp dir
    mkdir -p -- "${nr%/*}"

    # Create symbolic link to __REQUIRES temp dir
    ln -sf -- "$r" "$nr"

    # shellcheck disable=SC1090 # Dynamic source
    . "$r"
  done
}

counter=0

require_once ./b.sh
require_once ./b.sh

hello
printf 'counter=%d\n' "$counter"

b.sh

counter=$((counter + 1))

hello() {
  printf 'I am the hello function in b.sh.\n'
}

bash 3.2 没有实现关联数组,所以我们可以为每个脚本创建单独的“必需”变量名。

不要忘记 readlink -e-f 失败 如果符号链接被破坏,那么让我们将其工作到函数中。

MacOS readlink 根本没有“规范化”选项,所以我不知道如何将其移植。

也许这样:在我的 Mac 上工作,GNU readlink 安装了自制软件。

require_once() {
    local script canonical varname val
    for script; do
        if canonical=$(readlink -f -- "$script") && [[ -r "$canonical" ]]; then
            varname=$(printf '__REQUIRED__%s' "${canonical//[^[:alnum:]]/__}")
            val=$(eval "echo $$varname")
            if [[ -z $val ]]; then
                eval "$varname=1"
                . "$canonical"
            fi
        fi
    done
}