DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Bash 的 {1..$n} 没有展开,for 循环只执行一次

花括号展开早于变量展开,动态终点不会生成数字序列。使用经过输入校验的算术 for 循环,明确倒序、零次输入与重跑边界。

Bash 脚本先算出一个行数,再写 for i in {1..$n},结果没有按次数循环,反而只得到类似 {1..5} 的一段文本:

n=5
for i in {1..$n}; do
    printf '%s\n' "$i"
done

直接写 {1..5} 可以展开,却不代表把终点换成变量也一样。Bash 先做花括号展开,后做变量展开;处理花括号时,$n 还不是整数终点,整个不合法的范围表达式会被保留。随后替换变量得到的 {1..5},不会重新做一次花括号展开。

这与变量是否已经 export 无关。在范围里加 ${n}、双引号或反斜杠,也不会改变展开顺序。下面限定 Bash,示例不能用 sh script.sh 运行。

运行时决定次数,用算术 for 循环

需要从 1 到运行时取得的上限时,使用 Bash 的算术循环。下面是可保存为独立脚本的示例,只打印编号;第一个参数是循环次数:

#!/usr/bin/env bash

n=${1-}
if [[ ! $n =~ ^(0|[1-9][0-9]{0,3})$ ]]; then
    printf '%s\n' 'expected an integer from 0 to 9999 without leading zeros' >&2
    exit 2
fi

for ((i=1; i<=n; i++)); do
    printf '%s\n' "$i"
done

这里先限制输入为 0 到 9999 的普通十进制文本,不接受前导零、符号、小数或表达式。范围上限是示例协议,用来避免把任意字符串交给算术解释,也避免无意启动过大的任务;实际业务应使用自己允许的上限。

n=5 时循环处理 1 到 5;n=0 时条件一开始就不满足,不进入循环。循环体使用 "$i" 作为参数,实际改成文件处理或任务调用时,也要保留适当的参数引用。

如果上限来自文件统计,先确认统计命令成功,再去掉该接口明确允许的格式填充并做数值校验。不要把“没有输出”“读取失败”或包含其他文本的结果直接当作零次任务。

倒序时显式改变起点、条件和步进

要求从上限倒数到 1 时,在同样的输入校验之后使用:

for ((i=n; i>=1; i--)); do
    printf '%s\n' "$i"
done

这不是只交换两个参数就能保证相同含义的替换。应明确是否包含两端,以及零次输入如何处理。上例上限为 3 时得到 3、2、1,上限为 0 时不执行。

若需求是生成一组文件名,再一次性传给程序,可以在受控数量下逐项加入 Bash 数组,而不是把一段 {1..$n} 文本存进普通字符串再展开:

files=()
for ((i=1; i<=n; i++)); do
    files+=("page_${i}.jpg")
done

if ((${#files[@]} > 0)); then
    printf '%s\n' "${files[@]}"
fi

这段接在前面的校验之后,示例仅打印文件名;生成名字不等于文件存在。实际调用处理程序前还要核对文件集合及其输入约定。数组也会占用内存,一次传入太多参数仍可能触及系统参数长度限制,因此不能靠数组无限扩大批次。

不需要为了范围展开重新执行字符串

eval 可以让字符串再次经过 shell 解释,但这里已经有直接的循环结构。把外部输入拼成代码再执行,会同时引入引号、命令替换和循环体解析问题;修复一个数字范围,不需要把数据变成一整段新脚本。

seq 也能生成数字,不过 for i in $(seq ...) 还涉及命令替换和输出拆分,并引入外部命令。只是在 Bash 中按次数循环时,算术 for 更容易直接表达边界。它仍使用固定宽度整数,不能作为任意精度计数器;前面的短数字校验不能随意删除。

核对循环次数,而不只看脚本是否退出成功

先用只打印的循环验证 0、1、5,分别应执行 0、1、5 次;再验证倒序的起止位置。空参数、-1、08、1+2 和超过示例范围的输入应在进入循环前被拒绝。

保存原脚本后再替换业务循环。此前错误写法可能已经把字面量 {1..5} 当作文件名或任务参数执行过一次;修复循环不会撤销那个操作,也不会判断历史任务完成到哪里。恢复或重跑之前,应按业务记录检查已处理项,避免在得到正确次数后重复处理旧数据。