Shell脚本中的八进制陷阱:解决日期前导零问题

最近在执行一个用于计算月度时间统计的脚本时,遇到了一个有趣的错误。这个脚本本应计算当月已经过去的小时数,但在每月的8日和9日却会神秘地失败。错误信息十分具有误导性,让人一时难以找到问题所在:

./tmp.sh: line 20: 08: value too great for base (error token is "08")
(standard_in) 1: syntax error
(standard_in) 1: syntax error

问题代码

出错的脚本片段如下:

timecheck=$(date "+%Y-%m-%d_%H:%M:%S")

# 固定使用30天720小时计算
month_days=30
total_hours=720

# 获取当前日期、小时和分钟
current_day=$(date +%d)
current_hour=$(date +%H)
current_minute=$(date +%M)

# 计算已过去的小时数(当前日的小时 + 已过去的天数*24)
hours_passed=$(( (current_day - 1) * 24 + current_hour ))

错误发生在第20行,也就是计算hours_passed的那一行。

问题原因

这个问题看似简单的计算为何会出错?原因在于Shell中数字解析的一个隐藏陷阱:在Bash等Shell环境中,以0开头的数字默认会被解析为八进制(base-8)数字。

在八进制系统中,只允许使用0-7这八个数字。而在每月的8日和9日,date +%d命令返回的是”08”和”09”,这在八进制中是非法的,因此导致了”value too great for base”的错误。

这是一个典型的Shell编程陷阱,特别容易在处理日期和时间时遇到,因为日期和时间格式通常会包含前导零。

解决方案

针对这个问题,有两种有效的解决方案:

方案1:使用无前导零的日期格式

# 获取不带前导零的日期和时间
current_day=$(date +%-d)
current_hour=$(date +%-H)
current_minute=$(date +%-M)

通过在格式说明符前添加-符号(如%-d而不是%d),可以让date命令返回没有前导零的数字,从而避免八进制解析的问题。

方案2:强制使用十进制解析

如果你的系统不支持无前导零的日期格式(例如某些macOS版本),可以使用以下方法强制以十进制解析:

# 明确指定使用十进制解析
current_day=$((10#$(date +%d)))
current_hour=$((10#$(date +%H)))
current_minute=$((10#$(date +%M)))

通过添加10#前缀,我们明确告诉Shell使用十进制(base-10)来解析这些数字,无论它们是否有前导零。


macOS 选择特定 Profile 命令行启动 Chrome feedly 导出订阅内容