Bash 花括号展开:上限换成变量,为什么范围就不再展开
脚本里原来写着 part{1..3},能生成三个名称。把上限提取成变量之后,却只剩下一个带花括号的字符串。变量明明有值,末尾打印出来也像合法范围,为什么 Bash 没有再展开一次?关键在于展开阶段的先后顺序。
花括号展开先于参数展开。看到包含变量的范围时,Bash 还没有把变量替换成数字,因而无法把它当成合法序列表达式。后面虽然出现了看似正确的文本,前面的阶段也不会自动重来。最终长得像代码,并不意味着会再次按那种代码解释。
用数组数清实际生成了几个词
代码保存为 demo.sh,执行 bash demo.sh。本文在 Bash 5.2.37 运行。示例显式开启花括号展开,只在内存中构造名称,不创建或删除这些名称对应的文件,便于直接观察每一步的结果。
AI概念插图:用抽象图形表现本文讨论的关系,并非软件截图或真实运行结果。
#!/usr/bin/env bash
set -euo pipefail
set -B
upper=3
static=(part{1..3})
dynamic=(part{1..$upper})
quoted=("part{1..3}")
pattern='part{red,blue}'
from_variable=("$pattern")
prefix='report '
combined=("$prefix"{draft,final})
ordered=(item{z,a,m})
[[ ${#static[@]} == 3 && ${static[2]} == part3 ]]
[[ ${#dynamic[@]} == 1 && ${dynamic[0]} == 'part{1..3}' ]]
[[ ${quoted[0]} == 'part{1..3}' ]]
[[ ${from_variable[0]} == 'part{red,blue}' ]]
[[ ${#combined[@]} == 2 && ${combined[1]} == 'report final' ]]
[[ ${ordered[*]} == 'itemz itema itemm' ]]
printf 'static: <%s>\n' "${static[@]}"
printf 'variable bound: <%s>\n' "${dynamic[@]}"
printf 'quoted: <%s>\n' "${quoted[@]}"
printf 'variable text: <%s>\n' "${from_variable[@]}"
printf 'prefix: <%s>\n' "${combined[@]}"
printf 'order: %s\n' "${ordered[*]}"
[[ $upper =~ ^[1-9][0-9]{0,2}$ ]] || exit 2
(( upper <= 20 )) || exit 2
generated=()
for ((i=1; i<=upper; i++)); do
generated+=("part$i")
done
[[ ${generated[*]} == 'part1 part2 part3' ]]
printf 'dynamic loop: %s\n' "${generated[*]}"静态范围、变量内容和引号分别起作用
static 里有 part1、part2、part3 三个元素。dynamic 却只有 part{1..3} 一个元素。断言同时检查长度与内容,避免把终端上连续打印的文字误看成多个参数。下游命令真正接到几个参数,往往比它们连起来像什么更重要。
quoted 把整个表达式放进双引号,括号不再参与这次展开。from_variable 也只有一个元素:即使变量里保存的是合法逗号列表,参数展开得到的文字也不会重新接受花括号处理。这说明问题不只是范围上限是否为整数。
combined 则能生成 report draft 与 report final。这里可识别的逗号列表直接写在源码中,先分出两个词;每个词再展开带引号的前缀变量,前缀里的空格得到保留。因此可以让公共前缀动态变化,同时把少量静态后缀明确列出来。
ordered 的顺序是 itemz、itema、itemm。花括号按书写顺序生成文本,不负责字典排序,也不要求这些名称存在于目录里。需要按业务顺序处理任务时,这一点方便;需要检查真实文件时,则应另外做存在性检查。
动态次数交给循环,而不是重新解释字符串
最后用算术循环生成动态数量。上限先经过限定长度的十进制形式检查,再限制不超过二十,随后逐个加入数组。这样范围来自数据,但执行结构一直是固定的。本文只接受正整数,不把零、负数或任意表达式悄悄解释成另一种约定。
不要为了让变量里的花括号“生效”,随手把整串文字交给 eval。那会重新解释更多 shell 语法,而不只处理花括号,问题范围也从生成名称扩大成执行代码。对于这个需求,已验证的数值循环足以表达边界和顺序。
如果上限来自外部输入,还需要为运行成本设限。即使每个名称都合法,一次生成几百万个词也可能让启动过程占用大量内存。流式逐项处理通常比预先拼出巨大命令行更容易控制,示例的小数组只是为了便于验算。
本篇只验证 Bash 的行为,不把花括号当成通用的 sh 语法。排查类似问题时,先确认实际解释器,再记录数组长度和元素边界,最后按展开顺序找出哪一步已经过去。单看最后打印出的文本,很容易把阶段错误误认为变量没有赋值。


