如何指示 cron 每隔一周执行一次作业?

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

0 6 * * Tue

但是如何在“每隔一个星期二”(或者如果你喜欢的话——每隔一个星期)做到这一点呢? 我不想在脚本中实现任何逻辑,但是只在 cron 中保留定义。

100180 次浏览

这个怎么样,它确实把它保存在 crontab中,即使它在前五个字段中没有明确定义:

0 6 * * Tue expr `date +\%W` \% 2 > /dev/null || /scripts/fortnightly.sh

Cron 提供了“/2”语法。只要在适当的时间单位字段后面加上“/2”,它就会“每隔一次”执行 cronjob。对你来说。

0 6 * * Tue/2

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

语法“/2”不适用于工作日。因此,我添加到智能上面是简单地使用1,15在星期一归档。

061,15 * */script/fornightly.sh >/dev/null 2 > & 1

也许有点傻,但是一个人也可以创造两个骗子,一个在第一个星期二,一个在第三个星期二。

第一个骗子:

0 0 8 ? 1/1 TUE#1 *

第二个骗局:

0 0 8 ? 1/1 TUE#3 *

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

回答我

修改您的星期二 cron 逻辑,以每隔一周执行 从那个时代开始

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

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

让开

日历算术真是令人沮丧。

@ xahtep 的回答很棒,但作为 @ 分身在评论中被注意到,它在某些年份会失败。date实用程序的“每年一周”说明符在这里都没有帮助。一月初的某个星期二将不可避免地重复上一年最后一个星期二的周平价: 2016-01-05(% V) ,2018-01-02(% U)和2019-01-01(% W)。

为什么不呢

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

这样合适吗?

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

0006 * * 2 # 2

我发现了上述方法的一些额外限制,这些限制在某些边缘情况下可能会失败:

@ xahtep 和@Duppelganger 讨论了在上述某些年份使用 %W的问题。

@ pilCrow 的回答在一定程度上绕过了这个问题,然而它也会在某些界限上失败。这个或其他相关主题的答案使用一天中的秒数(相对于一周) ,这也会因为相同的原因在某些界限上失败。

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

假设 GMT-2:

  • 当地时间上午1时 = 世界协调时下午11时
  • 当地时间晚上10点 = 世界协调时晚上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

如果你想根据一个给定的开始日期来做:

0 6 * * 1 expr \( `date +\%s` / 86400 - `date --date='2018-03-19' +\%s` / 86400 \) \% 14 == 0 > /dev/null && /scripts/fortnightly.sh

从2018年3月19日开始每隔一个周一开火

表达式如下: 周一早上6点跑步,如果..。

1-得到今天的日期,以秒为单位,除以一天中的秒数,转换为天数

对开始日期执行相同的操作,将其转换为自纪元以来的天数

得到两者之间的差异

4除以14,然后检查余数

5-如果余数为零,则为两周周期

00/14

每个月的第14天都是凌晨零点

差不多

0 0 1-7,15-21 * 2

会发生在本月的第一和第三个星期二。

注意: 不要将它与 vixie cron (包含在 RedHat 和 SLES 发行版中)一起使用,因为它使用了一个或在一个月中的某一天和一个星期中的某一天之间的字段来代替和。

试试这个

0 0 1-7,15-21 * 2

这是每月运行2次,都在星期二和至少一个星期的间隔。

这里有很多好的答案。基于这些评论,我看到了相当多的困惑和沮丧。我这个答案的目的不仅是为了回答 OPs 问题 如何指示 cron 每隔一周执行一次作业?,而且也为将来可能读到这个问题的人们澄清一些困惑。

译者:
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
执行[命令]的时间
射程: 0-23 3: 执行[ command ]的月份
射程: 1-31
4: 执行[ command ]的月份
射程: 1-12
5: 执行[ command ]的星期几
范围: 检查您的手册页,可能是0-6,1-7或0-7
6: 命令执行
注意,还有@value,我在这里不讨论它。

有些版本的 * nix 允许使用表达式:
范围内的每个偶数
范围内的每个奇数
1,15 = 第一和第十五名
2-8 = 第二至第八年(包括在内)

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

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

对于每周的天数,*/2将每天运行一次——查看您的手册页,看看每周的天数是如何编号的。Tue/2不是有效的表达式。

人类可读(日历)灾难
你需要知道的一些常量:
一年有365.2464个 几天(为了方便,我们将四舍五入到365)。
一年有12个
一周有7个 几天
一天有86,400个 几秒钟

所以,
一个月就是四周半
[即3个月为13星期]
一年是52周半

日历数学的祸害:
半月 = 每半个月 = 2x/月 = 每年24次。
双周 = 每隔一周(两周) = 每年26次。
注意: 这些术语经常被误用。
有些年份有51周,有些年份有53周,大多数年份有52周。如果 My cron 每隔一周运行一次(date +%W mod 2) ,并且一年有51或53周,那么它也将运行下一周,即新年的第一周。相反,如果我的 cron 每个偶数周运行一次,它将跳过2周。不是我想要的。

CRON 可以支持半月,它不能支持双周! 半月:
每个月的第一天总是在第一天和第七天之间。一周的后半段总是发生在15号和21号之间。
半月有两个值,一个在上半月,另一个在下半月。例如:
2,16

Unix 时间
在非常高的级别上,* nix time 是2个值:
date +%s = 自纪元以来的秒数(01/01/197000:00:00:00)
date +%N = 小数秒(以纳秒为单位)
因此,* nix 中的时间是 date +%s.%N

尼克斯利用时间纪元 * 。Etc/shadow 文件包含上次更改密码的日期。它是% s 的整数部分除以86,400。

法提诺
GPS 卫星测量的时间为“自纪元以来的周数”和“(小数)秒到一周的时间”。
注: 纪元周与年份无关。一年有51周、52周或53周并不重要。历史性的一周永远不会结束。

双周时间算法
在 * Nix 日期 +% W 是 的周数,而不是新纪元周。* nix 没有 epoch week 值,但是可以计算它。
纪元周 = 整数(纪元秒/秒/天/天/周)
每两周 = 新纪元 _ 每周模数2
Fortnight 值总是0或1,因此,当每个偶数周运行 Fortnight = 0和每个奇数周运行1 = 时,运行该命令。

每两周计算一次)
第一种方式(公元前) :
date +"%s / 604800 % 2" | bc
这会产生「[纪元秒]/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个文件(Time Entry)将是:
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\>

希望这能澄清一些疑惑