如何每 36 小时运行一次 cron 作业?

如何每 36 小时运行一次 cron 作业?

我几天来一直在想这件事......(不要问我为什么要这样做!)

像这样添加两个作业怎么样?

    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

有两点值得一提:

  1. 虽然这适用于我使用的 Ubuntu 版本上的普通 bc 版本,但 else 语句依赖于 bc 的非 POSIX 扩展。如果这些非 POSIX 扩展未内置在您的普通 bc 版本中,则有一种解决方法,但我懒得去弄清楚。由于这是“询问 Ubuntu”,因此我可以放心地假设您使用的是带有非 POSIX 扩展的普通 Ubuntu bc。
  2. 如果您的系统采用夏令时时区,您实际上需要 6 条 crontab 行,每行用于受夏令时影响的日期范围,以计算一小时的增减。可以使用 cron 字段指定日期范围。

叙述性解释

我假设您熟悉 crontab 字段的使用(否则总会有手册页)。如果在五个时间和日期字段后给出的测试表达式成功,则显示的两行将在上午 6 点和下午 6 点运行“作业”。测试表达式成功如下所示。

我将解释更复杂的测试表达式,因为更简单的表达式是更复杂表达式的一部分。

  1. 日期实用程序用于将一年中的天数(1-366)和年份(例如,2016)填充到变量“jd”和“ya”中:
    jd=`/bin/date +\%j`;ya=`/bin/date +\%Y`
  1. 接下来测试 bc 方程的输出(使用 /usr/bin/test)。如果 bc 计算的结果等于 1(“-eq 1”),则作业在早上 6 点运行。如果 bc 计算的结果等于 2,则作业在下午 6 点运行。如果 bc 计算的结果等于任何其他数字(包括 0),则不运行作业。

  2. 然后使用 echo 实用程序将 bc 方程传送到 bc 实用程序中。

  3. bc 等式中首先是“if”语句。如果 bc 求值“if”后面括号中的等式的结果为非零,则求值("ya"%4)下一个等式;否则,求值“else”后面的等式。4-"ya"%4+"jd"%3"jd"%3

  4. 在所有情况下,“ya”都替换为当前年份数字,“jd”都替换为当前日期数字。

  5. 对于“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,以获得余数,用于测试是否应运行该作业。

  6. 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
  1. 这表明 2017 年底的模式 0,1,2 在 2018 年初重复出现,同样,2,0,1 在 2018 年底和 2019 年初也重复出现。上面“单个日历年”下显示的模式表明,2016 年底和 2020 年初的模式 1,2,0 在 2017 年初和 2019 年底重复出现。

相关内容