Bash脚本中无eval扩展含特殊字符的Glob变量为数组
解决Bash中带空格/引号的Glob模式扩展问题
你的核心问题在于直接用files=($glob)会忽略引号,把空格当成分隔符,导致带空格的文件名被拆分成多个项,同时无法正确解析带引号的模式。而且由于模式是从文件读取的,没法直接写进数组字面量里。这里有个不用eval的安全方案,分两步处理:先正确拆分带引号的模式项,再逐个扩展Glob。
改造后的核心代码
直接替换你原循环里的files=($glob)部分,完整修改后的代码如下:
template_include_pat='^#include (.*)$' # 提前开启nullglob,确保无匹配的模式不会留下空字符串 shopt -s nullglob while IFS='' read -r line || [[ -n "$line" ]]; do if printf '%s' "$line" | grep -qE "$template_include_pat"; then glob=$(printf '%s' "$line" | sed -nrE "s/$template_include_pat/\\1/p") cwd=$(pwd -P) cd "$targetdir" # 关键步骤1:拆分带引号的模式为单独的项(保留带空格的文件名) # 用xargs解析引号,把被包裹的内容当成单个项输出 IFS=$'\n' read -r -d '' -a patterns < <(printf '%s\n' "$glob" | xargs -d '\n' printf '%s\n') # 关键步骤2:逐个扩展每个模式,收集到files数组 files=() for pat in "${patterns[@]}"; do files+=($pat) done # 后续处理逻辑不变 for f in "${files[@]}"; do printf "\n\n%s\n" "# FILE $f" >> "$tempfile" cat "$f" >> "$tempfile" || die "Cannot read '$f'." done cd "$cwd" else echo "$line" >> "$tempfile" fi done < "$template" # 用完后关闭nullglob,避免影响其他逻辑 shopt -u nullglob
为什么这个方案可行?
- 模式拆分:
xargs -d '\n' printf '%s\n'会自动解析输入字符串中的引号(单/双引号)和反斜杠,把"dir/file with spaces"这类带空格的内容当成单个模式项输出,而不是拆分成三个部分。 - 安全扩展:对每个拆分后的模式单独进行Glob扩展,结合
nullglob选项,没有匹配的模式会被直接忽略,不会留下空元素。 - 避免eval风险:
eval虽然能解析引号,但如果模板文件里的模式被注入恶意代码(比如$(rm -rf /)),会直接执行,这个方案完全规避了这个安全问题。
兼容性说明
如果是在BSD系统(比如macOS)上,xargs的-d选项是GNU扩展,不可用,可以用下面的替代方式拆分模式:
# BSD xargs兼容的拆分方式 IFS=$'\n' read -r -d '' -a patterns < <(printf '%s\0' "$glob" | xargs -0 -n1 printf '%s\n')
原代码的问题根源
files=($glob)是无引号的变量展开,Bash会按照IFS(默认是空格、制表符、换行)拆分字符串,完全忽略引号的存在。比如"dir/file with spaces"会被拆成"dir/file"、with、spaces"三个无效的文件名,自然会出错。
内容的提问来源于stack exchange,提问作者steiny
相关产品推荐
相关产品推荐

