# The following lines create a batch that runs every 40 minutes i.e.
# runs on 0:00, 0:40, 02:00, 02:40 04:00 etc to 22:40
0,40 */2 * * * /path/to/your/command
# runs on 01:20, 03:20, etc to 23:20
20 1/2 * * * /path/to/your/command
# Combined: 0:00, 0:40, 01:20, 02:00, 02:40, 03:20, 04:00 etc.
#!/bin/bash
# seven-minute-job
# This batch will only run when 420 seconds (7 min) have passed
# since the file /tmp/lastrun was either created or updated
if [ ! -f /tmp/lastrun ] ; then
touch /tmp/lastrun
fi
if [ $(( $(date +%s) - $(date -r /tmp/lastrun +%s) )) -lt 420 ] ; then
# The minimum interval of 7 minutes between successive batches hasn't passed yet.
exit 0
fi
#### Start running your actual batch job below
/path/to/your/command
#### actual batch job is done, now update the time stamp
date > /tmp/lastrun
#EOF
然后你可以安全地(尝试)每分钟运行一次:
* * * * * /path/to/your/seven-minute-job
A different, but similar problem would to schedule a batch to run on the first Monday of every month (or the second Wednesday) etc. Simply schedule the batch to run every Monday and exit when date is neither between the 1st or 7th and the day of the week is not Monday.
#!/bin/bash
# first-monday-of-the-month-housekeeping-job
# exit if today is not a Monday (and prevent locale issues by using the day number)
if [ $(date +%u) != 1 ] ; then
exit 0
fi
# exit if today is not the first Monday
if [ $(date +%d) -gt 7 ] ; then
exit 0
fi
#### Start running your actual batch job below
/path/to/your/command
#EOF
Which you can then safely (attempt) to run every Monday:
If your needs are complex you might consider using a more advanced product that is designed to run complex schedules (distributed over multiple servers) and that supports triggers, job dependencies, error handling, retries and retry monitoring etc. The industry jargon would be "enterprise" job scheduling and/or "workload automation".
Adding my answer from here for completeness, and adding another potentially helpful resource:
The cron user has a different $PATH than you do:
A frequent problem users make with crontab entries is that they forget that cron runs in a different environment than they do as a logged-in user. For example, a user creates a program or script in his $HOME directory, and enters the following command to run it:
$ ./certbot ...
The command runs perfectly from his command line. The user then adds that command to his crontab, but finds this does not work:
*/10 * * * * ./certbot ....
The reason for the failure in this case is that ./ is a different location for the cron user than it is for the logged-in user. That is, the environment is different! The PATH is part of the environment, and it is usually different for the cron user. Complicating this issue is that the environment for cron is not the same for all *nix distributions, and there are multiple versions of cron
A simple solution to this particular problem is to give the cron user a complete path specification in the crontab entry:
0 22 * * * /path/to/certbot .....
What is the cron user's environment?
In some instances, we may need to know the complete environment specification for cron on our system (or we may just be curious). What is the environment for the cron user, and how is it different from ours? Further, we may need to know the environment for another cron user - root for example... what is the root user's environment using cron? One way to learn this is to ask cron to tell us:
Create a shell script in your home directory (~/) as follows (or with the editor of your choice):
$ nano ~/envtst.sh
Enter the following in the editor, after adjusting for your system/user:
#!/bin/sh
/bin/echo "env report follows for user "$USER >> /home/you/envtst.sh.out
/usr/bin/env >> /home/you/envtst.sh.out
/bin/echo "env report for user "$USER" concluded" >> /home/you/envtst.sh.out
/bin/echo " " >> /home/you/envtst.sh.out
Save the file, exit the editor and set file permissions as executable.
$ chmod a+rx ~/envtst.sh
Run the script you just created, and review the output in /home/you/envtst.sh.out. This output will show your current environment as the $USER you're logged in as:
$ ./envtst.sh $$ cat /home/you/envtst.sh.out
Open your crontab for editing:
$ crontab -e -u root
Enter the following line at the bottom of your crontab:
ANSWER: The output file /home/you/envtst.sh.out will contain a listing of the environment for the "root cron user". Once you know that, adjust your crontab entry accordingly.
I can't specify the schedule I need in my crontab entry:
The schedule entry for crontab is of course defined in man crontab, and you should read this. However, reading man crontab, and understanding the schedule are two different things. And trial-and-error on a schedule specification can become very tedious. Fortunately, there is a resource that can help: the crontab guru.. Enter your schedule specification, and it will explain the schedule in plain English language.
Finally, and at risk of being redundant with one of the other answers here, do not get trapped into thinking that you are limited to a single crontab entry because you have one job to schedule. You are free to use as many crontab entries as you need to get the schedule you need.
As @Seamus said, 99% of my crontab problems come from having different environmental variables or different PATH variables so crontab can't find the script and it fails silently. Therefore my solution was to write a wrapper script that would:
Sets the PATH variable to the same one I'm used to
Sets my PYTHONPATH to my custom one to include all of my common functions
Changes the DISPLAY AND DBUS variables so that gui apps like notify-send messages actually make it to the screen
Changes the current working directory to the same one as the script. (If applicable)
Launches the script with nice ionice -c3 to save on resources.
Logs the stdout and stderr of the script in the "logs" directory so that when something goes wrong I can actually find out what happened.
Now if I want to launch a script I just edit mycrontab with crontab -e to:
mm hh * * * /path-to.../cron.launcher.py script.name.sh
and it runs the script just as if I was trying to from the terminal without any aggravation. This was originally a BASH script, but it got hung up on arguments with spaces so I rewrote it all in python. It will even launch GUI apps into the userspace:
#!/usr/bin/python3
# Runtime directories:
SYSPATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
PYTHONPATH = ""
################################################################################
import os
import time
import sys
import subprocess
# Get username passed by crontab
user = os.environ['LOGNAME']
uid = subprocess.check_output(('id -u ' + user).split()).decode().strip()
gid = subprocess.check_output(('id -g ' + user).split()).decode().strip()
# Set Environmental Variables:
os.environ["PATH"] = SYSPATH
os.environ["PYTHONPATH"] = PYTHONPATH
os.environ["DISPLAY"] = ":0"
os.environ["XAUTHORITY"] = os.path.join('/home', user, '.Xauthority')
os.environ["DBUS_SESSION_BUS_ADDRESS"] = 'unix:path=' + os.path.join('/run/user', uid, 'bus')
os.environ["XDG_RUNTIME_DIR"] = os.path.join("/run/user", uid)
# Get args:
script = sys.argv[1]
args = sys.argv[2:]
name = os.path.basename(script)
basedir = os.path.dirname(sys.argv[0])
# Log dir
logdir = os.path.join(basedir, 'cron.logs')
log = os.path.join(logdir, name + '.' + str(int(time.time())) + '.log')
os.makedirs(logdir, exist_ok=True)
if not os.access(logdir, os.W_OK):
print("Can't write to log directory", logdir, file=sys.stderr)
sys.exit(1)
# If running as root, lock up any scripts so others can't edit them before root runs them.
if os.geteuid() == 0:
cron_log = os.path.join(logdir, 'cron.launcher.root.log')
for path in [script, sys.argv[0]] + list(filter(None, PYTHONPATH.split(':'))):
print("Setting permisisons for", path)
subprocess.run(('chown root -R ' + path).split(), stderr=subprocess.PIPE, check=False)
subprocess.run(('chmod og-w -R ' + path).split(), stderr=subprocess.PIPE, check=False)
else:
cron_log = os.path.join(logdir, 'cron.launcher.log')
cron_log = open(cron_log, 'a')
logger = lambda *args: cron_log.write(' '.join(map(str, args)) + '\n')
# Run the script
output = open(log, mode='w')
if os.path.exists(script):
os.chdir(os.path.dirname(script))
cmd = ['nice', 'ionice', '-c3'] + [script] + args
logger(time.strftime('\n%m-%d %H:%M', time.localtime()).lstrip('0'), user, "running:")
logger(cmd)
logger("in directory", os.getcwd(), "with log in", log)
start = time.time()
ret = subprocess.run(cmd, stdout=output, stderr=output, check=False)
# Cleanup
fmt = lambda num: ("{0:.3g}").format(num) if abs(num) < 1000 else str(int(num))
logger("Returned", ret.returncode, "after", fmt(time.time() - start), 'seconds')
output.close()
cron_log.close()
Make sure you double check your $PATH and $PYTHONPATH variables in case you need to edit them to be different.
如何解决所有与 crontab 相关的问题(Linux)
一、基本术语:
接下来,关于cron的教育:
系统上的每个用户都可能有自己的 crontab 文件。root 和用户 crontab 文件的位置取决于系统,但它们通常在
/var/spool/cron
.有一个系统范围的
/etc/crontab
文件,该/etc/cron.d
目录可能包含 crontab 片段,这些片段也由 cron 读取和操作。一些 Linux 发行版(例如 Red Hat)也有/etc/cron.{hourly,daily,weekly,monthly}
目录,其中的脚本将每小时/每天/每周/每月执行一次,具有 root 权限。root 总是可以使用 crontab 命令;可能会或可能不会授予普通用户访问权限。当您使用命令编辑 crontab 文件
crontab -e
并保存它时,crond 会检查它的基本有效性,但不保证您的 crontab 文件格式正确。有一个名为的文件cron.deny
,它将指定哪些用户不能使用 cron。文件位置取决于系统,cron.deny
可以删除,这将允许所有用户使用 cron。如果计算机未开机或 crond 守护程序未运行,并且命令运行的日期/时间已过,crond 将不会赶上并运行过去的查询。
crontab 详情,如何制定命令:
crontab 命令由一行表示。您不能用于
\
将命令扩展到多行。哈希 (#
) 符号表示注释,这意味着该行上的任何内容都将被 cron 忽略。忽略前导空格和空行。%
在命令中使用百分号 ()时要非常小心。除非它们被转义,否则它们将\%
被转换为换行符,并且第一个未转义后的所有内容%
都会传递给您在标准输入上的命令。crontab 文件有两种格式:
用户 crontab
系统范围
/etc/crontab
和/etc/cron.d
碎片请注意,后者需要用户名。该命令将作为指定用户运行。
该行的前 5 个字段表示应该运行命令的时间。您可以在时间规范中使用数字或适用的日期/月份名称。
,
) 用于指定列表,例如 1,4,6,8 表示在 1,4,6,8 运行。-
) 指定,并且可以与列表组合,例如 1-3,9-12 表示介于 1 和 3 之间,然后介于 9 和 12 之间。/
字符可用于引入一个步骤,例如 2/5,这意味着从 2 开始,然后每 5 个(2,7,12,17,22...)。他们不会结束。*
) 表示该字段的整个范围(例如0-59
分钟字段)。*/2
表示从相关字段的最小值开始,然后每 2 个,例如 0 表示分钟(0,2...58),1 表示月(1,3 ...11)等。调试 cron 命令
检查邮件!
默认情况下,cron 会将命令的任何输出邮寄给运行命令的用户。如果没有输出,则不会有邮件。如果您希望 cron 将邮件发送到不同的帐户,那么您可以在 crontab 文件中设置 MAILTO 环境变量,例如
自己捕获输出
您可以将 stdout 和 stderr 重定向到一个文件。捕获输出的确切语法可能因 shell cron 使用的内容而异。以下是将所有输出保存到文件的两个示例
/tmp/mycommand.log
:查看日志
Cron 通过系统日志记录其操作,系统日志(取决于您的设置)通常转到
/var/log/cron
或/var/log/syslog
。如果需要,您可以使用 eg 过滤 cron 语句
现在我们已经了解了 cron 的基础知识、文件的位置以及如何使用它们,让我们来看看一些常见的问题。
检查 cron 是否正在运行
如果 cron 没有运行,那么你的命令将不会被安排......
应该给你类似的东西
或者
如果不重启
或者
可能还有其他方法;使用您的发行版提供的内容。
cron 在受限环境中运行您的命令。
可用的环境变量可能非常有限。通常,您只会定义几个变量,例如
$LOGNAME
、$HOME
和$PATH
。特别要注意的
PATH
是 仅限于/bin:/usr/bin
. 绝大多数“我的 cron 脚本不工作”的问题都是由这个限制路径引起的。如果您的命令位于不同的位置,您可以通过以下几种方式解决此问题:提供命令的完整路径。
在 crontab 文件中提供合适的 PATH
如果您的命令需要其他环境变量,您也可以在 crontab 文件中定义它们。
cron 使用 cwd == $HOME 运行你的命令
无论你执行的程序位于文件系统的哪个位置,cron 运行时程序的当前工作目录将是用户的主目录。如果您在程序中访问文件,如果您使用相对路径,或者(最好)在任何地方都使用完全限定路径,则需要考虑到这一点,这样可以避免每个人的困惑。
我的 crontab 中的最后一个命令没有运行
Cron 通常要求命令以新行结束。编辑你的 crontab;转到包含最后一个命令的行的末尾并插入一个新行(按 enter)。
检查 crontab 格式
您不能将用户 crontab 格式的 crontab 用于 /etc/crontab 或 /etc/cron.d 中的片段,反之亦然。用户格式的 crontab 在一行的第 6 个位置不包含用户名,而系统格式的 crontab 包含用户名并以该用户身份运行命令。
我在 /etc/cron.{hourly,daily,weekly,monthly} 中放了一个文件,但它没有运行
#!/bin/sh
顶部)Cron 日期相关的错误
如果您的日期最近被用户或系统更新、时区或其他更改,那么 crontab 将开始表现不稳定并出现奇怪的错误,有时工作,有时不工作。这是 crontab 试图在时间从其下方改变时“做你想做的事”的尝试。更改小时后,“分钟”字段将失效。在这种情况下,只会接受星号。重新启动 cron 并在不连接到互联网的情况下重试(因此日期没有机会重置为其中一个时间服务器)。
又是百分号
为了强调有关百分号的建议,以下是 cron 对百分号的处理示例:
将创建包含 3 行的 ~/cron.out 文件
date
这在使用命令时特别麻烦。一定要避开百分号如何
sudo
在 cron 作业中使用以非root用户身份运行时,
将打开用户的 crontab,同时
将打开 root 用户的 crontab。不建议在 cron 作业中运行 sudo 命令,因此如果您尝试在用户的 cron 中运行 sudo 命令,请尝试将该命令移至 root 的 cron 并从命令中删除 sudo。
Debian Linux 及其衍生产品(Ubuntu、Mint 等)有一些特性可能会阻止您的 cron 作业执行;
/etc/cron.d
特别是,中的文件/etc/cron.{hourly,daily,weekly,monthly}
必须:最后一个经常伤害毫无戒心的用户;
whatever.sh
特别是这些名为、mycron.py
、testfile.pl
等文件夹之一中的任何脚本都不会被执行。根据我的经验,这个特殊点是迄今为止在 Debian 和衍生产品上不执行 cronjob 的最常见原因。
man cron
如有必要,请参阅更多详细信息。如果您的 cronjobs 停止工作,请检查您的密码是否已过期。因为一旦过期,所有 cron 作业都会停止。
将出现
/var/log/messages
类似于以下显示用户身份验证问题的消息:不常见和不规则的时间表
Cron 被认为是一个非常基本的调度程序,其语法不容易让管理员制定稍微不常见的计划。
考虑以下通常被解释为“
command
每 5 分钟运行一次”的作业:相对:
它 并不总是
command
每 7 分钟运行一次。请记住,该/字符可用于介绍一个步骤,但步骤不会超出系列的末尾,例如
*/7
从分钟开始每 7 分钟匹配一次,0-59
即 0、7、14、21、28、35、42、49, 56 但在一个小时和下一个小时之间,批次之间只有 4 分钟,在00:56
新系列开始于01:00
等 之后01:07
(并且批次不会在01:03
,01:10
等上运行01:17
)。该怎么办?
创建多个批次
而不是单个 cron 作业,而是创建多个批次,这些批次组合在一起产生所需的计划。
例如,每 40 分钟(00:00、00:40、01:20、02:00 等)运行一个批次,创建两个批次,一个在偶数小时运行两次,第二个仅在奇数小时运行:
减少批次的运行频率
与其每 7 分钟运行一次批处理(这是一个很难分解为多个批次的计划),不如每 10 分钟运行一次。
更频繁地启动批次(但要防止多个批次同时运行)
许多奇怪的调度是因为批处理运行时间增加/波动而演变的,然后批处理被安排了一些额外的安全裕度,以防止同一个批处理的后续运行重叠和并发运行。
相反,换个角度思考并创建一个 cronjob,它会在上一次运行尚未完成时优雅地失败,但会以其他方式运行。看到这个问答:
一旦之前的 /usr/local/bin/frequent_cron_job 运行完成,这几乎会立即开始新的运行。
更频繁地启动批次(但在条件不正确时优雅地退出)
由于 cron 语法有限,您可能决定将更复杂的条件和逻辑放在批处理作业本身中(或在现有批处理作业周围的包装脚本中)。这允许您利用您最喜欢的脚本语言的高级功能,注释您的代码,并防止 crontab 条目本身中难以阅读的结构。
在 bash 中,
seven-minute-job
然后看起来像这样:然后你可以安全地(尝试)每分钟运行一次:
A different, but similar problem would to schedule a batch to run on the first Monday of every month (or the second Wednesday) etc. Simply schedule the batch to run every Monday and exit when date is neither between the 1st or 7th and the day of the week is not Monday.
Which you can then safely (attempt) to run every Monday:
Don't use cron
If your needs are complex you might consider using a more advanced product that is designed to run complex schedules (distributed over multiple servers) and that supports triggers, job dependencies, error handling, retries and retry monitoring etc. The industry jargon would be "enterprise" job scheduling and/or "workload automation".
特定于 PHP
如果你有一些 cron 工作,比如:
如果预计会出现错误,它们会发送给您,但不会发送给您——检查一下。
PHP 默认情况下不会将错误发送到 STDOUT。@see https://bugs.php.net/bug.php?id=22839
要解决此问题,请在 cli 的 php.ini 或您的行中(或在您的 PHP 的 bash 包装器中)添加以下内容:
第一个设置将允许你有像'Memory oops'和第二个这样的致命错误 - 将它们全部重定向到 STDERR。只有在您睡得好之后,所有内容才会被发送到您的根目录的邮件中,而不仅仅是被记录下来。
Adding my answer from here for completeness, and adding another potentially helpful resource:
The
cron
user has a different$PATH
than you do:A frequent problem users make with
crontab
entries is that they forget thatcron
runs in a differentenvironment
than they do as a logged-in user. For example, a user creates a program or script in his$HOME
directory, and enters the following command to run it:The command runs perfectly from his command line. The user then adds that command to his
crontab
, but finds this does not work:The reason for the failure in this case is that
./
is a different location for thecron
user than it is for the logged-in user. That is, theenvironment
is different! The PATH is part of theenvironment
, and it is usually different for thecron
user. Complicating this issue is that theenvironment
forcron
is not the same for all *nix distributions, and there are multiple versions ofcron
A simple solution to this particular problem is to give the
cron
user a complete path specification in thecrontab
entry:What is the
cron
user'senvironment
?In some instances, we may need to know the complete
environment
specification forcron
on our system (or we may just be curious). What is theenvironment
for thecron
user, and how is it different from ours? Further, we may need to know theenvironment
for anothercron
user -root
for example... what is theroot
user'senvironment
usingcron
? One way to learn this is to askcron
to tell us:~/
) as follows (or with the editor of your choice):/home/you/envtst.sh.out
. This output will show your current environment as the$USER
you're logged in as:crontab
for editing:crontab
:ANSWER: The output file
/home/you/envtst.sh.out
will contain a listing of theenvironment
for the "root cron user". Once you know that, adjust yourcrontab
entry accordingly.I can't specify the schedule I need in my
crontab
entry:The schedule entry for
crontab
is of course defined inman crontab
, and you should read this. However, readingman crontab
, and understanding the schedule are two different things. And trial-and-error on a schedule specification can become very tedious. Fortunately, there is a resource that can help: the crontab guru.. Enter your schedule specification, and it will explain the schedule in plain English language.Finally, and at risk of being redundant with one of the other answers here, do not get trapped into thinking that you are limited to a single
crontab
entry because you have one job to schedule. You are free to use as manycrontab
entries as you need to get the schedule you need.As @Seamus said, 99% of my crontab problems come from having different environmental variables or different
PATH
variables so crontab can't find the script and it fails silently. Therefore my solution was to write a wrapper script that would:PATH
variable to the same one I'm used toPYTHONPATH
to my custom one to include all of my common functionsDISPLAY
ANDDBUS
variables so that gui apps like notify-send messages actually make it to the screennice ionice -c3
to save on resources.stdout
andstderr
of the script in the "logs" directory so that when something goes wrong I can actually find out what happened.Now if I want to launch a script I just edit mycrontab with crontab -e to:
and it runs the script just as if I was trying to from the terminal without any aggravation. This was originally a BASH script, but it got hung up on arguments with spaces so I rewrote it all in python. It will even launch GUI apps into the userspace:
Make sure you double check your
$PATH
and$PYTHONPATH
variables in case you need to edit them to be different.