如何指示 cron 每两周执行一次作业?
Posted
技术标签:
【中文标题】如何指示 cron 每两周执行一次作业?【英文标题】:How to instruct cron to execute a job every second week? 【发布时间】:2010-09-25 21:20:48 【问题描述】:我想通过 cron 运行一个作业,该作业将在每个第二个星期二在一天中的给定时间执行。因为每个星期二都很容易:
0 6 * * Tue
但是如何在“每个第二个星期二”(或者如果您愿意的话 - 每个第二周)做到这一点? 我不想在它自己的脚本中实现任何逻辑,但只在 cron 中保留定义。
【问题讨论】:
另见unix.stackexchange.com/questions/197324/… 每周运行它并检查您的脚本(例如,通过使用单独的文件)是第二周还是第一周怎么样?if [ "$(cat week.txt)" == "1" ]; then echo -n "0">week.txt; dostuff; fi
【参考方案1】:
回答
修改您的星期二 cron 逻辑以每隔一周执行一次自纪元以来。
知道一周有 604800 秒(忽略 DST 变化和闰秒,谢谢),并使用 GNU 日期:
0 6 * * Tue expr `date +\%s` / 604800 \% 2 >/dev/null || /scripts/fortnightly.sh
一边
日历算术令人沮丧。
@xahtep 的回答非常棒,但作为@Doppelganger noted in comments,它会在某些年份界限内失败。 date
实用程序的“一年中的一周”说明符都无济于事。一月初的某个星期二不可避免地会重复上一年最后一个星期二的周平价:2016-01-05 (%V)、2018-01-02 (%U) 和 2019-01-01 (%W) .
【讨论】:
这个答案,或者使用一天(不是一周)中的秒数的替代版本,例如86400 在某些极端情况下也有限制:UTC 时间与本地服务器的调整。假设我有一个 cron 作业,每隔一个星期二的凌晨 3 点和晚上 11 点运行一次。您最终可能会遇到这样一种情况,即根据 UTC 时间(自纪元以来的秒数),这些被视为不同的日子。在我的情况下,21600 是我的本地偏移量(秒)与时代的对比:expr \( `date +%s` - 21600 \) / 86400 % 2
并且,用于测试 expr \( `date +%s -d "Thursday"` - 21600 \) / 86400 % 2
查看下面我的回答以获得更完整的解决方案。【参考方案2】:
这个怎么样,它确实保留在crontab
中,即使它没有在前五个字段中准确定义:
0 6 * * Tue expr `date +\%W` \% 2 > /dev/null || /scripts/fortnightly.sh
【讨论】:
我的猜测是这个解决方案在一年的最后一周有问题。有时一年中的最后一个星期五在第 51 周,有时在第 52 周。所以这可能会让您连续两周或每三周运行一次脚本【参考方案3】:pilcrow's answer 很棒。但是,它会导致 fortnightly.sh 脚本每 even 周运行一次(从 epoch 开始)。如果您需要脚本在 odd 周内运行,您可以稍微调整他的答案:
0 6 * * Tue expr \( `date +\%s` / 604800 + 1 \) \% 2 > /dev/null || /scripts/fortnightly.sh
将 1 更改为 0 会将其移回偶数周。
【讨论】:
其实你不需要 0 或 1。只需将||
更改为 &&
。如果表达式的值为 1,则执行&&
之后的命令,否则,执行||
之后的命令【参考方案4】:
可能有点笨,但也可以创建两个 cronjobs,每个第一个星期二一个,每个第三个星期二一个。
第一个 cronjob:
0 0 8 ? 1/1 TUE#1 *
第二个定时任务:
0 0 8 ? 1/1 TUE#3 *
不确定这里的语法,我使用http://www.cronmaker.com/ 来制作这些。
【讨论】:
将作业安排在每个月的第一个和第三个星期二会错过一个月的第五个星期二(例如,2015 年 3 月 31 日),当然添加第五个星期二会过于频繁地运行作业。另外,这个答案引用了类似 cron 的 Quartz scheduler 而不是传统的 *NIX cron。 “#”是什么意思? @Asif “它允许您指定构造,例如给定月份的“第二个星期五”。” en.wikipedia.org/wiki/Cron @bigtex777:Linux 发行版的哪个 cron 实现了该功能?这当然不是 POSIX。【参考方案5】:类似
0 0 1-7,15-21 * 2
每月的第一个和第三个星期二。
注意:不要将它与 vixie cron(包含在 RedHat 和 SLES 发行版中)一起使用,因为它会在日期和星期几字段之间生成一个 or,而不是 and。
【讨论】:
我认为这是最好的解决方案!【参考方案6】:如果你想根据给定的开始日期来做:
0 6 * * 1 expr \( `date +\%s` / 86400 - `date --date='2018-03-19' +\%s` / 86400 \) \% 14 == 0 > /dev/null && /scripts/fortnightly.sh
应该从 2018-03-19 开始每隔一个星期一触发一次
表达式为:如果...,则在星期一早上 6 点运行
1 - 获取今天的日期,以秒为单位,除以一天中的秒数以转换为纪元的天数
2 - 对开始日期执行相同操作,将其转换为自纪元以来的天数
3 - 获取两者之间的差异
4 - 除以 14 并检查余数
5- 如果余数为零,则您处于两周周期
【讨论】:
【参考方案7】:我发现上述方法的一些额外限制在某些极端情况下可能会失败。例如,考虑:
@xahtep 和@Doppelganger 在上述特定年份使用
%W
讨论了问题。@pilcrow 的回答在某种程度上解决了这个问题,但它也会在某些边界上失败。此主题和/或其他相关主题的答案使用一天中的秒数(相对于一周),出于同样的原因,这在某些边界上也会失败。
这是因为这些方法依赖于 UTC 时间(日期 +%s)。假设我们在每个第二个星期二的凌晨 1 点和晚上 10 点运行一项工作。
假设 GMT-2:
当地时间凌晨 1 点 = 世界标准时间晚上 11 点 昨天 当地时间晚上 10 点 = 世界标准时间晚上 8 点 今天如果我们每天只检查一次,这不是问题,但如果我们检查多次——或者如果我们接近 UTC 时间并且出现夏令时,脚本不会考虑这些是同一天。
为了解决这个问题,我们需要根据我们的 local 时区而不是 UTC 计算与 UTC 的偏移量。我在 BASH 中找不到一种简单的方法来执行此操作,因此我开发了一种解决方案,该解决方案使用 Perl 中的快速单行来计算与 UTC 的偏移量(以秒为单位)。
此脚本利用 date +%z 输出本地时区。
Bash 脚本:
TZ_OFFSET=$( date +%z | perl -ne '$_ =~ /([+-])(\d2)(\d2)/; print eval($1."60**2") * ($2 + $3/60);' )
DAY_PARITY=$(( ( `date +%s` + $TZ_OFFSET ) / 86400 % 2 ))
然后,判断这一天是偶数还是奇数:
if [ $DAY_PARITY -eq 1 ]; then
...
else
...
fi
【讨论】:
日期不使用 UTC 时间。它使用您系统上设置的时区。 @ScottieH 请仔细阅读解决方案。无论系统时区是什么,它都会将日期输出调整为 UTC 时间。 你真的见过这种行为吗?我系统上的 Cron 无法按照您在边缘情况下描述的方式工作。date +%s
返回一个 unix 纪元时间。这是 UTC 格式的标准化格式,与任何本地时区设置无关。因此,根据您的服务器时区,它将有边缘情况。例如,英国的夏令时与美国的夏令时不同。此外,美国的几个地方不遵守夏令时,而 UTC 确实遵守夏令时。将 unix 纪元时间转换为本地时间是目标,但根据您运行脚本的时间和您的 TZ 与 UTC 的偏移量,会有一些极端情况。
@ScottieH 您的答案(与其他许多人一样)看起来更简单,并且可能适用于 99.9% 的情况。但是,由于时区差异存在一些极端情况,这可能会导致意外行为或错过 cron 作业。如果作业在同一天每 2 周运行一次,我提出的解决方案会奏效。【参考方案8】:
试试这个
0 0 1-7,15-21 * 2
每月运行 2 次,每周二和至少相隔一周。
【讨论】:
【参考方案9】:这里有很多很好的答案。根据 cmets,我看到了相当多的困惑和沮丧。我这个答案的目的不仅是回答 OPs 问题How to instruct cron to execute a job every second week?,而且还为将来可能会阅读此内容的人们澄清一些困惑。
TL;DR: crontab 条目如下所示:
\<minute\> \<hour\> * * \<Day of Week\> expr \\( $( date+\\%s ) \\/ 604800 \\% 2 \\) > /dev/null && \<command to run on odd weeks\> || \<command to run on even weeks\>
Crontab 条目:
根据手册页,所有 crontab 条目 [使用 crontab -e
创建的] 都具有这种格式:
1:每小时执行的分钟 [命令]
范围:0-59
2:一天中执行的时间 [命令]
范围:0-23
3:要执行的日期 [命令]
范围:1-31
4:要执行的月份[命令]
范围:1-12
5:星期几执行[命令]
范围:检查您的手册页,可以是 0-6、1-7 或 0-7
6:要执行的命令
注意,还有@值,这里就不讨论了。
*nix 的某些版本允许表达式:*/2
= 范围内的每个偶数1 + */2
= 范围内的每个奇数1,15
= 第一个和第 15 个@ 987654327@ = 第二至第八(含)
某些版本还允许人类可读的单词,例如二月或星期三。
对于月份,*/2 将在 2 月、4 月、6 月、8 月、10 月、12 月执行。
对于星期几,*/2 将在偶数日运行——查看你的手册页以查看星期几是如何编号的。 Tue/2 不是一个有效的表达式。
人类可读(日历)问题 你需要知道的一些常量: 一年有 365.2464 天(为方便起见,我们将四舍五入为 365)。 一年有 12 个个月。 一周有 7 天。 一天有 86,400 秒。
因此, 1 个月是 4-1/3 周 [IE。 3个月是13周] 1 年是 52-1/7 周
日历数学的祸根:
半月 = 每半个月 = 2 次/月 = 每年 24 次。
每两周一次 = 每隔一周(每两周一次)= 每年 26 次。
注意:这些术语经常被误用。
有些年份有 51 周,有些有 53 周,大多数有 52 周。如果我的 cron 每隔奇数周运行一次(date +%W
mod 2),并且一年有 51 或 53 周,它也会在下一周运行,即第 1 周的新年。相反,如果我的 cron 每偶数周运行一次,它将跳过 2 周。不是我想要的。
CRON 可以支持半月,不支持双周!
半月刊:
每月的第一天总是在 1 号和 7 号之间。下半周总是发生在 15 日和 21 日之间。
Semi-monthly 将有 2 个值,一个在上半月,另一个在下半月。如:2,16
Unix 时间
在非常高的水平上,*nix 时间是 2 个值:date +%s
= 自 Epoch (01/01/1970 00:00:00) 以来的秒数date +%N
= 小数秒(以纳秒为单位) )
因此,*nix 中的时间为date +%s.%N
*Nix 使用纪元时间。 /etc/shadow 文件包含上次更改密码的日期。它是 %s 的整数部分除以 86,400。
Factino GPS 卫星将时间测量为“自纪元时间以来的周数”和“一周内的(小数)秒。 注意:纪元周与年份无关。一年有 51 周、52 周还是 53 周并不重要。纪元周永远不会翻转。
双周时间算法 在 *Nix date +%W 中是 year 的周数,而不是纪元周。 *nix 没有纪元周值,但可以计算。 纪元周 = 整数(纪元秒数 / Seconds_per_Day / Days_per_Week ) 每两周一次 = Epoch_Week 模 2 Fortnightly 值将始终为 0 或 1,因此,当 Fortnightly = 0 时在每个偶数周运行命令,1 = 每个奇数周运行。
每两周计算一次)
第一种方式(bc):date +"%s / 604800 % 2" | bc
这将生成“[Epoch Seconds] / 604800 % 2”
然后将该答案发送到 bc,这是一个基本计算器,它会进行数学运算并将答案回显到 STDOUT 以及任何错误到 STDERR。
返回码为0。
第二种方式(expr):
在这种情况下,将表达式发送到 expr 并让它进行数学运算
expr $( date +%s ) / 604800 % 2
expr 命令进行数学运算,将答案回显到 STDOUT,将错误回显到 STDERR。
如果答案为 0,则返回码为 1,否则返回码为 0。
以这种方式,我不关心答案是什么,只关心返回码。考虑一下:
$ expr 1 % 2 && echo Odd || echo Even
1
Odd
$ expr 2 % 2 && echo Odd || echo Even
0
Even
由于结果无关紧要,我可以将 STDOUT 重定向到 /dev/null。在这种情况下,我会离开 STDERR,以防发生意外情况,我会看到错误消息。
Crontab 条目
在 crontab 中,百分号和括号有特殊的含义,所以需要用反斜杠 (\) 进行转义。
首先,我需要决定是要在偶数周还是奇数周运行。或者,也许,在奇数周运行 Command_O,在偶数周运行 command_E。然后,我需要转义所有特殊字符。
由于 expr 仅评估 WEEKS,因此有必要指定一周中的特定日期(和时间)进行评估,例如每周二下午 3:15。因此,我的 crontab 条目的前 5 个文件(时间条目)将是:15 3 * * TUE
我的 cron 命令的第一部分将是 expr 命令,将 STDOUT 发送到 null:
expr \\( $( date+\\%s ) \\/ 604800 \\% 2 \\) > /dev/null
第二部分将是评估者,&&
或 ||
第三部分将是我要运行的命令(或脚本)。看起来像这样:
expr \\( $( date+\\%s ) \\/ 604800 \\% 2 \\) > /dev/null && \<command_O\> || \<command_E\>
希望这能澄清一些困惑
【讨论】:
这篇文章与其他许多帖子有相同的错误。看我的帖子。如果您的服务器在 UTC 时间运行,则此解决方案将起作用。但是,对于任何其他时区,由于夏时制,存在遇到边缘情况的风险......并且事实上a)并非所有地方都遵守夏时制,并且b)不同国家/地区的夏时制规则不同(例如,月/周不同步)。考虑 1616025600 和 1616025599 时期。如果您的 cron 时间在边界上,并且您正在经历夏令时事件,则可能会产生错误的结果。 不是真的!一天有86,400秒。 2 周包含 1,209,600 秒。因此,每隔一周只是时间的流逝,与我的时区无关。如果碰巧我需要通过地理多样化的网络同步我的 cron 作业,那么您说得有道理。但是,这不是 OP 的问题。问题是(有效地):如何每 1,209,600 秒执行一次 cron 作业。 /完成 我认为这里有一个微妙但关键的点,你错过了。没有人争论一天中的秒数。然而,Cron 作业以“每个星期二@晚上 11 点”之类的术语来引用。由于夏令时以及 UTC 和每个单独的时区夏令时之间的差异,unix 时期之间的“有效”差异可能是出乎意料的。考虑:1615708800 = 美国/太平洋时间 21 年 3 月 14 日凌晨 12:00。 1615791600 = 美国/太平洋时间 21 年 3 月 15 日凌晨 12:00。 unix epoch 之间的差异只有 82,800 秒……但根据 cron 作业,应该已经过去了 24 小时。【参考方案10】:如果您只想要第二周的每个星期二:
00 06 * * 2#2
【讨论】:
哪个 cron 在哪个 Linux 上支持该功能? Linux 上的标准 cron 使用#
作为注释分隔符。【参考方案11】:
语法“/2”不适合工作日。所以我添加到上面的智能只是在 MonthDay 归档时使用 1,15。
0 6 1,15 * * /scripts/fornightly.sh > /dev/null 2>&1
【讨论】:
请注意,这在每个月的 1 日和 15 日运行,而不是每两周一次(每两周一次)。有时一个月有超过4周。您在这里看到的是“每月两次”,而不是“每隔一周”。 嗯。我仍然无法找出类似请求的解决方案:一年中每个星期一的中午运行的工作,无论月份如何。 0 12 * * 1/2 表示“糟糕的星期几”。【参考方案12】:0 0 */14 * *
每月第 14 天的 00:00 运行
【讨论】:
【参考方案13】:Cron 提供“每隔一个”语法“/2”。只需在适当的时间单位字段后面加上“/2”,它将“每隔一次”执行 cronjob。在你的情况下......
0 6 * * Tue/2
以上应该每隔一个星期二执行一次。
【讨论】:
这个命令我测试过,每周二、四、六运行;不是每隔一个星期二。 你有没有机会测试过 0 6 * * Tue/14? 遗憾 - Tue/2 没有在我的系统上运行 :(【参考方案14】:为什么不喜欢
0 0 1-7,15-21,29-31 * 5
这样合适吗?
【讨论】:
这只会在每个月的第一周、第三周和第五周运行。 但如果改为 0 0 15-21 * 5 这似乎可以解决问题。 重新阅读crontab(5)
的手册页。您的格式将在第一周的每一天、第三周的每一天、最后一周的每一天以及恰好是星期五的任何一天运行。跑了很多次。
同意@ChristopherSchultz以上是关于如何指示 cron 每两周执行一次作业?的主要内容,如果未能解决你的问题,请参考以下文章