cron (简体中文)

From ArchWiki

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

摘自 Wikipedia:

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

安装

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

可用的包:

配置

激活及开机启动

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

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

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

处理任务中的错误

cron 会记录 stdoutstderr 的输出并尝试通过 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 行:

[email protected]

要么 创建 /etc/msmtprc 文件,并添加这一行:

aliases /etc/aliases

并创建 /etc/aliases 文件:

your_username: [email protected]
# 可选:
default: [email protected]

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

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

使用 esmtp 的例子

安装 esmtpAURprocmailAUR

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

/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 EDITORVISUAL 来配置,或通过这样的命令直接指定编辑器: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
)

另见这个论坛主题

确保排他性

如果您有可能运行很久的任务(比如说变化很多或者网速突然变慢,备份可能会偶尔运行很长时间),那么 flockutil-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 提供了 cronanacron 两种功能:只要系统在指定的时间可用,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 获取更多信息和配置示例。

另请参见