如何在 Linux 中使用 cron

没有时间执行命令? 使用 cron 安排任务意味着程序可以运行,而您无需熬夜。
806 位读者喜欢这篇文章。
Top 5 Linux pain points in 2017

Internet Archive Book Images。 由 Opensource.com 修改。 CC BY-SA 4.0

作为系统管理员,面临的挑战之一(也是众多优势之一)是在您想睡觉的时候运行任务。例如,某些任务(包括定期重复的任务)需要在夜间或周末运行,此时预计没有人会使用计算机资源。我没有时间在晚上运行必须在非工作时间运行的命令和脚本。而且我不想在凌晨起床开始备份或主要更新。

相反,我使用两个服务实用程序,它们允许我在预定的时间运行命令、程序和任务。 cronat 服务使系统管理员能够安排任务在未来的特定时间运行。 at 服务指定在特定时间运行的一次性任务。 cron 服务可以按重复的方式安排任务,例如每天、每周或每月。

在本文中,我将介绍 cron 服务以及如何使用它。

常见(和不常见)的 cron 用途

我使用 cron 服务来安排显而易见的事情,例如每天凌晨 2 点发生的定期备份。 我也用它来做一些不太明显的事情。

  • 我许多计算机上的系统时间(即,操作系统时间)是使用网络时间协议 (NTP) 设置的。 虽然 NTP 设置了系统时间,但它没有设置可能漂移的硬件时间。 我使用 cron 根据系统时间设置硬件时间。
  • 我还有一个 Bash 程序,我每天早上早些时候运行它,在每台计算机上创建一个新的“每日消息”(MOTD)。 它包含诸如磁盘使用情况之类的信息,这些信息应该是最新的才有意义。
  • 许多系统进程和服务,例如 LogwatchlogrotateRootkit Hunter,使用 cron 服务来安排任务并每天运行程序。

crond 守护进程是启用 cron 功能的后台服务。

cron 服务检查 /var/spool/cron/etc/cron.d 目录以及 /etc/anacrontab 文件中的文件。 这些文件的内容定义了要在不同时间间隔运行的 cron 作业。 各个用户的 cron 文件位于 /var/spool/cron 中,系统服务和应用程序通常在 /etc/cron.d 目录中添加 cron 作业文件。 /etc/anacrontab 是一种特殊情况,将在本文后面介绍。

使用 crontab

cron 实用程序基于 cron 表 (crontab) 中指定的命令运行。 每个用户(包括 root)都可以拥有一个 cron 文件。 默认情况下,这些文件不存在,但可以使用 crontab -e 命令在 /var/spool/cron 目录中创建,该命令也用于编辑 cron 文件(请参阅下面的脚本)。 我强烈建议您不要使用标准编辑器(例如 Vi、Vim、Emacs、Nano 或任何其他可用的编辑器)。 使用 crontab 命令不仅允许您编辑命令,还会在您保存并退出编辑器时重新启动 crond 守护程序。 crontab 命令使用 Vi 作为其底层编辑器,因为 Vi 始终存在(即使在最基本的安装中也是如此)。

新的 cron 文件是空的,因此必须从头开始添加命令。 我将下面的作业定义示例添加到我自己的 cron 文件中,仅作为快速参考,以便我知道命令的各个部分意味着什么。 随意复制它供您自己使用。

# crontab -e
SHELL=/bin/bash
MAILTO=root@example.com
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed

# backup using the rsbu program to the internal 4TB HDD and then 4TB external
01 01 * * * /usr/local/bin/rsbu -vbd1 ; /usr/local/bin/rsbu -vbd2

# Set the hardware clock to keep it in sync with the more accurate system clock
03 05 * * * /sbin/hwclock --systohc

# Perform monthly updates on the first of the month
# 25 04 1 * * /usr/bin/dnf -y update

crontab 命令用于查看或编辑 cron 文件。

上面的代码中的前三行设置了默认环境。 必须将环境设置为给定用户所需的任何环境,因为 cron 不提供任何类型的环境。 SHELL 变量指定执行命令时要使用的 shell。 此示例指定 Bash shell。 MAILTO 变量设置将发送 cron 作业结果的电子邮件地址。 这些电子邮件可以提供 cron 作业的状态(备份、更新等),并且包含如果您从命令行手动运行程序时会看到的输出。 第三行设置环境的 PATH。 即使此处设置了路径,我也始终将完全限定的路径附加到每个可执行文件。

上面的示例中有几个注释行详细说明了定义 cron 作业所需的语法。 我将分解这些命令,然后添加更多命令来向您展示 crontab 文件的一些更高级的功能。

01 01 * * * /usr/local/bin/rsbu -vbd1 ; /usr/local/bin/rsbu -vbd2

我的 /etc/crontab 中的这行运行一个脚本,该脚本执行我的系统的备份。

这行运行我自己的 Bash shell 脚本 rsbu,它备份我的所有系统。 此作业在每天凌晨 1:01 (01 01) 开始。 时间规范的第三、四和五位中的星号 (*) 就像文件全局变量或通配符,用于其他时间划分; 它们指定“每月每一天”、“每个月”和“每周每一天”。 这行运行我的备份两次; 一个备份到内部专用备份硬盘驱动器,另一个备份到我可以带到保险箱的外部 USB 驱动器。

以下行使用系统时钟作为准确时间的源来设置计算机上的硬件时钟。 此行设置为每天凌晨 5:03 (03 05) 运行。

03 05 * * * /sbin/hwclock --systohc

这行使用系统时间作为源来设置硬件时钟。

我曾经使用第三个也是最后一个 cron 作业(已注释掉)在每个月的第一天凌晨 04:25 执行 dnfyum 更新,但我将其注释掉,因此它不再运行。

# 25 04 1 * * /usr/bin/dnf -y update

这行曾经执行每月更新,但我已将其注释掉。

其他调度技巧

现在让我们做一些比这些基本操作更有趣的事情。 假设您想在每个星期四下午 3 点运行一个特定的作业。

00 15 * * Thu /usr/local/bin/mycronjob.sh

这行在每个星期四下午 3 点运行 mycronjob.sh

或者,您可能需要在每个季度结束后运行季度报告。 cron 服务没有“当月的最后一天”的选项,因此您可以改用下个月的第一天,如下所示。(这假设报告所需的数据将在作业设置为运行时准备就绪。)

02 03 1 1,4,7,10 * /usr/local/bin/reports.sh

此 cron 作业在季度结束后下个月的第一天运行季度报告。

以下显示了一个作业,该作业在上午 9:01 到下午 5:01 之间的每个小时的 1 分钟后运行。

01 09-17 * * * /usr/local/bin/hourlyreminder.sh

有时您想在正常工作时间的固定时间运行作业。

我遇到过需要每两、三或四个小时运行一次作业的情况。 可以通过将小时数除以所需的间隔来完成,例如 */3 表示每三个小时,或 6-18/3 表示在早上 6 点到下午 6 点之间每三个小时运行一次。 可以类似地划分其他间隔; 例如,分钟位置中的表达式 */15 表示“每 15 分钟运行一次作业”。

*/5 08-18/2 * * * /usr/local/bin/mycronjob.sh

此 cron 作业在上午 8 点到下午 5:58 之间的每个小时内每五分钟运行一次。

需要注意的一点是:除法表达式的结果必须为零,作业才能运行。 这就是为什么,在本例中,作业设置为每五分钟(08:05、08:10、08:15 等)在从上午 8 点到下午 6 点的偶数小时内运行,但在任何奇数小时内都不会运行。 例如,作业根本不会在晚上 9 点到早上 9:59 之间运行。

我相信您可以根据这些示例提出许多其他可能性。

限制 cron 访问

具有 cron 访问权限的普通用户可能会犯错误,例如,这可能会导致系统资源(如内存和 CPU 时间)被淹没。 为了防止可能的滥用,系统管理员可以通过创建一个包含具有创建 cron 作业权限的所有用户的列表的 /etc/cron.allow 文件来限制用户访问权限。 无法阻止 root 用户使用 cron。

通过阻止非 root 用户创建自己的 cron 作业,可能需要 root 将其 cron 作业添加到 root crontab。 “但是等等!” 你说。 “那不会以 root 身份运行这些作业吗?” 不一定。 在本文的第一个示例中,注释中显示的用户名字段可用于指定作业运行时要使用的用户 ID。 这可以防止指定的非 root 用户的作业以 root 身份运行。 以下示例显示了一个作业定义,该作业以用户“student”身份运行作业

04 07 * * * student /usr/local/bin/mycronjob.sh

如果未指定用户,则作业将以拥有 crontab 文件的用户身份运行,在本例中为 root。

cron.d

/etc/cron.d 目录是某些应用程序(例如 SpamAssassinsysstat)安装 cron 文件的地方。 因为没有 spamassassin 或 sysstat 用户,这些程序需要一个找到 cron 文件的地方,因此它们被放置在 /etc/cron.d 中。

下面的 /etc/cron.d/sysstat 文件包含与系统活动报告 (SAR) 相关的 cron 作业。 这些 cron 文件与用户 cron 文件具有相同的格式。

# Run system activity accounting tool every 10 minutes
*/10 * * * * root /usr/lib64/sa/sa1 1 1
# Generate a daily summary of process accounting at 23:53
53 23 * * * root /usr/lib64/sa/sa2 -A

sysstat 软件包安装 /etc/cron.d/sysstat cron 文件,以运行 SAR 的相关程序。

sysstat cron 文件包含两行代码来执行任务。第一行每 10 分钟运行一次 sa1 程序,以收集存储在 /var/log/sa 目录中的特殊二进制文件中的数据。然后,每天晚上 23:53,sa2 程序运行以创建每日摘要。

调度技巧

我在 crontab 文件中设置的某些时间看起来相当随机 - 并且在某种程度上它们确实是随机的。 尝试调度 cron 作业可能具有挑战性,尤其是在作业数量增加时。 我通常在每台计算机上只安排几个任务,这比我在一些生产和实验室环境中工作时的情况要简单得多。

我管理的一个系统每天晚上运行大约十几个 cron 作业,另外还有三四个在周末或每月的第一天运行。 这是一个挑战,因为如果太多作业同时运行 - 尤其是备份和编译 - 系统会耗尽 RAM 并几乎填满交换文件,这会导致系统颠簸,同时性能下降,所以什么也做不了。 我们增加了更多内存并改进了任务的调度方式。 我们还删除了一项编写得很差且使用了大量内存的任务。

crond 服务假设主机一直运行。 这意味着如果在计划运行 cron 作业期间计算机已关闭,则它们将不会运行,直到下次安排运行时才会运行。 如果它们是关键的 cron 作业,这可能会导致问题。 幸运的是,还有另一种选择可以定期运行作业:anacron

anacron

anacron 程序执行与 crond 相同的功能,但它添加了运行被跳过作业的能力,例如,如果计算机关闭或者在某个或多个周期内无法运行该作业。 这对于笔记本电脑和其他关闭或进入睡眠模式的计算机非常有用。

计算机一打开并启动,anacron 就会检查配置的作业是否错过了上次安排的运行。 如果是这样,这些作业会立即运行,但只运行一次(无论错过了多少个周期)。 例如,如果由于系统在您度假时关闭而导致每周作业未运行三周,则它会在您打开计算机后立即运行,但只运行一次,而不是三次。

anacron 程序提供了一些简单的选项来运行定期安排的任务。 只需将您的脚本安装到 /etc/cron.[hourly|daily|weekly|monthly] 目录中,具体取决于它们需要运行的频率。

这是如何工作的? 这个序列比最初看起来更简单。

  1. crond 服务运行 /etc/cron.d/0hourly 中指定的 cron 作业。
# Run the hourly jobs
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
01 * * * * root run-parts /etc/cron.hourly

/etc/cron.d/0hourly 的内容导致位于 /etc/cron.hourly 中的 shell 脚本运行。

  1. /etc/cron.d/0hourly 中指定的 cron 作业每小时运行一次 run-parts 程序。
  2. run-parts 程序运行位于 /etc/cron.hourly 目录中的所有脚本。
  3. /etc/cron.hourly 目录包含 0anacron 脚本,该脚本使用此处显示的 /etdc/anacrontab 配置文件运行 anacron 程序。
# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22
                                                                
#period in days   delay in minutes   job-identifier   command
1       5       cron.daily              nice run-parts /etc/cron.daily
7       25      cron.weekly             nice run-parts /etc/cron.weekly
@monthly 45     cron.monthly            nice run-parts /etc/cron.monthly

/etc/anacrontab 文件的内容在适当的时间运行 cron.[daily|weekly|monthly] 目录中的可执行文件。

  1. anacron 程序每天运行一次位于 /etc/cron.daily 中的程序; 它每周运行一次位于 /etc/cron.weekly 中的作业,每月运行一次位于 cron.monthly 中的作业。 请注意每行中指定的延迟时间,这有助于防止这些作业相互重叠和其他 cron 作业。

我没有将完整的 Bash 程序放在 cron.X 目录中,而是将它们安装在 /usr/local/bin 目录中,这使我可以轻松地从命令行运行它们。 然后我在适当的 cron 目录(例如 /etc/cron.daily)中添加一个符号链接。

anacron 程序并非旨在在特定时间运行程序。 相反,它旨在以指定的时间间隔开始运行程序,例如每天凌晨 3 点(请参阅上面脚本中的 START_HOURS_RANGE 行)、星期日(开始一周)和每月的第一天。 如果错过了一个或多个周期,anacron 将尽快运行错过的作业一次。

快捷方式

上面显示的 /etc/anacrontab 文件向我们展示了一个线索,说明我们如何可以使用快捷方式来表示一些特定的常见时间。 这些单字时间快捷方式可用于替换通常用于指定时间的五个字段。 @ 字符用于标识 cron 的快捷方式。 下面的列表摘自 crontab(5) 手册页,显示了快捷方式及其等效含义。

  • @reboot:重启后运行一次。
  • @yearly:每年运行一次,即 0 0 1 1 *
  • @annually:每年运行一次,即 0 0 1 1 *
  • @monthly:每月运行一次,即 0 0 1 * *
  • @weekly:每周运行一次,即 0 0 * * 0
  • @daily:每天运行一次,即 0 0 * * *
  • @hourly:每小时运行一次,即 0 * * * *

这些快捷方式可以在任何 crontab 文件中使用,例如 /etc/cron.d 中的文件。

更多关于设置限制

我使用这些方法中的大多数来安排任务在我的计算机上运行。 所有这些任务都是需要以 root 权限运行的任务。 在我的经验中,普通用户真正需要 cron 作业的情况很少见。 其中一个案例是一名开发人员用户,他需要在开发实验室中使用 cron 作业来启动每日编译。

限制非 root 用户访问 cron 功能非常重要。 但是,在某些情况下,用户需要设置任务以在预先指定的时间运行,而 cron 可以允许他们这样做。 许多用户不了解如何使用 cron 正确配置这些任务,并且他们会犯错误。 这些错误可能无害,但通常情况下,它们会导致问题。 通过设置导致用户与系统管理员交互的功能策略,单个 cron 作业不太可能干扰其他用户和其他系统功能。

可以限制可以分配给单个用户或组的总资源,但这是另一篇文章的内容。

有关更多信息,请参阅 croncrontabanacronanacrontabrun-parts 的手册页,它们都包含有关 cron 系统如何工作的优秀信息和描述。

本文最初发表于 2017 年 11 月,并已更新以包含其他信息。

接下来要阅读的内容
David Both
David Both 是开源软件和 GNU/Linux 的倡导者、培训师、作家和演讲者。 自 1996 年以来,他一直从事 Linux 和开源软件的工作,自 1969 年以来一直从事计算机工作。 他是“系统管理员的 Linux 哲学”的坚定支持者和传播者。

9 条评论

很棒的文章 David。 我在 .sh 文件中添加了两行代码,这些代码被添加到自定义日志文件中。 这样我就知道我的关键 cron 何时开始、结束以及完成任务需要多长时间。

很棒的文章,David。 一个小的更正。 `crontab -e` 将使用任何默认编辑器。 因此,如果您恰好喜欢 `nano`,例如,您可以将您的 `EDITOR` 环境变量设置为 nano。

Cron 绝对是一个好工具。 但是,如果您需要进行更高级的调度,那么 Apache Airflow 非常适合这种情况。

Airflow 比 Cron 有很多优势。 最重要的是:依赖关系(让任务在其他任务之后运行)、漂亮的基于网络的概述、自动故障恢复和集中式调度器。 缺点是您需要在服务器上设置调度器和一些其他集中式组件,并在您要运行内容的每台机器上设置一个 worker。

您绝对想使用 Cron 来做一些事情。 但是,如果您发现 Cron 对于您的用例来说太有限了,我建议您研究一下 Airflow。

虽然我喜欢命令行? 因为我是 Bash 脚本的 n00b,所以如果 cron 有一个前端 GUI,我可以随意摆弄,直到我的命令行 chops 上升到标准就好了。 但再说一遍,走那条路?...只会让我变得懒惰。 我想我必须以艰难的方式学习这一切!!! 哈哈! 不过文章很棒!

你好 David,
你写了一篇很棒的文章。 非常感谢。 我使用 @reboot crontab 条目。 使用 crontab 和 root。 我运行以下命令。

@reboot /bin/dofstrim.sh

我想每周一次且仅一次运行 fstrim 用于我的 SSD 驱动器。
dofstrim.sh 是一个每周运行一次“fstrim”程序的脚本,无论系统重启多少次。 我恰好有多个 Linux 系统共享一台计算机,并且每个系统都有一个带有该条目的 root crontab。 由于我可能会在一周内或每周多次从 Linux 跳转到 Linux,因此我的 dofstrim.sh 每周只运行一次 fstrim,无论我启动哪个 Linux 系统。 我使用所有 Linux 系统通用的一个分区,一个安装为 "/scratch" 的分区,以及出色的 Linux 命令行 "date" 程序。

dofstrim.sh 列表如下所示。

#!/bin/bash
# 运行 fstrim 每天一次或每周一次,而不是每次重启都运行一次
#
# 使用日期函数提取今天的日期编号或周编号
# 日期编号范围为 1..366,周编号为 1 到 53
#WEEKLY=0 #每天一次
WEEKLY=1 #每周一次
lockdir='/scratch/lock/'

if [[ WEEKLY -eq 1 ]]; then
dayno="$lockdir/dofstrim.weekno"
today=$(date +%V)
else
dayno=$lockdir/dofstrim.dayno
today=$(date +%j)
fi

prevval="000"

if [ -f "$dayno" ]
then
prevval=$(cat ${dayno} )
if [ x$prevval = x ];then
prevval="000"
fi
else
mkdir -p $lockdir
fi

if [ ${prevval} -ne ${today} ]
then
/sbin/fstrim -a
echo $today > $dayno
fi

我曾考虑使用 anacron,但随后 fstrim 会经常运行,因为每个 linux 的 anacron 都会有类似的条目。
“date”程序生成日期编号或周编号,具体取决于 +%V 或 +%j

如果使用日期程序,则很容易生成每月最后一天的报告。 使用 Linux 中的日期函数,如下所示

*/9 15 28-31 * * [ `date -d +'1 day' +\%d` -eq 1 ] && echo "明天是下个月的第一天 今天(现在)是 `date`" >> /root/message

从 28 日到 31 日,每天一次,执行日期函数。
如果 date +1day 的结果是当月的第一天,则今天一定是当月的最后一天。

为什么不使用 crontab 来启动像 Ansible playbook 这样的东西,而不是简单的 bash 脚本?现在这样更容易进行故障排除和管理。 :-)

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.