如何使用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
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.$(($(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).
[ ... -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.