
简介该Shell脚本实例演示了在给定起止日期之间倒序遍历并输出日期列表。面向Linux/Unix运维人员和需要按日期批量处理数据的开发者可用于日志归档、定时任务补偿、按日数据抓取等场景。资源共1个PDF文档整包约27KB内含完整脚本、执行命令及输出示例便于复制验证。脚本接受两个日期参数借助date -d将日期转为时间戳通过双层循环逐日递减外层循环提供300次安全边界内层比较当前日期与结束日期最终以%F标准格式输出序列并演示调用Python脚本pc_jibzhuanti_url.py的扩展方式体现Shell与Python联动写法。脚本执行后从开始日期递减到结束日期输出结果可直接作为目录名或后续入参如需更长范围可自行调整循环次数。已有2025人学习下载适合希望掌握Shell日期遍历、或改造为其他自动化任务的开发者参考。1. Shell脚本遍历一个日期范围实例不用每天手动改日期Shell脚本遍历一个日期范围实例看着代码不长但真正写起来很容易在日期边界上翻车。这份脚本接收两个日期参数比如./pc_jibzhuanti_url_run.sh 2014-06-30 2014-06-01它就会从2014-06-30开始按天倒序把日期逐个打印出来每打印一个日期就调用一次Python脚本继续处理。对每天定时补数据、刷日志、跑专题的运维和开发来说这比手动改日期省事得多。它用的是一个for循环加时间戳比较没有特别花哨的语法但把日期遍历这个需求讲得很完整。这个实例很适合刚入门shell脚本的人拿来练手也适合直接改成自己的日期处理工具。今天我会把它逐行拆开再把几个更隐蔽的坑翻出来。2. 拆解实例两个日期参数、时间戳比较和倒序循环2.1 先看脚本全貌把原脚本保存成pc_jibzhuanti_url_run.sh加执行权限后运行它会从2014-06-30一路倒序打印到2014-06-01。完整代码是这样的#!/usr/bin/env bash date1$1 date2$2 echo date1: $date1 echo date2: $date2 tempdatedate -d -0 day $date1 %F enddatedate -d -0 day $date2 %F tempdateSecdate -d -0 day $date1 %s enddateSecdate -d -0 day $date2 %s echo #################################### echo tempdate: $tempdate echo enddate: $enddate for i in seq 1 300; do if [[ $tempdateSec -lt $enddateSec ]]; then break fi echo $tempdate python pc_jibzhuanti_url.py $tempdate tempdatedate -d -$i day $date1 %F tempdateSecdate -d -$i day $date1 %s done这个脚本的骨架很清楚两个位置参数、日期格式化、时间戳转换、for循环、if退出条件、业务调用。严格说它只有一个循环不是嵌套循环if break只是提前退出。每次循环里i从1开始递增恰好用来表示“从date1往前推i天”所以第一次循环输出的还是date1本身而不是date1的前一天。这里有个容易看错的点循环体里先输出tempdate再用i去更新它。也就是说当i1时输出的是初始化好的date1等下一次循环i2时tempdate已经变成date1减1天。这样整个序列才是date1、date1-1、date1-2这样连续递减的。2.2 参数接收、回显和-0 day的写法原脚本开头这几行我一般会单独拿出来看date1$1 date2$2 echo date1: $date1 echo date2: $date2 tempdatedate -d -0 day $date1 %F$1和$2就是调用脚本时传入的开始日期和结束日期。加双引号是好习惯避免日期参数里出现空格时被拆成多个单词。echo回显主要是确认参数有没有接对实际使用中我通常还会加一个参数个数校验否则漏传参数时date命令很容易报出一堆看不懂的错误。-0 day这个写法看起来有点怪它表示“日期字面量减0天”本质上就是不改变日期。在GNU date环境里直接写成date -d $date1 %F效果一样。我猜原作者是想强调后面可以跟-N day这种偏移表达式但写成-0 day反而容易让新手误会。如果要把这份脚本收进自己的工具库我更推荐用$(date -d $date1 %F)而不是反引号反引号在嵌套时很容易出问题$()更清晰。参数校验的常见做法是if [ $# -lt 2 ]; then echo 用法: $0 开始日期 结束日期 echo 示例: $0 2014-06-30 2014-06-01 exit 1 fi校验放在脚本开头能挡住大部分因为手滑漏传参数导致的翻车。日期格式方面%F等价于%Y-%m-%d输出出来是2014-06-30这种标准格式Python的datetime.strptime可以直接按%Y-%m-%d解析不需要额外转换。2.3 时间戳比较为什么用%s而不是比字符串脚本把两个日期都转成了Unix时间戳tempdateSecdate -d -0 day $date1 %s enddateSecdate -d -0 day $date2 %s%s输出的是从1970年1月1日0点开始到指定日期的秒数。在Linux里date -d 2014-06-30 %s会按本地时区解析这个日期然后得到一个整数秒。有了这两个整数比较日期早晚就变成了数值比较不牵扯字符串字典序的问题。理论上YYYY-MM-DD格式的日期也可以用字符串比较比如2014-06-30 2014-06-01是成立的。但一旦日期格式变成2014-6-30这种缺前导0的写法字符串比较就会出错。时间戳比较更通用而且后面要计算两个日期之间的天数差时直接用秒数相减除以86400就行。tempdateSec -lt enddateSec这个判断条件的意思是如果当前日期已经早于结束日期就退出循环。注意是-lt不是-le。当当前日期恰好等于结束日期时条件不成立脚本会先输出这个结束日期再更新日期下一轮才break。这正是我们想要的日期范围的头尾都包含在输出里。2.4 输出日期和调用Python之间的传参细节循环体里的核心操作是echo $tempdate python pc_jibzhuanti_url.py $tempdate tempdatedate -d -$i day $date1 %F tempdateSecdate -d -$i day $date1 %secho $tempdate把当前日期打印到终端方便人工核对紧接着调用的Python脚本接收日期参数继续做业务处理。这个实例里的Python脚本是pc_jibzhuanti_url.py从名字看应该是抓取专题URL的爬虫脚本按日期逐个跑。我在排错时会把python那一行先注释掉只保留echo $tempdate跑一遍。这样能快速确认日期序列对不对不会因为Python业务脚本报错干扰判断。等日期序列确认无误再放开Python调用。另外一个细节是调用Python时变量最好加双引号python pc_jibzhuanti_url.py $tempdate不加引号时如果$tempdate为空或者包含空格会被shell拆成多个参数传给Python导致TypeError或者参数数量对不上。日期字符串本身没有空格但养成给变量加双引号的习惯能省很多事。3. 手动指定日期范围参数校验、date格式与方向控制3.1 传参方式位置参数、默认值和usage原脚本靠$1、$2接收日期这种位置参数是最基础的shell命令行用法。实际写脚本时我会在开头加一段usage提示不然隔一个月再拿来用很容易忘掉参数顺序。#!/usr/bin/env bash if [ $# -lt 2 ]; then echo 用法: $0 开始日期 结束日期 echo 示例: $0 2014-06-30 2014-06-01 exit 1 fi date1$1 date2$2这段逻辑不复杂参数数量少于2就直接退出并打印用法。$0是脚本自身文件名这样即使脚本被改名提示也是跟着走的。如果你希望某个日期缺省时用默认值也可以这样写date1${1:-$(date %F)} date2${2:-$(date %F)}${1:-默认值}是Bash参数扩展的写法意思是“第一个参数如果是空就用默认值”。日期遍历这种场景里我一般还是强制要求两个参数都传因为默认值很容易让脚本在无人值守时静默跑错日期。3.2%F、%s和%Y%m%d日期格式怎么选date -d的格式化参数决定了输出的日期长什么样也决定了下游脚本怎么解析。把常见的几种列在这里格式输出示例适用场景%F2014-06-30通用、可排序、Python可直接解析%Y/%m/%d2014/06/30对接老接口或展示%Y%m%d20140630文件名、表分区键%T14:30:00时间戳场景%s1404057600时间戳比较、天数计算%F是最省事的选择date -d 2014-06-30 %F输出的字符串即使是字符串比较也不会出错Python里对应的解析格式是%Y-%m-%d。如果改成%Y%m%d比如20140630在shell里写文件路径会舒服很多。假设你想生成一个带日期的日志文件名current_date$(date -d 2014-06-30 %Y%m%d) echo log_${current_date}.txt输出log_20140630.txt。但是要注意一旦改了日期输出格式Python脚本里的解析逻辑也要同步改这是日期遍历脚本最常见的一类联动事故。3.3 倒序改成成序不是只换一个减号原脚本是倒序输出因为date -d -$i day $date1里的减号表示“往前推i天”。想改成从2014-06-01正序输出到2014-06-30不只要把减号换成加号还要把退出条件反过来。原脚本的判断是“当前日期早于结束日期就break”如果改成正序逻辑要变成“当前日期晚于结束日期就break”。手工改这两处很容易漏掉一个。比如只把减号换加号不调整判断条件脚本会在第一天就bark掉一个日期都出不来。所以我在实际使用时更愿意把方向判断交给脚本自动处理这就是下一章要讲的重构方案。不要把日期方向做成一个变量来传比如DIRECTIONforward这样还得在循环里写一堆if分支容易绕晕。最省心的做法是比较两个时间戳大的当作起点小的当作终点自动决定用加法还是减法。这个思路放代码里特别干净。4. 改造动态计算日期差替换固定seq 1 3004.1 固定seq 1 300这个上限到底有什么问题原脚本里有一行注释#for i in seq 1 130; do for i in seq 1 300; do作者把循环上限从130改大到了300说明这个数字纯粹是一个“安全边界”。日期范围在300天以内时没问题一旦跨度接近300天麻烦就来了。问题出在循环次数上。seq 1 300最多让循环体执行300次而输出序列包括开始日期本身所以它实际最多输出300个日期。如果范围是2014-01-01到2013-12-31这种跨两天的情况能正常输出但如果范围是299天输出的日期数量是range1个正好300个刚好覆盖。范围一旦是300天需要301个日期最后一天就会被丢掉。而且这个截断是静默的脚本不会报错也不会提示你“日期范围太大”等你发现时已经少跑了一天的数据。因此动态计算日期差是有必要的。4.2 用时间戳差算出需要遍历多少天计算两个日期之间差多少天最直接的做法是用时间戳相减再除以86400。但直接除有个小坑如果本地时区刚好遇到夏令时切换某一天可能只有23或25小时除出来的结果会有小数。稳妥的做法是先把日期固定到中午12点再转时间戳。start_date2014-06-30 end_date2014-06-01 start_sec$(date -d $start_date 12:00 %s) end_sec$(date -d $end_date 12:00 %s) if [ $start_sec -gt $end_sec ]; then diff$(( (start_sec - end_sec) / 86400 )) else diff$(( (end_sec - start_sec) / 86400 )) fi echo date diff: $diff加上12:00后两个时间戳都落在中午不再受夏令时切换影响。diff得到的是两个日期之间隔了多少个整天。比如2014-06-30和2014-06-01的diff是29但要输出从6月30日到6月1日这30个日期循环次数是diff1。后面写循环时很容易把diff写成 diff少输出一天这条踩坑记录会在第5章详细说。4.3 一个自动判断方向的完整版本下面是我习惯用的重构版本动态计算天数差自动判断正序还是倒序并且保留了调用Python的接口#!/usr/bin/env bash # 动态日期范围遍历自动判断正序/倒序 if [ $# -lt 2 ]; then echo 用法: $0 开始日期 结束日期 echo 示例: $0 2014-06-30 2014-06-01 exit 1 fi start_date$1 end_date$2 # 固定到中午12点再计算时间戳避开夏令时误差 start_sec$(date -d $start_date 12:00 %s) end_sec$(date -d $end_date 12:00 %s) # 计算日期差天 if [ $start_sec -gt $end_sec ]; then diff$(( (start_sec - end_sec) / 86400 )) else diff$(( (end_sec - start_sec) / 86400 )) fi # 根据方向遍历日期 for ((i0; idiff; i)); do if [ $start_sec -ge $end_sec ]; then current_date$(date -d $start_date - $i day %F) else current_date$(date -d $start_date $i day %F) fi echo $current_date # 业务调用按需放开 # python pc_jibzhuanti_url.py $current_date done这段逻辑有四个关键点第一个关键点是用for ((i0; idiff; i))替代了seq 1 300循环次数由真实的日期差决定。i从0开始对应输出起点日期本身当idiff时正好输出终点日期。第二个关键点是方向判断只看start_sec和end_sec的大小关系。start_sec end_sec时说明前一个日期更晚需要倒序输出用- $i day反之用 $i day。两个参数谁先谁后都能跑。第三个关键点是date -d $start_date - $i day这种写法GNU date会把整个字符串当作日期表达式解析。- $i day中间有空格不会影响结果写成-${i} day也一样。第四个关键点是调用Python前一定要给$current_date加双引号。重构后日期变量统一从date命令输出理论上不会有空格但双引号是习惯问题能顺手挡掉很多玄学报错。验证这个脚本是否改对了方法很简单把Python调用注释掉跑一次小范围bash date_range.sh 2024-06-05 2024-06-01预期输出是2024-06-05、2024-06-04、2024-06-03、2024-06-02、2024-06-01头尾齐全。再跑一次正序bash date_range.sh 2024-06-01 2024-06-05预期输出六一到六五日期顺序反过来。能把这两个用例跑通重构基本就稳了。5. 日期遍历的常见坑与排查思路5.1 日期边界结束日期被跳过或固定上限导致静默截断现象一脚本输出上没有结束日期比如从2014-06-30到2014-06-01结果最后一个是2014-06-02。原因往往是把退出条件写成了-le而不是-lt当当前日期等于结束日期时直接break结束日期就被跳过了。解决方法是确认判断条件用的是-lt并且跑完后用head和tail分别看序列的第一个和最后一个日期。现象二日期范围很大时输出在某个日期戛然而止没有任何报错。原因就是原脚本的seq 1 300上限。i最多跑到300输出数量上限是300范围超过299天就会漏掉末端日期。解决方法是改成第4章那种动态计算diff的写法让循环次数和真实日期差挂钩。5.2 日期差计算夏令时让“一天”不是24小时现象计算两个日期之间的天数差时预期输出30天脚本却只输出了29天或31天。原因在实行夏令时的地区某个晚上变成23小时或25小时直接用两个时间戳相减再除以86400结果不是整数shell的整数运算直接舍掉小数天数差就偏了。解决计算前把日期固定到中午12点再转时间戳也就是date -d $date 12:00 %s。这一步把时间戳都拨到一天的中间避开夏令时切换的那几个小时。还有更彻底的办法统一使用UTC时间date -u -d $date 12:00 %s。不过大部分业务脚本只处理本地日期固定到12点已经足够。5.3 系统差异与传参GNU date、BSD date和没加引号的变量现象同一个脚本在Linux服务器上跑得好好的拿到macOS的终端上一执行直接报date: illegal option -- d。原因macOS自带的date是BSD date不支持Linux的date -d它用的是-v参数。解决要么在脚本里兼容两种系统要么干脆把日期计算交给Python。兼容的判断片段如下if [[ $(uname) Darwin ]]; then current_date$(date -v-1d -j -f %Y-%m-%d $start_date %Y-%m-%d) else current_date$(date -d $start_date - 1 day %F) fi这段代码只示意了“减1天”完整版本里还要处理加法和循环。如果这个脚本只给自己用我更推荐在Linux环境里跑别让macOS和BSD date参与进来。另一个传参坑是调用下游脚本时变量没加引号。现象是Python脚本收到的参数变成一个元组比如把整段日期当成多个参数传进去报got an unexpected keyword argument或者ValueError。原因就是python pc_jibzhuanti_url.py $tempdate里的$tempdate没有加引号被shell分开了。解决方法是统一写成$tempdate这个习惯在shell脚本里越早养成越好。6. 进阶把日期遍历封装成函数以后随用随取日期遍历逻辑不只是写在一两个脚本里我会把它封装成函数放在一个公共的shell库文件中。这样其他脚本只要source一下就能直接调用。# 放到 date_lib.sh 里 iter_dates() { local start$1 local end$2 local start_sec end_sec diff i start_sec$(date -d $start 12:00 %s) end_sec$(date -d $end 12:00 %s) if [ $start_sec -ge $end_sec ]; then diff$(( (start_sec - end_sec) / 86400 )) for ((i0; idiff; i)); do date -d $start - $i day %F done else diff$(( (end_sec - start_sec) / 86400 )) for ((i0; idiff; i)); do date -d $start $i day %F done fi }使用的时候#!/usr/bin/env bash source date_lib.sh for d in $(iter_dates 2014-06-30 2014-06-01); do echo 处理 $d python pc_jibzhuanti_url.py $d doneiter_dates函数只负责输出日期不负责业务逻辑。需要把日期写入文件时直接重定向iter_dates 2014-06-30 2014-06-01 dates.txt需要并行处理时配合xargs -P可以同时开多个任务iter_dates 2014-06-30 2014-06-01 | xargs -I {} -P 4 python pc_jibzhuanti_url.py {}不过并行会带来日志顺序乱和下游接口压力问题如果Python脚本本身不是线程安全的不要轻易加-P。我自己现在写日期遍历脚本第一件事是先把iter_dates函数复制过来第二件事是接业务前先纯echo跑一遍确认头尾日期没有多也没有少。这个习惯帮我避开了好多次跨月补数据的坑。希望帮到你。本文还有配套的精品资源点击获取