Bash 的 08 为什么不能直接算:先验证十进制,再处理前导零
补零展示和算术语法不是一回事
脚本从表单或文件拿到月份、重试次数等文本时,常遇到前面补零的数字。显示为零八不代表 Bash 会按十进制八处理:在算术表达式中,以零开头的整数常量按八进制解释,因此零一零得到八,包含八或九的写法则不符合八进制数字规则。
在数字前加上 10# 可以明确指定十进制,但这一步没有替你定义输入契约。空字符串、符号、空格和表达式片段是否允许,仍然需要自己决定。把外部文本直接塞进算术上下文,再等待计算成功或失败,无法清楚地区分格式问题与数值范围问题。
把接受条件写在转换前面
本文定义一个窄而明确的规则:接受至少一位 ASCII 数字,允许前导零,数值范围为零到六万五千五百三十五。正负号、首尾空白、十六进制前缀、小数点和其他字符都拒绝。这个范围只是便于演示的业务上限,不代表 Bash 的整数容量,也不暗示任何具体字段都应接受零。
验证流程分成三步。先用完整匹配检查原始字符;再去掉多余前导零,同时保留全零输入最后的一位;最后比较规范化数字串的长度,同长时按字符顺序与上限比较。只有通过范围检查后,才进入带十进制前缀的数值转换。
下面保存为脚本并用 bash 执行,不要交给 sh。函数内把字符比较环境固定为 C,使相同长度的十进制数字串按预期排序。范围判断中的长度来自字符串长度操作,并没有提前把原始数字串当成整数,因此超长数字也不会先触发数值溢出。
AI概念配图,非真实界面
#!/usr/bin/env bash
set -euo pipefail
parse_decimal() {
local LC_ALL=C
local raw=${1-} digits
[[ $raw =~ ^[0-9]+$ ]] || return 1
digits=$raw
while [[ ${#digits} -gt 1 && $digits == 0* ]]; do
digits=${digits#0}
done
if [[ ${#digits} -gt 5 ||
( ${#digits} -eq 5 && $digits > 65535 ) ]]; then
return 1
fi
printf '%s\n' "$((10#$digits))"
}
[[ $((010)) == 8 ]] || exit 1
[[ $((10#010)) == 10 ]] || exit 1
printf 'literal 010: octal=%s decimal=%s\n' "$((010))" "$((10#010))"
for pair in 0:0 00:0 08:8 009:9 010:10 00042:42 65535:65535 00065535:65535; do
raw=${pair%:*}
expected=${pair#*:}
actual=$(parse_decimal "$raw")
[[ $actual == "$expected" ]] || exit 1
printf 'accepted %s -> %s\n' "$raw" "$actual"
done
bad_inputs=('' '+8' '-8' ' 8' '8 ' $'8\n' $'\t8' '1.5'
'1e3' '0x10' '1+2' 'a[0]' '8x' '12' '65536'
'00065536' '99999999999999999999999999999999999999')
for raw in "${bad_inputs[@]}"; do
if actual=$(parse_decimal "$raw"); then
printf 'unexpected acceptance: %q -> %s\n' "$raw" "$actual" >&2
exit 1
fi
[[ -z $actual ]] || exit 1
done
if parse_decimal >/dev/null; then
printf 'missing input accepted\n' >&2
exit 1
fi
printf 'rejected %s invalid inputs and missing input\n' "${#bad_inputs[@]}"为什么要先比字符串范围
Bash 使用固定宽度整数进行运算,不会为普通整数运算自动检查溢出。若先转换一个极长数字,再拿结果与上限比较,结果可能已经变形。这里先消除前导零,再做长度和同长字符串比较,让超出业务上限的文本在算术发生之前返回失败。
去零循环保留一位的条件很关键。若把全零输入全部删掉,就会得到空字符串,失去合法数字零的含义。输出中的零、双零、零八、双零九分别变为零、零、八、九,证明我们接受的是十进制数值,前面的零只是输入表示的一部分。
这里只为数值转换删除前导零。如果输入代表订单编号、邮编或版本片段,前导零可能有业务意义,应保留原始字符串,甚至根本不要做算术。格式清理必须服务于字段定义,不能因为看起来像数字就自动转换。
拒绝输入也要验证得具体
失败分支不向标准输出打印结果,返回非零状态,由调用方在 if 中决定如何处理。测试同时检查状态和空输出,防止函数一边报告失败,一边留下会被下游误用的半成品。真正接入命令行工具时,可以由调用层在标准错误中说明格式不符或超出范围。
拒绝样本包括前后空格、换行、制表符、加减号、表达式以及全角数字;它们不会被偷偷修剪或改写成有效值。若产品以后需要允许空白,应先明确新的规范化步骤,并增加相应测试,而不是把正则改得越来越宽,让下游承担含义不明的输入。
这个函数解决的是有界无符号十进制解析。若要接收负数,符号与数值部分应分别验证,再重新设计上下限;若需要超出固定宽度的整数运算,应选择支持大整数的工具。不能仅仅去掉范围判断,就把示例升级成任意精度解析器。


