尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Shell脚本遍历日期范围:原理、常见坑与高效实现
简介面向Shell初学者的日期范围遍历解析文档系统讲解如何利用脚本在两个指定日期之间生成递减日期序列并为日志分析、定时任务调度、按日期批量抓取数据等自动化场景提供可直接借鉴的写法。压缩包内仅有1个PDF文件大小27KB内容紧凑完整收录Shell代码、执行流程说明及关键设计点。已有2026人浏览学习。文档以实际可运行的脚本为主线从命令行接收开始与结束日期依次介绍date命令时间戳转换、%F与%s格式化选项、嵌套循环边界控制、倒序输出判定逻辑以及将当前日期传递给Python脚本的联动方式。读者不仅可以看到date1/date2参数如何参与运算还能理解如何使用seq设置外层安全边界、内层循环如何通过时间戳比较决定是否继续输出。掌握该实例后只需调整循环次数或日期格式就能将脚本用于多日数据补爬、周期性报表生成、备份清理等任务适合正在学习Shell编程并希望提升自动化脚本能力的开发与运维人员。1. Shell脚本遍历一个日期范围为什么这么个小循环总有人翻车在数据补跑、日志清理和报表重算这类日常任务里Shell脚本遍历一个日期范围几乎是每个运维或后端都会写到的前置动作把 2025-01-01 到 2025-01-10 之间的每一天取出来再去拼路径、做打包、调接口。听起来就是一层窗户纸可实际写起来却常常在跨月、跨年、08 被当成八进制这类细节上翻车。原因在于 shell 里的“日期”不是普通数字而是需要交给 date 命令解释的字符串很多人把它当整数手算结果闰年二月和时区就成了玄学。对正在做 shell 脚本入门或刚把 for 循环用熟的开发者来说这篇笔记能把这条链路的原理、参数和暗坑一次讲完让你写出的日期遍历脚本拿过来就能直接跑。2. 先选对工具date、for、seq 怎么组合才不踩坑2.1 最常用的三件套for date -d 与它的适用边界Shell 脚本遍历日期范围最常用的组合是 for 循环负责生成偏移量date 命令负责把偏移量换算成真实日期。核心是 date -d 这个参数它接受一个“相对日期表达式”比如2025-01-01 1 day然后输出对应的日期。对 shell 脚本入门阶段的开发者来说记住这一个参数就能解决 90% 的需求。# 最小示例用 for date -d 打印 3 天日期 for i in 0 1 2; do date -d 2025-01-01 $i day %Y-%m-%d done这段代码的逻辑很简单i 是距离起始日的偏移天数每循环一次date 就把基准日期往后推一天。注意前后有空格写成$i day在某些 GNU date 版本里也能解析但推荐保留空格这是最稳妥的写法。day这个单位写成单数即可GNU date 不区分单复数。它的适用边界是任务运行在 Linux 环境日期范围在几万天以内。超过这个量级循环里反复 fork 出 date 子进程的开销会变得明显后面避坑清单会专门讲。如果你的生产环境是 macOS 或有其他 Unix 变体-d不一定存在这一点也要提前确认。2.2 为什么不推荐自己算日期闰年、大小月和时区的“黑匣子”有相当一部分人第一次写这个需求时会试图绕开 date自己维护一个“月份天数表”1 月 31 天、2 月 28 天、闰年 29 天……再把年月日拆开做进位。这种做法的维护成本极高而且每到一个边界就要补一个 if。更隐蔽的问题是时区如果你用 epoch 秒数date %s加 86400 来推下一天在实行夏令时的地区某一天可能只有 23 小时或 25 小时直接导致日期错位。我的建议是把日历计算完全交给系统 dateshell 只负责循环、拼接路径和调业务命令。date 背后是系统的 C 库闰年规则、大小月、时区转换这些“黑匣子”已经被验证过无数次没必要自己再实现一遍。你只需要约定好输入格式剩下的进位让 date 去处理。这也是这个方案最省心的部分——你写的是业务逻辑不是日历算法。2.3 先搭一个最小可跑版本一个 3 行脚本打印出你想要的日期序列要验证环境是否支持这套写法可以先把最小版本跑通。下面这个脚本打印从2025-01-01到2025-01-05的所有日期#!/bin/bash # 打印 2025-01-01 到 2025-01-05共 5 天 start2025-01-01 for i in $(seq 0 4); do echo $(date -d $start $i day %Y%m%d) doneseq 0 4生成 0 到 4 的整数序列一共 5 个数对应 5 天。$(...)是命令替换把 date 的输出拼到 echo 里。输出结果是20250101到20250105的 8 位字符串这种格式排序方便也适合直接做目录名。两个参数值得注意第一个是基准日期写法GNU date 对2025-01-01和20250101都能解析但20250101更像数字容易在后续算术里被误用所以示例里加了连字符第二个是%Y%m%d%Y是四位年份%m是两位月份%d是两位日期小写%y只有两位年份写脚本时别用错。最小版本跑通后再往里面加业务逻辑就顺理成章了。3. 从“能跑”到“能干活”把日期范围遍历做成带格式、带任务的实用脚本3.1 业务里最常见的形态按天遍历日志目录把某几天的数据重新打包日期遍历本身不是目的它总是为了服务一个具体任务。最常见的是日志处理系统每天生成一个以日期命名的目录比如/var/log/myapp/20250101/某天日志需要重新归档或补传给下游就需要遍历一个日期范围把其中存在的目录逐天打包。#!/bin/bash # 固定起止日期按天重打包日志目录 start2025-01-01 end2025-01-05 log_root/var/log/myapp backup_root/backup/myapp mkdir -p $backup_root # 这里用 1000 作为偏移量上限配合 break 保证循环一定结束 for i in $(seq 0 1000); do d$(date -d $start $i day %Y%m%d) if [ $d \ $end ]; then break fi src_dir$log_root/$d if [ -d $src_dir ]; then tar czf $backup_root/$d.tar.gz -C $log_root $d echo 已打包 $d else echo 跳过 $d目录不存在 2 fi done逻辑分三层第一层date 算出当前偏移量对应的日期第二层用[ $d \ $end ]做字符串比较在 test 里需要转义成\因为 date 输出是定长 8 位字符串字典序和日期序一致所以可以直接比较第三层判断目录存在才打包不存在就跳过并输出到 stderr避免脚本在中断任务里因为缺目录而报错退出。这里用了seq 0 1000而不是精确计算总天数是故意的。先用一个“足够大”的上限循环里发现超过 end 就 break脚本逻辑更简单也不容易因为日期计算差一天而出错。代价是如果 start 和 end 差很远for 会先生成一个较大列表但几千个数对内存几乎没有影响。3.2 三个必调参数日期格式、起点终点写法、循环步长写这类脚本时有三个参数几乎每次都要根据场景调整值得单独说清楚。日期格式方面%Y%m%d适合做目录名和文件名排序、去重都方便%F等价于%Y-%m-%d可读性好适合打印日志%Y-%m-%d_%H%M%S则适合带时间戳的归档。同一个脚本内建议日期格式只用一种避免在判断和拼接路径时来回转换。起点和终点的写法上GNU date 能同时接受20250101和2025-01-01两种格式。但我倾向在脚本内部统一用YYYY-MM-DD作为基准因为20250101如果在双引号里被误当成数字参与运算前导零问题会更隐蔽而YYYY-MM-DD只能被 date 解析语义一目了然。循环步长方面按天遍历是 $i day的形态。如果任务需要按小时把偏移单位改成hour即可但循环层数要相应扩大如果任务是按月遍历就不建议用“加 N 天”来实现了直接循环月份数字更可靠参数推荐写法适用场景常见误区日期格式%Y%m%d目录名、文件名用%y导致两位年份基准日期2025-01-01date -d 解析20250101被当数字做算术偏移单位day/hour按天、按小时遍历按月也用 day 偏移跨月换算复杂3.3 把起止日期改成用户输入read 与命令行参数两种常见做法固定起止日期的脚本只能自己改代码要能给别人用还得支持传参。常见做法是用位置参数$1、$2再配合正则校验防止乱传导致 date 解析出奇怪结果。#!/bin/bash # 用法./traverse_dates.sh 20250101 20250110 start${1} end${2} # 校验必须是 8 位数字 if ! [[ $start ~ ^[0-9]{8}$ ]] || ! [[ $end ~ ^[0-9]{8}$ ]]; then echo 日期参数需要 8 位数字例如 20250101 2 exit 1 fi # 统一转成带连字符的格式便于 date 解析 start_fmt${start:0:4}-${start:4:2}-${start:6:2} end_fmt${end:0:4}-${end:4:2}-${end:6:2} echo 遍历范围$start_fmt 到 $end_fmt参数校验用了 Bash 的正则匹配[[ ~ ]]^[0-9]{8}$保证输入是 8 位纯数字。随后的字符串切片${start:0:4}把20250101拆成2025-01-01这样后续 date 命令不用再关心用户写的是哪种格式。也有团队习惯用交互式read让用户手动输入好处是提示明确坏处是没法在定时任务里复用。我更推荐位置参数因为 crontab 或 Jenkins 任务可以通过变量注入起止日期脚本保持无交互状态。如果你需要处理更复杂的选项比如同时传入“跳过已存在目录”这类开关可以升级到getopts但对大多数场景$1、$2已经够用了。4. 跨月、跨年与闰年日期范围遍历的四个隐藏边界4.1 跨月为什么容易翻车日期字符串比较的基准与 08 的陷阱日期遍历最常出问题的地方是跨月。比如从20250128遍历到20250203如果你在脚本里写的是“把日部分加 1到 32 就进位到月”那 2 月只有 28 天这个规则就得自己维护。更常见的一个坑是date %d在 1 到 9 号会输出08、09这样的两位字符串如果把它们直接参与 bash 算术就会报错# 错误示范把 08 当数字做加法 day$(date %d) # 假设输出 08 next$((day 1)) # bash: value too great for base (error token is 08)原因是 bash 把以 0 开头的数字当成八进制而八进制里没有 8 和 9。解决办法是给变量加10#前缀显式声明十进制next$((10#$day 1))。这个错误非常隐蔽因为 1 到 7 号跑得好好的一到 8 号、9 号就崩很多人第一反应是怀疑 date 命令出了问题。4.2 跨年与二月date -d 处理闰年的表现跨年其实是最不需要担心的场景因为 date -d 会自动进位。20241231 1 day会直接输出20250101不需要任何额外判断。但很多人不放心总想自己写一段“如果 month 等于 12 且 day 等于 31则 year 加 1”的逻辑——这恰恰是多余的而且容易写错。闰年也同理。date -d 20240228 1 day %Y%m%d在 2024 年闰年会输出20240229在 2025 年平年则输出20250301。下面这条命令可以一次验证你关心的所有边界# 验证跨年和闰年进位 for base in 20240228 20241231 20250131; do echo $base - $(date -d $base 1 day %Y%m%d) done我在写任务调度脚本前习惯先跑一遍这种验证命令。肉眼确认输出正确比在完整脚本里调试要快得多。如果输出和你预想不一致优先检查系统时区而不是怀疑 date 的进位逻辑——它在这方面的可靠性远高于手写判断。4.3 结束日期判断的两种写法while 与 for 各自的坑遍历日期范围的循环结构主要有两种写法。第一种是 for 偏移量前面已经用过先算出总天数再逐天偏移第二种是 while date每次让日期自增一天直到越过结束日期。#!/bin/bash # while 写法每次让日期自增直到超过 end current2025-01-01 end2025-01-05 # 先确认 start 不晚于 end否则循环不会终止 while [ $current \ $end ] || [ $current $end ]; do echo $current current$(date -d $current 1 day %Y-%m-%d) done这里用[ $current \ $end ]加上等于判断组成“小于等于”的语义。注意\在 test 里是转义后的小于号不加反斜杠会被 shell 当成输入重定向。这种写法结构直观缺点是每循环一次就要 fork 一个 date 进程如果范围只有几天当然无所谓但要是遍历三年 1000 多天性能问题就来了。for 偏移量的写法正好相反计算总天数只需要一次 date 调用循环体内不再依赖 date 计算当前日期速度更快。它的坑在于“总天数”本身要算对如果依赖(end_epoch - start_epoch) / 86400取整在跨夏令时切换的地区可能差一天。更稳妥的方式是直接用日期偏移循环用 break 跳出这也是我推荐在业务脚本里使用的方式。5. Shell脚本遍历日期范围的常见避坑清单现象、原因、解决5.1 报错 value too great for base08 和 09 被当成八进制现象脚本在每月 8 号、9 号突然报错错误信息里带着(error token is 08)其他日子一切正常。原因date %d输出的08、09以 0 开头bash 算术默认把它们当八进制解析但八进制里没有 8 和 9。解决所有从 date 输出或用户输入里拿到的数字参与算术前统一加10#前缀例如$((10#$day 1))。如果是变量本身也可以用$((10#${var} 1))包裹。这个坑最容易发生在你不经意地把date %m或date %d的输出存进变量然后又拿去加一的时候。5.2 在 macOS 上跑date -d直接报 illegal time format现象同一套脚本在 Linux 服务器上运行正常放到 macOS 上就提示illegal time format。原因date -d是 GNU date 的扩展macOS 自带的 BSD date 不支持它用的是-j -f参数组合。解决统一在 Linux 容器或服务器上执行如果必须跨平台可以在脚本开头做一次系统判断macOS 分支用date -j -f %Y-%m-%d $start $i day这类写法调用但更常见的做法是别让脚本承担跨平台责任直接固定运行环境。因为就算勉强兼容了-dBSD date 和 GNU date 在相对日期表达式的语义上也有细微差别测试成本不小。5.3 遍历 1000 天耗时几十秒循环里频繁 fork date 的性能问题现象脚本遍历 3 年日期跑完花了 50 多秒大部分时间花在 date 命令上。原因每次循环的$(date ...)都会创建一个新进程1000 次循环就是 1000 次进程创建和销毁。解决范围小几十天内不必优化范围大时改用偏移量循环把 date 调用次数从“天数”降到“1 次”。如果还需要更极致可以用seq 0 $((total_days - 1))预先算好偏移量列表配合printf或数组一次性处理。注意seq本身也会展开成一个大列表但对几万天以内没什么压力真正的瓶颈在进程 fork不在内存。5.4 结束日期判断写成!日期跳过一天就死循环现象脚本在某些范围内正常运行换一个开始日期后陷入死循环CPU 跑满也不退出。原因如果用[ $current ! $end ]作为循环条件只有当 current 刚好等于 end 时才会退出一旦偏移过程里跳过了 end比如时区问题导致某天重复或缺失条件永远为真循环就永远停不下来。解决改用“大于 end 就退出”的判断比如 for break 模式或者[ $current \ $end ] || [ $current $end ]的 while 写法。另外在脚本入口校验 start 不晚于 end能避免“倒着遍历”导致的另一个死循环分支。5.5 输出日期“看起来对、排序错”时区与 locale 的干扰现象脚本下午或晚上运行时某一天的目录名和日志里记录的日期差一天或者%Y%m%d输出正常但用%c之类格式时出现中文月份影响排序。原因date 命令默认使用系统时区如果服务器 TZ 是 UTC而业务日志用的是本地时间二者在跨午夜时就会差一天locale 则影响月份的本地化输出。解决脚本开头统一设置export TZAsia/Shanghai或你的业务时区日期格式固定用%Y%m%d、%F这类数字格式不要依赖%b、%c等文本型格式做排序和比较。设置export LC_ALLC可以顺带避免中文 locale 下某些命令输出格式变化。6. 给日期遍历加“后悔药”断点续跑与并行是最后两招6.1 断点续跑用一个标记文件跳过已处理日期批量任务跑一半机器重启重新执行时要是从头再来前面的时间全白费。常见做法是为每个日期生成一个.done标记文件脚本开头判断标记是否存在存在就跳过for d in ${dates[]}; do done_file$backup_root/$d.done [ -e $done_file ] continue tar czf $backup_root/$d.tar.gz -C $log_root $d \ touch $done_file done这里保证只有打包成功才写标记避免“假成功”。这个模式不光适合日期遍历任何批量任务都能套用。6.2 并行遍历xargs -P 与串行循环的取舍日期与日期之间通常没有依赖天然适合并行。串行循环简单但遇到大范围、单日任务耗时长的情况可以用xargs -P控制并发数比如 4 个任务同时跑# 按偏移量生成日期列表并并行处理 seq 0 1825 | xargs -P 4 -I{} bash -c d$(date -d 2025-01-01 {} day %Y%m%d) tar czf /backup/$d.tar.gz -C /var/log/myapp $d 注意并行时日志会交错输出如果需要日志落盘建议每个任务重定向到各自的文件。并发数不是越大越好先试试-P 4再根据机器负载和磁盘 IO 调整。6.3 验证脚本输出文件计数与 md5 对比遍历完成后的验证比遍历本身更考验细节。最简单的验证是数文件数量期望值是“结束日期减开始日期再加 1”用ls $backup_root/*.tar.gz | wc -l对比。如果脚本跳过了不存在的目录这个数字会比期望值小这是预期行为。更严格的验证是抽查单日打包内容是否完整tar tzf /backup/20250105.tar.gz看文件清单或对源目录和包内文件做一次 md5 比对。我第一次写这类脚本时仗着“反正就几行代码”直接跑全量结果 8 号和 9 号的目录因为八进制报错被静默跳过下游拿到的数据缺了两天排查花了一个下午。后来养成习惯任何日期遍历脚本先打印序列再跑真实任务最后做数量校验。希望你少走这一步弯路希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

WMS物流仓储智能调度新解法:DeepSeek多目标优化与九个关键参数调参实战

WMS物流仓储智能调度新解法:DeepSeek多目标优化与九个关键参数调参实战

简介:这是一份面向物流仓储智能化从业者的实战文档,聚焦DeepSeek多目标优化算法在WMS仓储管理系统中的参数调优方法与落地路径,尤其适合负责库存分配、拣货路径规划和配送调度的算法工程师与研究者。压缩包内为单份PDF文档,体积约…

📅 2026/10/5 7:03:52
谢希仁《计算机网络》课后答案:从题库到协议分析实战指南

谢希仁《计算机网络》课后答案:从题库到协议分析实战指南

简介:计算机网络经典教材谢希仁《计算机网络》的配套课后习题答案,整合第七版与第八版内容,适合计算机、网络工程及相关专业学生课后自测、期末备考或考研复习使用。资源包仅含一个PDF文档,大小1.63MB,文件体量小&…

📅 2026/10/5 7:03:52
PatchCore工业缺陷检测实战:特征嵌入与记忆库构建详解

PatchCore工业缺陷检测实战:特征嵌入与记忆库构建详解

简介:基于特征嵌入的工业缺陷检测技术配套的PatchCore算法实践资料,面向有一定编程基础的数据科学从业者、本科生和研究生,适用于产品质检、图像处理等工程与教学场景。内容围绕基于重构与基于嵌入两条技术主线展开,重点讲解特征提…

📅 2026/10/5 7:03:52
MORE NEWS

更多资讯

📰

ARM64 CentOS 7手工安装MySQL 5.7实战:从二进制包到systemd全流程

1. 为什么ARM64的MySQL 5.7安装不像x86_64那样“无脑”如果你以前在x86_64的CentOS 7上装过MySQL,大概率会有这种体验:下载官方Yum源,然后yum install mysql-server,等进度条跑完就完事了。整个过程顺得就像在应用商店里装了个App…

📰

OpenHarmony上适配dcli_common:构建鸿蒙CLI工具链实践

说实话,第一次在 OpenHarmony 设备上正经跑通一套基于 Dart 的 CLI 工具流时,我的第一反应不是兴奋,而是恍惚。过去几年我们在 Linux 服务器和 macOS 上写习惯了各种dcli脚本,处理文件、读环境变量、拉起子进程,一切都…

📰

插件加载失败排查指南:从IAR到Harness与MusicFree的实战解析

搞技术这些年,我见过太多人被“plugins”这三个字母折磨得够呛。装了IDE,它提示failed to load plugins;跑了CI流水线,它提示harness failed to load plugins web boot;就连电脑上装个开源播放器,也动不动来…

📰

Win11 安装配置 Node.js 完全指南:从 LTS 到环境变量与 npm 提速

1. 先说清楚:Node.js 是干嘛的,哪些人需要装 1.1 一句话理解 Node.js Node.js 是什么?说人话,它就是一套能让 JavaScript 脱离浏览器、直接在操作系统上跑起来的运行环境。以前 JS 只能在网页里写点交互逻辑,装上 Nod…

📰

Supabase RLS实战:公开读、投稿写、待审核可见的权限策略

在内容社区类的项目里,权限设计永远是绕不开的一道坎。游客想看、用户想发、运营想审,三拨人对着同一张表,稍不留神就会出现“该看的看不到、不该改的随便改”的惨剧。我前段时间在 Supabase 上把一套「公开读、投稿写、待审核可见」的 RLS&a…

📰

408计算机网络复习:三层协议栈考点梳理与CRC/CIDR手算通关

简介:这份计算机网络复习资料以Word文档整理,面向考研408统考、申博复试及本科期末复习等场景,内容覆盖计算机网络概述、物理层、数据链路层、网络层等核心章节。资料系统梳理了互联网发展历程、网络体系结构、性能指标,并展开讲解…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬