如何在POSIX兼容的/bin/sh脚本中实现类似#ifdef的包含保护?
POSIX可移植的Shell脚本包含保护实现
原代码的核心问题
你提供的脚本之所以始终判定已被包含,是因为保护变量的检查逻辑错误:
if test "${guard}" ; then return ; fi
这里的${guard}是拼接出的变量名字符串(比如H_/path/to/script.sh转换后的合法变量名),test "${guard}"实际是判断这个字符串是否非空——显然它永远非空,所以无论脚本是否被包含过,都会直接执行return,导致核心代码从未运行。
正确的POSIX兼容实现思路
要实现类似C语言#pragma once的包含保护,需要三个核心步骤:
- 可靠获取当前脚本的绝对路径(确保不同调用方式下路径唯一)
- 基于绝对路径生成唯一的保护变量名
- 检查该变量是否已定义,若已定义则退出当前脚本执行,否则标记变量为已定义
完整实现代码
#!/bin/sh set -eu # POSIX兼容的绝对路径获取函数,处理符号链接 get_absolute_path() { local path="$1" # 解析符号链接 while [ -L "$path" ]; do path=$(readlink "$path") done # 确保文件存在 if [ ! -f "$path" ]; then echo "Error: Script file '$path' not found" >&2 return 1 fi # 拼接绝对路径 local script_dir=$(cd "$(dirname "$path")" && pwd) local script_name=$(basename "$path") echo "${script_dir}/${script_name}" } # 获取当前脚本的绝对路径 this_file=$(get_absolute_path "$0") # 生成唯一的保护变量名(替换非法字符为下划线) guard="H_$(printf '%s' "$this_file" | sed 's/[^a-zA-Z0-9_]/_/g')" # 包含保护逻辑:检查变量是否已定义,已定义则退出 if [ -n "${!guard:-}" ]; then return 0 fi # 标记脚本已被加载(动态设置变量) eval "${guard}=1" # -------------------------- # 脚本核心内容开始 # -------------------------- DIR=$(dirname "$this_file") export DIR # 简化的脚本根目录获取(根据实际层级调整) SCRIPT_ROOT_DIR="${SCRIPT_ROOT_DIR:-$(cd "$DIR/../../.." && pwd)}" export SCRIPT_ROOT_DIR # 引入其他脚本 . "${SCRIPT_ROOT_DIR}/_lib/_common/os_info.sh" echo "执行脚本核心内容:$this_file"
关键细节说明
- 绝对路径可靠性:
get_absolute_path函数处理符号链接,确保无论脚本是直接执行、通过source调用还是通过符号链接启动,都能得到唯一的绝对路径,避免同一个脚本因路径不同生成不同的保护变量。 - 间接变量引用:
${!guard:-}是POSIX兼容的间接引用语法,用来检查名为$guard的变量是否已定义且非空——这才是正确的包含保护判断逻辑。 - 动态变量设置:用
eval "${guard}=1"设置保护变量,因为guard是动态生成的变量名,直接赋值无法生效,eval能确保变量在当前shell上下文被正确定义。 - 避免不必要的export:包含保护只需要在当前shell上下文生效,无需导出到环境变量,减少环境变量污染。
内容的提问来源于stack exchange,提问作者Samuel Marks
相关产品推荐
相关产品推荐

