我几天来一直在想这件事......(不要问我为什么要这样做!)
像这样添加两个作业怎么样?
00 06 current_day-31/3 * * job
00 18 next_day-31/3 * * job
答案1
正如@Rinzwind 所说,你应该运行一个脚本,每 12 小时检查一次是否该运行
0 0,6,12,18 * * * /path/to/script.sh
问题是,如果直接除以 36,它必须准确地在 00 和 12 运行,而且它会跳过一年的第一天。
该脚本允许为~00 和 ~36 小时选择任意值
#!/bin/bash
# Global variable with result from shouldItRun()
runCommand=0
# Run at this hours
runAt00=6
runAt36=18
# There's an issue when the year changes. Since ((365*24)/36) is
# not exact, so using the cron-only solution @JEL, it will run at
# 6PM on day 365 of first year, and at 6am on day 1 of second year
# So ...
# Year when the script started running
yearStart=2015
# Get number of days since the first year the script is running
yearCurrent=$((`date '+%Y'`-1))
daysAccumulatedUntilThisYear=0
for i in $(seq $yearStart $yearCurrent)
do
thisYearDays=$((`date -d ''$i'-12-31' '+%j'`+0))
daysAccumulatedUntilThisYear=$(($daysAccumulatedUntilThisYear+$thisYearDays));
echo '>> Year '$i' ('$thisYearDays') | Accumulated : '$daysAccumulatedUntilThisYear
done
function shouldItRun {
# Init to false
runCommand=0
#
hourOfMonth=$(($(($daysAccumulatedUntilThisYear+$1+0))*24))
hourOfDay=$(($2+0))
# POSIBLE VALUES 24,12,0
hoursLeft=$(( $hourOfMonth % 36 ))
# IF 24 hours left AND time = $runAt00:XX
if [ $hoursLeft -eq 24 ] && [ $hourOfDay -eq $runAt00 ]
then
echo '>>> DAY '$1' '$2':XX ('$hourOfMonth') : '$hoursLeft
runCommand=1
fi
# IF 12 hours left AND time = $runAt36:XX
if [ $hoursLeft -eq 12 ] && [ $hourOfDay -eq $runAt36 ]
then
echo '>>> DAY '$1' '$2':XX ('$hourOfMonth') : '$hoursLeft
runCommand=1
fi
}
# Example of today at this time
dayOfYear=`date '+%j'`
hourOfDay=`date '+%H'`
#echo '> '$dayOfYear
#echo '> '$hourOfDay
shouldItRun $dayOfYear $hourOfDay
if [ $runCommand -eq '1' ]
then
echo 'Run it now!!'
else
echo 'STOP : do not run it now!!'
fi
# Example with first 20 days of year, at 06: and 18:,
# but tested also at 00: and 12:
for i in {1..20}
do
shouldItRun $i 00
shouldItRun $i 06
shouldItRun $i 12
shouldItRun $i 18
done
答案2
单个日历年
使用两行 crontab。日期实用程序输出获取一年中的日期(日期 +\%j),bc 输出使用余数算法(模数运算符)将日期分配为 0、1 或 2。虽然配置可以是任意 36 小时分布,但一个示例配置是在 1 天的早上 6 点运行作业,在第二天的下午 6 点运行作业,即 2 天,而在 0 天不运行作业。
0 6 * * * jd=`/bin/date +\%j`; /usr/bin/test `/bin/echo "$jd"\%3 | /usr/bin/bc` -eq 1 && job
0 18 * * * jd=`/bin/date +\%j`; /usr/bin/test `/bin/echo "$jd"\%3 | /usr/bin/bc` -eq 2 && job
您可以在命令行上使用类似以下命令进行测试
jd=`/bin/date +\%j`; /usr/bin/test `/bin/echo "$jd"%3 | /usr/bin/bc` -eq 2 && echo 'yes'
将 jd 替换为一年中的目标日期,并将“-eq 2”中的 2 替换为 0 或 1,以查看给定日期的结果。例如,在 2016 年,一年中的 1-3 天和 364-366 天:
1%3 = 1 364%3 = 1
2%3 = 2 365%3 = 2
3%3 = 0 366%3 = 0
因此,使用 cron、日期和测试实用程序以及 bc(如上例所示),该作业将在 2016 年第 1 天和第 364 天的早上 6 点运行,在第 2 天和第 365 天的下午 6 点再次运行,并且不会在第 3 天和第 366 天运行。
修正版非闰年计算方法
一位慷慨的评论者和另一个答案揭示了上述答案对于除闰年(2016 年、2020 年)以外的每一年都不起作用。这是因为余数模式在非闰年(365 天年份)之后的年份边界上无法成立。因此,余数模式需要强制转换为 4 年模式,而不是一年模式。
针对 4 年模式的更丑陋的命令行测试是
jd=`/bin/date +\%j`;ya=`/bin/date +\%Y`;/usr/bin/test `/bin/echo if \("ya"%4\) 4-"ya"%4+"jd"%3 else "jd"%3 | /usr/bin/bc` -eq 0 && /bin/echo 'yes'
如果该作业每 36 小时运行一次(例如,如图所示,一天早上 6 点,第二天下午 6 点,然后后天早上 6 点),可以使用两个 crontab 行运行,如单个日历年示例中所示,即
0 6 * * * jd=`/bin/date +\%j`;ya=`/bin/date +\%Y`;/usr/bin/test `/bin/echo if \("ya"%4\) 4-"ya"%4+"jd"%3 else "jd"%3 | /usr/bin/bc` -eq 1 && job_to_run
0 18 * * * jd=`/bin/date +\%j`;ya=`/bin/date +\%Y`;/usr/bin/test `/bin/echo if \("ya"%4\) 4-"ya"%4+"jd"%3 else "jd"%3 | /usr/bin/bc` -eq 2 && job_to_run
有两点值得一提:
- 虽然这适用于我使用的 Ubuntu 版本上的普通 bc 版本,但 else 语句依赖于 bc 的非 POSIX 扩展。如果这些非 POSIX 扩展未内置在您的普通 bc 版本中,则有一种解决方法,但我懒得去弄清楚。由于这是“询问 Ubuntu”,因此我可以放心地假设您使用的是带有非 POSIX 扩展的普通 Ubuntu bc。
- 如果您的系统采用夏令时时区,您实际上需要 6 条 crontab 行,每行用于受夏令时影响的日期范围,以计算一小时的增减。可以使用 cron 字段指定日期范围。
叙述性解释
我假设您熟悉 crontab 字段的使用(否则总会有手册页)。如果在五个时间和日期字段后给出的测试表达式成功,则显示的两行将在上午 6 点和下午 6 点运行“作业”。测试表达式成功如下所示。
我将解释更复杂的测试表达式,因为更简单的表达式是更复杂表达式的一部分。
- 日期实用程序用于将一年中的天数(1-366)和年份(例如,2016)填充到变量“jd”和“ya”中:
jd=`/bin/date +\%j`;ya=`/bin/date +\%Y`
接下来测试 bc 方程的输出(使用 /usr/bin/test)。如果 bc 计算的结果等于 1(“-eq 1”),则作业在早上 6 点运行。如果 bc 计算的结果等于 2,则作业在下午 6 点运行。如果 bc 计算的结果等于任何其他数字(包括 0),则不运行作业。
然后使用 echo 实用程序将 bc 方程传送到 bc 实用程序中。
bc 等式中首先是“if”语句。如果 bc 求值“if”后面括号中的等式的结果为非零,则求值
("ya"%4)
下一个等式;否则,求值“else”后面的等式。4-"ya"%4+"jd"%3
"jd"%3
在所有情况下,“ya”都替换为当前年份数字,“jd”都替换为当前日期数字。
对于“if”条件,结果是将年份除以 4 后的余数。对于 2016 年,余数为 0,因此计算“else”方程。对于 2017 年,余数为 1,对于 2018 年,余数为 2,对于 2019 年,余数为 3。因此,对于 2017-2019 年(但不是 2016 年或 2020 年),
4-"ya"%4+"jd"%3
将计算该方程。对于 2017 年,4-2017%4 的结果为 3(4-1);对于 2018 年,结果为 2,对于 2019 年,结果为 1。将该结果添加到日期数,然后将日期数除以 3,以获得余数,用于测试是否应运行该作业。2017 年至 2019 年第 1-3 天和第 363-365 天的 bc 计算测试结果为 1,2,0 的模式如下所示:
2017 2018 2019 3 + 1% 3 = 1 2 + 1% 3 = 0 1 + 1% 3 = 2 3 + 2 % 3 = 2 2 + 2 % 3 = 1 1 + 2 % 3 = 0 3 + 3% 3 = 0 2 + 3% 3 = 2 1 + 3% 3 = 1 3 + 363 % 3 = 0 2 + 363 % 3 = 2 1 + 363 % 3 = 1 3 + 364 % 3 = 1 2 + 364 % 3 = 0 1 + 364 % 3 = 2 3 + 365 % 3 = 2 2 + 365 % 3 = 1 1 + 365 % 3 = 0
- 这表明 2017 年底的模式 0,1,2 在 2018 年初重复出现,同样,2,0,1 在 2018 年底和 2019 年初也重复出现。上面“单个日历年”下显示的模式表明,2016 年底和 2020 年初的模式 1,2,0 在 2017 年初和 2019 年底重复出现。