DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Bash 比较 08、09 报 value too great for base

Bash 算术把前导零数字解释为八进制。对确定为非负十进制的短字段,先校验内容和长度,再用 10# 转换,避免 010 静默变成 8。

变量保存 08,在 Bash 中用 [[ $value -lt 10 ]] 比较,却报 value too great for base (error token is "08")。同样的脚本处理 07 没报错,到了 08 或 09 才失败,常见于带前导零的月份、日期字段或补零编号。

原因是算术解释:前导 0 表示八进制,而八进制只有 0 到 7。[[ ... -lt ... ]] 的操作数会按算术表达式求值,$((...)) 中也有同样规则。给变量加双引号不会取消这条算术规则。

下面针对 Bash 5.x 的非负十进制字段。它不是任意 shell 通用语法,也不负责完整日期的合法性校验。

没有报错的数字也可能已经变了

010 是合法八进制,算术结果是十进制的 8,不会出现 08 那样的错误。因此,验收不能只用“脚本不再报错”判断,还要确认数值是否符合业务含义。

保存出错语句和变量的原始文本,确认该字段本来要求十进制,还是确实采用八进制。例如文件权限数字可能有意使用八进制,不能全局给所有数字都加 10#。

如果原始输入是完整日期 2026-08-09,也不要直接放进算术展开。那会涉及减法运算符,并不是解析日期;需要先按输入格式拆出字段,再分别处理。

校验短数字字段,再显式使用十进制

下面是可以保存为独立脚本的示例。它接受一到三位 ASCII 数字,保留前导零,但拒绝符号、空白、小数点和表达式。长度上限只是示例输入协议:

#!/usr/bin/env bash

value=${1-}
if [[ ! $value =~ ^[0123456789]{1,3}$ ]]; then
    printf '%s\n' 'expected one to three decimal digits' >&2
    exit 2
fi

number=$((10#$value))
if (( number < 10 )); then
    printf 'less than ten: %s\n' "$number"
else
    printf 'at least ten: %s\n' "$number"
fi

10# 指定后面数字使用十进制。这里必须先展开 $value,才能形成例如 10#08;写成 10#value 不会按这个变量名读取数字。

输入校验必须在转换之前。空串、-08、+08 或任意文本都不属于上述协议,不能把 10# 当成万能数字解析器。若业务允许负数,应先设计符号与绝对值的校验和范围规则,不能直接照搬这段无符号转换。

Bash 使用固定宽度整数算术,不提供任意精度或自动溢出保护。不要为了接收所有输入而去掉长度上限;较大编号、金额或超长数字应采用满足业务精度与范围要求的处理方式。

只删除一个零不够

${value#0} 只去掉一个匹配的前导零。008 会变成 08,仍然触发同一个错误。直接删掉所有零又要处理 0、00 最终变成空串的情况,不适合当作未经校验的快捷替代。

将 [[ ... ]] 换成 [ ... ] 在某些具体比较中可能改变结果,但不会修复脚本里其他算术展开的基数解释。应在数据进入算术计算的位置明确进制,并保留原来的比较语义;不要把 < 的字符串排序当作数值大小比较。

如果字段只是标识符,008 与 8 需要保持不同,就根本不应转换成数字。使用字符串相等比较或按原文本存储,避免“修复”后丢掉有效的前导零。

同时检查数值和拒绝输入

在隔离测试中,示例脚本传入 08、09、010、000 时,预期数字分别是 8、9、10、0。空参数、-08、1+2、12x 和四位输入应被拒绝,并返回状态 2。

实际业务还需要在转换后校验范围,例如月份只能是 1 到 12;十进制转换成功不表示它是一个有效日期。修改前保存脚本和原输入样本,回退时恢复对应的转换与比较逻辑。已经用错误数值生成的结果需要另行核对,不能靠脚本修复自动恢复。