# Cron (简体中文)

From ArchWiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

相关文章

- systemd/Timers (简体中文)

翻译状态：本文是 Cron 的翻译。上次翻译日期：2021-02-27。如果英文版本有所更改，则您可以帮助同步翻译。

摘自 Wikipedia:

  cron 是一个在 Unix 及类似操作系统上执行计划任务的程序。cron 使用户能够安排工作(命令或shell脚本)在特定时间、日期或间隔定期运行，通常用于系统的自动化维护或者管理。

## 安装

cron 有多个实现程序，但是基础系统默认使用 systemd 的 Timers，下面的实现都不被默认安装。Gentoo Linux Cron 指南 提供了一个这些实现之间的比较。

可用的包:

- cronie
- fcron
- dcronAUR
- vixie-cronAUR
- scron-gitAUR

## 配置

### 激活及开机启动

安装后，默认的守护进程不会启动。安装的软件包通常提供了可以用 systemctl 控制的服务文件。例如 cronie 使用 `cronie.service`。

`/etc/cron.daily/` 和类似的目录包含当前的任务，启动 cron 服务时会触发所有这类任务。

注意： cronie 提供了 `0anacron` 任务，每小时执行一次，它允许延迟运行其他某些任务，比如因为未开机而延迟的任务。

### 处理任务中的错误

cron 会记录 stdout 和 stderr 的输出并尝试通过 `sendmail` 命令发送邮件给用户。如果 Cronie 未找到 `/usr/bin/sendmail`，则会禁用邮件通知。要发送邮件到用户的 spool，需要在系统上运行一个 smtp 守护进程，例如 opensmtpd。也可以安装提供 sendmail 命令的软件包，然后配置成通过外部邮件服务器发送邮件。或者使用 `-m` 选项将错误记录到日志并通过定制的脚本进行处理。

提示： 通过 Postfix/localmail 可以发送邮件到本地系统。

1. 编辑 `cronie.service` 服务。
2. 安装 esmtpAUR, msmtp, opensmtpd, sSMTP 或编写自定义脚本。

#### 使用 ssmtp 的例子

ssmtp 是一个仅包含发送功能的 sendmail 模拟器，可以从本地计算机向 smtp 服务器发送邮件。尽管目前已经没有活跃维护者，这个程序依然是发送邮件的最简单方式。不需要运行守护进程，配置可以简单到只需在一个配置文件中编辑三行即可（如果你的主机是受信任的，可以通过你的邮件服务提供商转发未经认证的邮件）。ssmtp 无法收取邮件、展开别名或管理队列。

安装 ssmtpAUR，安装时会创建链接 `/usr/bin/sendmail` 指向 `/usr/bin/ssmtp`。安装后编辑 `/etc/ssmtp/ssmtp.conf` 配置文件。详情请参考 ssmtp，到 `/usr/bin/sendmail` 的软链接可以确保 S-nail 等提供 `/usr/bin/mail` 的程序可以无需修改直接使用。

安装配置完成后重启 `cronie` 以确保 cronie 能够检测到新配置的 `/usr/bin/sendmail` 命令。

#### 使用 msmtp 的例子

安装 msmtp-mta, 安装时会创建链接 `/usr/bin/sendmail` 指向 `/usr/bin/msmtp`。重启 `cronie` 以确保 cronie 能够检测到新配置的 `/usr/bin/sendmail` 命令。你必须提供一种方法让 `msmtp` 能够将你的用户名转换成电子邮件地址。

然后要么在你的 crontab 中添加 `MAILTO` 行：

    MAILTO=your@email.com

要么 创建 `/etc/msmtprc` 文件，并添加这一行：

    aliases /etc/aliases

并创建 `/etc/aliases` 文件：

    your_username: your@email.com
    # 可选：
    default: your@email.com

然后修改 cronie 的配置，将cronie 守护进程的 `ExecStart` 命令替换为

    ExecStart=/usr/bin/crond -n -m '/usr/bin/msmtp -t'

#### 使用 esmtp 的例子

安装 esmtpAUR 和 procmail。

安装完成后，进行如下配置:

    /etc/esmtprc

    identity myself@myisp.com
           hostname mail.myisp.com:25
           username "myself"
           password "secret"
           starttls enabled
           default
    mda "/usr/bin/procmail -d %T"

Procmail 需要 root 权限才能在投递模式下工作，但如果你是以 root 身份运行 cronjobs，就不会有这个问题。

要测试一切是否正常工作，请创建一个内容为 `"test message"` 的 `message.txt` 文件。

在同一目录下运行：

    $ sendmail user_name < message.txt

紧接着：

    $ cat /var/spool/mail/user_name

现在您应该看到测试信息以及发送的时间和日期。

所有作业的错误输出现在会被重定向到 `/var/spool/mail/user_name`。

由于权限问题，很难创建和发送邮件给root用户（比如 `su -c ""`）。你可以要求 `esmtp` 将 root 的所有邮件转发给普通用户，方法是：

    /etc/esmtprc

    force_mda="user-name"

注意： 如果上述测试没有成功，您可以尝试在 `~/.esmtprc` 中创建一个具有相同内容的本地配置。

运行下面的命令以确保它有正确的权限：

    $ chmod 710 ~/.esmtprc

然后用 `message.txt` 文件重新运行一遍上面的测试。

#### 使用 opensmtpd 的例子

安装 opensmtpd。

编辑 `/etc/smtpd/smtpd.conf`。下面的配置允许本地投递：

    listen on localhost
    action "local" mbox alias <aliases>
    match for local action "local"

您现在可以进行测试。运行 `smtpd.service`，然后执行：

    $ echo test | sendmail user

user 可以用任何 能够处理 mbox 格式的邮件阅读器来检查邮件，或者直接查看 `/var/spool/mail/user` 文件。如果一切都符合预期，您可以启用 openmtpd 使其开机运行。

这种方法的好处是不需要向远程服务器发送本地 cron 通知。但缺点是需要运行一个新的守护进程。

注意：

- 在写这篇文章的时候，Arch 的 opensmtpd 包还没有在 `/var/spool/smtpd` 下创建好所有需要的目录，但是守护进程会在需要指定所有者和权限时发出警告。只需按照警告创建即可。
- 尽管上文的配置中程序并不会接受远程连接，但使用iptables或类似的方法来阻止端口 25 也是预防措施。

#### 运行时间很长的 cron 任务

假设 cron 调用了这个脚本：

    #!/bin/sh
    echo "我有一个可恢复错误！"
    sleep 1h

这时会发生以下事件：

1. cron 运行这个脚本
2. 一旦 cron 检查到脚本有输出，就会启动你的邮件传输程序，并向通过管道其传递一些必要的头部内容。因为脚本还没有结束，还会有更多的输出，管道就没有关闭。
3. 邮件传输程序打开到邮件服务器的链接，并等待后续的输出。
4. 邮件服务器会在特定时间后关掉这个空闲的链接，你会受到类似这样的错误postfix closes the idle connection after less than an hour and you get an error like this :

    smtpmsg='421 ... Error: timeout exceeded' errormsg='the server did not accept the mail' (服务器没有接受邮件)

要解决这个问题，你可以使用 moreutils 中的 chronic 或 sponge 命令。 它们各自的手册页面中写道：

chronic（时序）
  chronic 运行一个命令时，会缓存它的标准输出和标准错误输出，并只在命令失败（返回值非零或崩溃）的情况下才会一并输出。如果命令成功执行，任何无关的输出将被隐藏。
sponge（海绵）
  sponge 读取标准输入并将其写入指定的文件。不同于 shell 重定向，sponge 在打开输出文件之前会吸收所有的输入……如果没有指定输出文件，sponge 会输出到标准输出。

Chronic也会在打开标准输出之前缓冲命令输出。

## Crontab 格式

crontab 的基本格式是：

    分 时 日 月 星期 命令

- 分 值从 0 到 59。
- 时 值从 0 到 23。
- 日 值从 1 到 31。
- 月 值从 1 到 12。
- 星期 值从 0 到 6, 0 代表星期日。

空格用来分开字段，要微调你的时间表，也可以用下面特殊字符来设定范围:

符号 | 描述
--- | ---
* | 通配符，表示所有支持的时间值
, | 用逗号分隔多个时间
- | 连接两个数值，给出一个范围
/ | 指定一个周期或频率

例如，下面一行：

    */5 9-16 * 1-5,9-12 1-5 ~/bin/i_love_cron.sh

将会在周内从早上 9 点到下午 4 点 55 分，每隔 5 分钟执行一次脚本 `i_love_cron.sh`，夏季除外（6月、7月和8月）。

此外，crontab 还有一些特殊的关键字。

    @reboot 启动时
    @yearly 每年一次
    @annually ( 同 @yearly)
    @monthly 每月一次
    @weekly 每周一次
    @daily 每天一次
    @midnight (午夜，同 @daily)
    @hourly 每小时一次

例如：

    @reboot ~/bin/i_love_cron.sh

将在启动时执行脚本 `i_love_cron.sh`。

更多信息参见: https://www.adminschoice.com/crontab-quick-reference

更多的例子和高级配置技巧可以在下面找到。

## 基本命令

Crontabs 绝不应该被直接编辑；用户应该使用 crontab 程序来处理他们的 crontabs。为了能够访问这个命令，用户必须添加到 users 用户组（见 gpasswd 命令）。

要查看 crontabs，用户应该运行下面的命令：

    $ crontab -l

要编辑 crontabs，可以使用：

    $ crontab -e

注意： 默认情况下，`crontab` 命令使用 `vi` 编辑器。可以通过export `EDITOR` 或 `VISUAL` 来配置，或通过这样的命令直接指定编辑器：`EDITOR=vim crontab -e`。

要移除 crontabs, 可以使用：

    $ crontab -r

如果用户有一个保存好的 crontab 想要用它完全覆盖旧的 crontab，可以使用：

    $ crontab saved_crontab_filename

想从命令行(Wikipedia:stdin)覆盖一个 crontab，使用：

    $ crontab -

想编辑别的用户的 crontab, 使用root运行下面的命令:

    # crontab -u username -e

同一个格式（在命令后追加 `-u username`）也可以用来列出或删除 crontabs。

## 范例

下面的条目:

    01 * * * * /bin/echo Hello, world!

将会在每个月的每一天的每一个小时的第一分钟（例如，在12:01，1:01，2:01等）执行命令 `/bin/echo Hello, world!`

类似地,

    */5 * * jan mon-fri /bin/echo Hello, world!

将会在一月的每个工作日每五分钟（例如，在12：00，12：05，12：10等）执行一次相同的命令。

和前文 Crontab 格式一章相同，这一行

    *0,*5 9-16 * 1-5,9-12 1-5 /home/user/bin/i_love_cron.sh

将会在周内从早上 9 点到下午 4 点 55 分，每隔 5 分钟执行一次脚本 `i_love_cron.sh`，夏季除外（6月、7月和8月）。

也可以像这样输入周期性设置：

    # Chronological table of program loadings
    # Edit with "crontab" for proper functionality, "man 5 crontab" for formatting
    # User: johndoe

    # mm  hh  DD  MM  W /path/progam [--option]...  ( W = weekday: 0-6 [Sun=0] )
      21  01  *   *   * /usr/bin/systemctl hibernate
      @weekly           $HOME/.local/bin/trash-empty

下面是一些不言自明的crontab语法例子：

    30 4 echo "四点半了。"
    0 22 echo "晚上十点了。"
    30 15 25 12 echo "现在是圣诞节下午三点半。"
    30 3 * * * echo "每天早上三点半提醒我。"
    0 * * * * echo "新的一个小时到来了。"
    0 6 1,15 * * echo "每月1号和15号的早上六点。"
    0 6 * * 2,3,5 echo "周二三四的早上六点。"
    59 23 * * 1-5 echo "周内每天的最后一分钟。"
    0 */2 * * * echo "每两个小时。"
    0 20 * * 4 echo "周四的晚上八点。"
    0 20 * * Thu echo "周四的晚上八点。"
    */15 9-17 * * 2-5 echo "周内朝九晚五的每一刻钟。"
    @yearly echo "新年好！"

## 默认编辑器

要修改默认编辑器，请在 shell 初始化脚本中定义 `EDITOR` 环境变量，如环境变量所述。

作为普通用户，需要使用 `su` 代替 `sudo` 来正确拉取环境变量：

    $ su -c "crontab -e"

如果希望给这个命令取别名，因为 su 会在一个新启动的子 shell 中启动，为了防止一些以外的发生，需要用 `printf` 加一个任意字符串，来提醒你仍然在 root 下运行：

    alias scron="su -c $(printf "%q " "crontab -e")"

## 运行基于 X.org 的应用程序

Cron 不在 X.org 下运行，因此它无法知道启动 X.org 应用程序所需的环境变量，因此必须显式定义。我们可以使用类似 xuserrun-gitAUR 这样的程序来完成：

    17 02 * ... /usr/bin/xuserrun /usr/bin/xclock

或者可以手动定义它们（`echo $DISPLAY` 将给出当前的 DISPLAY 环境变量值）：

    17 02 * ... env DISPLAY=:0 /usr/bin/xclock

如果需要在 cron 中运行 notify-send 进行桌面通知，因为 notify-send 通过 dbus 发送值。需要告诉 dbus 连接到正确的总线。 通过检查 DBUS_SESSION_BUS_ADDRESS 环境变量，并设置为相同的值，就可以找到地址。因此：

    17 02 * ... env DBUS_SESSION_BUS_ADDRESS=your-address notify-send 'Foo bar'

如果是通过SSH完成的，需要给与权限：

    # xhost +si:localuser:$(whoami)

## 异步任务处理

如果你经常关机，但又不想错过任务的执行，这里有一些解决方案（从最简单到最难）：

### Cronie

cronie 内含 anacron。其项目主页介绍道：

Cronie 包含了标准的 UNIX 守护进程 crond，它可以在预定的时间运行指定的程序和相关工具。 它基于最初的 cron，并增强了安全性和配置功能，比如可以使用 pam 和 SELinux。

### Dcron

Vanilla dcronAUR 支持异步任务处理。只要用@hourly、@daily、@weekly或者@monthly加上任务名就可以了：

    @hourly         ID=greatest_ever_job      echo This job is very useful.

### Cronwhip

cronwhipAUR 是一个自动运行遗漏的 cron 任务的脚本。它与以前的默认 cron 实现 dcron 一起工作。 另见这个论坛帖子。

### Anacron

Anacron 是 dcron 的完全替代者，它可以异步处理任务。

它由 cronie 提供，通过 `/etc/anacrontab` 进行配置。关于格式的信息可以在 `anacrontab(5)` man page 中找到。运行 `anacron -T` 可以测试 `/etc/anacrontab` 的有效性。

### Fcron

和anacron一样，fcron 假设计算机并不总是在运行。但与anacron不同的是，它可以在比一天更短的时间内安排这些事件，这对于经常暂停/休眠的系统（例如笔记本电脑）可能很有用。和 cronwhip 一样， fcron 也可以运行那些本应在计算机停机期间运行的作业。

用 fcron 取代 cronie 时，spool 目录会变为 `/var/spool/fcron`，并使用 `fcrontab` 命令代替 crontab 来编辑用户的 crontabs。这些 crontab 以二进制格式存储，其旁边的文本版本在 spool 目录中为 foo.orig。由于这种行为上的差异，任何手动编辑的用户 crontabs 可能需要进行调整。

一个快速的脚本小程序，可以帮助您将传统的用户 crontabs 转换为 fcron 格式：

    cd /var/spool/cron && (
     for ctab in *; do
      fcrontab ${ctab} -u ${ctab}
     done
    )

另见这个论坛主题。

## 确保排他性

如果您有可能运行很久的任务（比如说变化很多或者网速突然变慢，备份可能会偶尔运行很长时间），那么 `flock`（util-linux）可以确保 cron 任务在同一时间点只有一个运行。

      5,35 * * * * /usr/bin/flock -n /tmp/lock.backup /root/make-backup.sh

## Cronie

cronie 的相关文件层次结构如下：

       /etc/
         |----- cron.d/
                  | ----- 0hourly
         |----- cron.minutely/
         |----- cron.hourly/
                  | ----- 0anacron
         |----- anacrontab
         |----- cron.daily/
         |----- cron.monthly/
         |----- cron.weekly/
         |----- crontab
         |----- cron.deny

Cronie 提供了 cron 和 anacron 两种功能：只要系统在指定的时间可用，cron 以固定的时间间隔（粒度为一分钟）运行工作，，而anacron则在以天为单位指定的时间间隔执行命令。与 cron 不同的是，它并不假设系统连续运行。当系统启动时，anacron 就会检查是否有任何漏掉的任务，并进行相应的处理。

cron任务可以在 `/etc/cron.d` 目录中类似 crontab 的文件中定义，或者在 `/etc/crontab` 文件中添加。注意后者在默认情况下并不存在，但如果存在就会被使用。按照 `/etc/cron.d/0hourly` 的内容，`/etc/cron.hourly` 中的任何可执行文件将每小时运行一次（默认为每小时的第1分钟），而在 `/etc/cron.minutely` 中的可执行文件将每分钟执行一次。这些可执行文件通常是 shell 脚本，也可以使用可执行文件的符号链接。 `/etc/cron.deny` 文件包括不允许使用 crontab 的用户列表，如果没有这个文件，只有 `/etc/cron.allow` 中列出的用户才能使用它。

Anacron的工作原理类似，通过执行根据所需的作业频率放置在 `/etc/cron.daily`、`/etc/cron.weekly` 和 `/etc/cron.monthly`目录下的文件，cron 作业 `/etc/cron.hourly/0anacron` 确保 anacron 每天运行一次，以执行其待办任务。

注意：

- Cronie 使用 `run-parts` 来执行不同目录下的脚本。文件名中不应该包含任何点号（.），因为 `run-parts` 在默认模式下会忽略它们（参见run-parts(8)）。名字必须只由大小写字母、数字、下划线和减号组成。
- `systemctl status cronie` 的输出可能会显示诸如 `CAN'T OPEN (/etc/crontab): No such file or directory` 的内容。您可以忽略这些内容，因为这个文件 cronie 不是必须的。
- Cronie 对 `/etc/cron.d/0hourly` 的权限要求很严格。如果 `/etc/cron.d/{hourly,weekly,daily}...` 中的任务损坏或权限不正确，`/etc/cron.d/0hourly` 中的所有任务都不会被运行（包括 anacron 启动器）。`pacman -Qkk cronie` 可以显示是否有这样的问题。

提示： 如果不需要某些命令的输出和电子邮件提醒，请在该 cron 任务的行末添加 `>/dev/null 2>&1`，将输出重定向到/dev/null：

    0 1 5 10 * /path/to/script.sh >/dev/null 2>&1

您也可以在您的 crontab 文件中设置 `MAILTO=""` 变量来禁用全部电子邮件提醒。

## Dcron

cron守护进程会解析一个名为`crontab`的配置文件。系统中的每个用户都可以维护一个单独的crontab文件来单独调度命令。root 用户的 crontab 用于调度全系统的任务（用户可以选择使用 `/etc/crontab` 或 `/etc/cron.d` 目录，这取决于他们选择的 cron 实现）。

The cron daemon parses a configuration file known as `crontab`. Each user on the system can maintain a separate crontab file to schedule commands individually. The root user's crontab is used to schedule system-wide tasks (though users may opt to use `/etc/crontab` or the `/etc/cron.d` directory, depending on which cron implementation they choose).

    /var/spool/cron/root

    # Run command at a scheduled time
    # Edit this 'crontab -e' for error checking, man 1 crontab for acceptable format

    # <@freq>                       <tags and command>
    @hourly         ID=sys-hourly   /usr/sbin/run-cron /etc/cron.hourly
    @daily          ID=sys-daily    /usr/sbin/run-cron /etc/cron.daily
    @weekly         ID=sys-weekly   /usr/sbin/run-cron /etc/cron.weekly
    @monthly        ID=sys-monthly  /usr/sbin/run-cron /etc/cron.monthly

    # mm  hh  DD  MM  W /path/command (or tags) # W = week: 0-6, Sun=0
      21  01  *   *   * /usr/bin/systemctl suspend

下面几行是 crontab 条目的一种可接受格式，即以空格分隔的字段:

1. @period
2. ID=jobname （这个是 dcron 独有的）
3. command

crontab 条目的另一种标准格式是：

1. minute
2. hour
3. day
4. month
5. day of week
6. command

crontab文件本身通常存储为 `/var/spool/cron/username`。例如，root 的 crontab 文件位于 `/var/spool/cron/root`。

参见 crontab man page 获取更多信息和配置示例。

## 另请参见

- Gentoo Linux Cron 指南
- crontab.guru - cronjob 表达式在线编辑器。

Category:

- System administration (简体中文)