我想通过cron来运行一个任务,该任务将在每个月的第二个星期二在特定时间执行。对于每个星期二是很容易处理的:
0 6 * * Tue
但是如何使其在“每个第二个星期二”(或者如果您喜欢-每两周)运行?我不想在脚本中实现任何逻辑,而只想在cron中保留定义。
我想通过cron来运行一个任务,该任务将在每个月的第二个星期二在特定时间执行。对于每个星期二是很容易处理的:
0 6 * * Tue
但是如何使其在“每个第二个星期二”(或者如果您喜欢-每两周)运行?我不想在脚本中实现任何逻辑,而只想在cron中保留定义。
修改您的星期二cron逻辑,使其自时代以来每隔一周执行一次。
知道每周有604800秒(忽略DST更改和闰秒,谢谢),并使用GNU date:
0 6 * * Tue expr `date +\%s` / 604800 \% 2 >/dev/null || /scripts/fortnightly.sh
日历计算很令人沮丧。
@xahtep的答案非常棒,但是,正如@Doppelganger在评论中指出的那样,它在某些年份边界上会失败。 任何一个date
实用程序中的“周数”格式化标识符都无法解决这个问题。 一月初的某个星期二将不可避免地重复前一年最后一个星期二的周奇偶性:2016-01-05(%V),2018-01-02(%U)和2019-01-01(%W)。
expr \( `date +%s` - 21600 \) / 86400 % 2
并且,用于测试expr \( `date +%s -d "Thursday"` - 21600 \) / 86400 % 2
- dxdc这样怎么样,即使在前五个字段中没有明确定义,它也可以保留在crontab
中:
0 6 * * Tue expr `date +\%W` \% 2 > /dev/null || /scripts/fortnightly.sh
类似这样的东西
0 0 1-7,15-21 * 2
将在每月的第一个和第三个星期二运行。
注意:不要在使用vixie cron(包含在RedHat和SLES发行版中)时使用此功能,因为它会将日期字段和星期字段之间的逻辑关系从"and"改成"or"。
0 0 8 ? 1/1 TUE#1 *
第二个定时任务:
0 0 8 ? 1/1 TUE#3 *
这里的语法我不是很确定,我使用了http://www.cronmaker.com/来生成这些。
pilcrow的回答非常好。然而,它导致fortnightly.sh脚本每隔偶数周(自纪元以来)运行一次。如果你需要该脚本在奇数周运行,可以稍微调整他的答案:
0 6 * * Tue expr \( `date +\%s` / 604800 + 1 \) \% 2 > /dev/null || /scripts/fortnightly.sh
||
更改为&&
即可。如果表达式评估为1,则执行&&
后面的命令,否则执行||
后面的命令。 - Scottie H0 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 - 获取今天的日期,转换为距离 epoch 的天数,单位为秒,然后除以一天的秒数
2 - 对于起始日期也进行同样的步骤,将其转换为距离 epoch 的天数
3 - 获取两者之间的差值
4 - 将差值除以14并检查余数
5 - 如果余数为零,则您处于两周周期中
\<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 -e
创建的Crontab条目都遵循以下格式,根据man页面:某些*nix版本允许使用表达式:
*/2
= 范围内的每个偶数
1 + */2
= 范围内的每个奇数
1,15
= 第一和第十五
2-8
= 从第二到第八(包括第二和第八)
某些版本还允许使用可读性较强的单词,例如“February”或“Wednesday”。
对于月份,*/2将在二、四、六、八、十二月执行。
对于星期几,*/2将在每个偶数日运行 - 请查看您的man页面以查看星期几的编号。Tue/2不是有效的表达式。
人类可读(日历)困扰
您需要了解一些常量:
一年有365.2464天(为方便起见,我们将四舍五入为365)。
一年有12个月。
一周有7天。
一天有86,400秒。
因此,
1个月是4-1/3周
[即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日之间。
半月将有2个值,一个在月初,另一个在月中旬。例如:
2,16
Unix时间
在非常高的层面上,*nix时间有两个值:
date +%s
= 自纪元(1970年1月1日00:00:00)以来的秒数
date +%N
= 分数秒数(以纳秒为单位)
因此,在*nix中的时间是date +%s.%N
*Nix使用纪元时间。 /etc/shadow文件包含最后更改密码的日期。它是%s的整数部分除以86,400。
事实
GPS卫星将时间测量为“自历元时间以来的周数”和“一周内的(分数)秒数。
注意:Epoch周与年无关。无论该年是否有51、52或53周,Epoch周都不会翻转。
计算每两周
第一种方法(bc):
date +"%s / 604800 % 2" | bc
这将生成"[Epoch Seconds] / 604800 % 2"
然后将该答案发送到bc,一个基本计算器,它执行计算并将答案回显到STDOUT和任何错误到STDERR。
返回代码为0。
expr $( date +%s ) / 604800 % 2
$ 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\>
希望这能澄清一些混淆的问题。
@xahtep和@Doppelganger上面讨论了使用
%W
在特定年份边界上的问题。@pilcrow的答案在某种程度上解决了这个问题,但它也会在某些边界上失败。在此和其他相关主题中的答案使用一天中的秒数(而不是星期),由于同样的原因,这也会在某些边界上失败。
这是因为这些方法依赖于UTC时间(日期+%s)。考虑这样一个情况,我们每隔两周的第二个星期二在1am和10pm运行一个作业。
假设GMT-2:
如果我们只检查每天的单个时间,这将不是一个问题,但如果我们检查多次--或者如果我们接近UTC时间并且夏令时发生,脚本将不认为这些是同一天。
为了解决这个问题,我们需要根据我们的本地时区而不是UTC计算偏移量。我在BASH中找不到简单的方法来实现这一点,因此我开发了一个解决方案,使用Perl中的一行快速代码来计算以秒为单位的与UTC的偏移量。TZ_OFFSET=$( date +%z | perl -ne '$_ =~ /([+-])(\d{2})(\d{2})/; print eval($1."60**2") * ($2 + $3/60);' )
DAY_PARITY=$(( ( `date +%s` + ${TZ_OFFSET} ) / 86400 % 2 ))
if [ ${DAY_PARITY} -eq 1 ]; then
...
else
...
fi
date +%s
返回 Unix 时间戳。这是一个标准化的格式,在 UTC 时间下,与任何本地时区设置无关。正因为如此,它将会有一些边缘情况,具体取决于您的服务器时区设置。例如,英国的夏令时不会在同一时间发生,而美国的夏令时也是如此。此外,美国的一些地方不遵守夏令时,而 UTC 则遵守夏令时。将 Unix 时间戳转换为本地时间是目标,但是根据您运行脚本的时间和您相对于 UTC 的 TZ 偏移量,可能会出现一些边缘情况。 - dxdc试试这个
0 0 1-7,15-21 * 2
这个程序每月运行2次,都在周二,并且至少相隔一周。
if [ "$(cat week.txt)" == "1" ]; then echo -n "0">week.txt; dostuff; fi
- xdevs23