76

我想通过 cron 运行一项作业,该作业将在每个星期二的给定时间执行。因为每个星期二都很容易:

0 6 * * Tue

但是如何在“每个第二个星期二”(或者如果您愿意 - 每个第二周)做到这一点?我不想在它自己的脚本中实现任何逻辑,但只在 cron 中保留定义。

4

14 回答 14

52

回答

修改您的周二 cron 逻辑以从 epoch 开始每隔一周执行一次。

知道一周有 604800 秒(忽略 DST 变化和闰秒,谢谢),并使用 GNU 日期:

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) .

于 2013-10-09T17:21:37.453 回答
49

怎么样,crontab即使在前五个字段中没有完全定义它,它也会保留它:

0 6 * * Tue expr `date +\%W` \% 2 > /dev/null || /scripts/fortnightly.sh
于 2008-12-08T16:25:55.363 回答
8

pilcrow的回答很棒。但是,它会导致 fortnightly.sh 脚本每偶数周运行一次(自纪元以来)。如果您需要脚本在奇数周运行,您可以稍微调整他的答案:

0 6 * * Tue expr \( `date +\%s` / 604800 + 1 \) \% 2 > /dev/null || /scripts/fortnightly.sh

将 1 更改为 0 会将其移回偶数周。

于 2015-10-20T04:22:08.410 回答
5

也许有点笨,但也可以创建两个 cronjobs,每个第一个星期二一个,每个第三个星期二一个。

第一个cronjob:

0 0 8 ? 1/1 TUE#1 *

第二个cronjob:

0 0 8 ? 1/1 TUE#3 *

不确定这里的语法,我使用http://www.cronmaker.com/来制作这些。

于 2013-10-09T15:50:45.053 回答
4

就像是

0 0 1-7,15-21 * 2

将在本月的第一个和第三个星期二。

注意:不要将它与 vixie cron(包含在 RedHat 和 SLES 发行版中)一起使用,因为它会在日期和星期几字段之间生成一个或,而不是一个和。

于 2021-06-30T09:11:40.110 回答
3

如果您想根据给定的开始日期执行此操作:

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- 如果余数为零,则您处于两周周期

于 2018-11-15T19:20:10.890 回答
1

我发现上述方法的一些额外限制在某些极端情况下可能会失败。例如,考虑:

@xahtep 和 @Doppelganger 讨论了使用%W上述特定年份界限的问题。

@pilcrow 的答案在某种程度上解决了这个问题,但它也会在某些边界上失败。此主题和/或其他相关主题的答案使用一天中的秒数(与一周相比),出于相同的原因,这在某些边界上也会失败。

这是因为这些方法依赖于 UTC 时间(日期 +%s)。考虑一个案例,我们在每个第二个星期二的凌晨 1 点和晚上 10 点运行一项工作。

假设 GMT-2:

  • 当地时间凌晨 1 点 = UTC昨天晚上 11 点
  • 当地时间晚上 10 点 =今天UTC 晚上 8 点

如果我们每天只检查一次,这不是问题,但如果我们检查多次——或者如果我们接近 UTC 时间并且发生夏令时,脚本不会认为这些是同一天。

为了解决这个问题,我们需要根据我们的本地时区而不是 UTC 计算与 UTC 的偏移量。我在 BASH 中找不到简单的方法来执行此操作,因此我开发了一种解决方案,该解决方案使用 Perl 中的快速单行来计算与 UTC 的偏移量(以秒为单位)。

这个脚本利用了 date +%z,它输出本地时区。

bash 脚本:

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
于 2016-12-21T05:19:13.450 回答
1

这里有很多很好的答案。根据评论,我看到了相当多的困惑和沮丧。我对这个答案的意图不仅是回答 OPs 问题如何指示 cron 每两周执行一次作业?,但也为将来可能会阅读此内容的人们消除了一些困惑。

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:执行 [command] 的分钟
范围:0-59
2:一天中执行 [command] 的时间
范围: 0-23 3:执行 [command] 的
日期 范围:1-31
4:执行 [command] 的月份
范围:1-12
5:执行 [command] 的星期几
范围:检查你的人page,可以是 0-6、1-7 或 0-7
6:执行命令
注意,还有 @ 值,这里不再讨论。

*nix 的某些版本允许表达式:
*/2= 范围内的
1 + */2每个偶数 = 范围内的每个奇数
1,15= 第一个和第 15 个
2-8= 第二个到第八个(包括)

一些版本也允许人类可读的单词,例如二月或星期三。

对于月份,*/2 将在 2 月、4 月、6 月、8 月、10 月、12 月执行。

对于星期几,*/2 将在偶数日运行——查看你的手册页以查看星期几是如何编号的。Tue/2 不是一个有效的表达式。

人类可读(日历)
问题 您需要知道的一些常数:一年
有 365.2464(为方便起见,我们将四舍五入为 365)。
一年有12个月
一周有7
一天有 86,400

因此,
1个月是4-1/3周
[即3个月是13周]
1年是52-1/7周

日历数学的祸根:
半月 = 每半个月 = 2x/月 = 每年 24 次。
双周 = 每隔一周(两周一次) = 每年 26 次。
注意:这些术语经常被误用。
有些年份有 51 周,有些年份有 53 周,大多数有 52 周。如果我的 cron 每隔奇数周运行一次(date +%W模式 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 中的双周时间算法是一年
中的周数,而不是纪元周。*nix 没有纪元周值,但可以计算。 Epoch Week = Integer( Epoch Seconds / Seconds_per_Day / Days_per_Week ) Fortnightly = Epoch_Week Modulo 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\>

希望这将澄清一些混乱

于 2021-08-26T23:03:49.217 回答
0

试试这个

0 0 1-7,15-21 * 2

每月运行 2 次,在星期二和至少相隔一周。

于 2021-06-30T09:25:28.133 回答
-2

如果您只想要第二周的每个星期二:

00 06 * * 2#2

于 2016-11-23T12:43:26.170 回答
-3

语法“/2”不符合工作日。所以我添加到上面的智能只是在 MonthDay 提交时使用 1,15。

0 6 1,15 * * /scripts/fornightly.sh > /dev/null 2>&1

于 2013-05-06T08:47:32.923 回答
-3

0 0 */14 * *

每月第 14 天 00:00 运行

于 2020-02-07T00:15:41.247 回答
-4

Cron 提供了“每隔一个”语法“/2”。只需在适当的时间单位字段后面加上“/2”,它将“每隔一次”执行 cronjob。在你的情况...

0 6 * * Tue/2

以上应该每隔一个星期二执行一次。

于 2013-02-22T11:11:56.840 回答
-4

为什么不喜欢

0 0 1-7,15-21,29-31 * 5

这合适吗?

于 2013-11-26T19:59:16.697 回答