Post

如何使用Cron实现每7分钟执行一次脚本

如何使用Cron实现每7分钟执行一次脚本

V2EX上,有一则关于如何使用cron让脚本每7分钟执行一次的讨论。这次讨论涵盖了各种方法的优劣,并最终得出了几种可靠的解决方案。以下是对这次讨论的总结。

讨论背景

楼主在V2EX上发帖询问如何使用cron设置一个任务,使其每7分钟执行一次脚本。虽然这个问题看似简单,但实际实现中有一些细节和潜在的问题需要注意。

初步方案:*/7 方法

大部分用户最先想到的方法是使用*/7,即:

1
*/7 * * * * /path/to/your_script.sh

这种方法表示在每小时内的每7分钟执行一次。然而,这种方法并不完全可靠,因为它基于每小时的分钟数,因此只会在每小时的第0、7、14、21、28、35、42、49、56分钟执行,而不是从脚本启动的那一刻开始严格的每7分钟。

讨论要点

使用 sleep 命令

有用户建议在脚本内使用sleep命令,例如:

1
2
3
4
while true; do
  /path/to/your_script.sh
  sleep 420
done

然而,这种方法也不够准确。原因是sleep命令的等待时间加上脚本运行时间会导致累积误差,时间长了会有较大的偏差【17†source】【18†source】。

使用 at 命令

另一种方法是使用at命令,每次脚本执行后调度下一次运行:

1
echo "/path/to/your_script.sh" | at now + 7 minutes

尽管这方法在一定程度上解决了累积误差的问题,但仍然不是最佳方案,因为需要在脚本中嵌套at命令,复杂度较高。

UNIX 时间戳取模法

这是大多数用户推荐的方法。每分钟执行一次任务,通过date +\%s获取当前时间的UNIX时间戳,然后用60取模,再对7取模来判断是否应该执行脚本。具体实现如下:

1
* * * * * [ $(($(date +\%s) / 60 \% 7)) -eq 0 ] && /path/to/your_script.sh

这种方法每分钟执行一次,通过UNIX时间戳的计算确保脚本每7分钟运行一次,既简单又准确。

穷举法

另一种可靠但繁琐的方法是列出所有可能的7分钟间隔,例如:

1
2
3
4
5
6
7
0,7,14,21,28,35,42,49,56 * * * * /path/to/your_script.sh
1,8,15,22,29,36,43,50,57 * * * * /path/to/your_script.sh
2,9,16,23,30,37,44,51,58 * * * * /path/to/your_script.sh
3,10,17,24,31,38,45,52,59 * * * * /path/to/your_script.sh
4,11,18,25,32,39,46,53 * * * * /path/to/your_script.sh
5,12,19,26,33,40,47,54 * * * * /path/to/your_script.sh
6,13,20,27,34,41,48,55 * * * * /path/to/your_script.sh

这种方法直接在crontab中写入多个任务,确保脚本每7分钟执行一次。虽然可靠,但维护起来较为繁琐。

总结

在这次讨论中,最可靠和简洁的方法是使用UNIX时间戳取模法。*/7方法由于其固有的局限性并不可靠,而sleep命令和at命令也有各自的问题。穷举法虽然可靠但过于繁琐。因此,推荐使用UNIX时间戳取模法来实现每7分钟执行一次脚本的需求。

1
* * * * * [ $(($(date +\%s) / 60 \% 7)) -eq 0 ] && /path/to/your_script.sh

这个解决方案不仅实现了准确的时间间隔,还保持了实现的简洁性,是处理这种定时任务的最佳实践。


附上ChatGPT对这条cron的解释:

This cron job runs every minute and executes a specified command only if a certain condition related to the current time is met. Let’s break it down step by step:

Cron Schedule

* * * * * means the cron job runs every minute.

Conditional Execution

[ $(($(date +\%s) / 60 \% 7 )) -eq 0 ] && ( command ) is a condition followed by the command to be executed if the condition is true.

Explanation of the Condition

  1. date +\%s: This returns the current time in seconds since the Unix epoch (January 1, 1970). The backslash before % is to escape the percent sign in the cron environment.

  2. $(($(date +\%s) / 60 \% 7 )): This expression performs a series of calculations on the current time in seconds:
    • date +\%s: Fetch the current time in seconds.
    • / 60: Divide the current time in seconds by 60 to convert it to minutes.
    • \% 7: Take the remainder of the division by 7 (modulus operation).
  3. [ ... -eq 0 ]: Checks if the result of the above calculation is equal to 0.

Putting it All Together

  • The cron job runs every minute.
  • Each minute, it checks if the current time in minutes (since the Unix epoch) modulo 7 is equal to 0.
  • If the condition is true (i.e., the remainder is 0), it executes the command.
  • If the condition is false (i.e., the remainder is not 0), it does nothing.

Example

Let’s illustrate with an example:

  • Suppose the current Unix time in seconds is 1656361200 (which corresponds to a specific date and time).
  • Converting this to minutes: 1656361200 / 60 = 27606020 minutes.
  • Calculating the modulus: 27606020 % 7 = 0.
  • Since the result is 0, the condition is true, and the command is executed.

Summary

This cron job ensures that the specified command runs every 7 minutes, starting from a specific point in time (the Unix epoch). The condition ensures that the command is executed only when the number of minutes since the Unix epoch is a multiple of 7.

This post is licensed under CC BY 4.0 by the author.