为什么 crontab 脚本不起作用?

通常,crontab脚本没有按计划或预期执行。出现这种情况的原因有很多:
  1. 错误的crontab标记
  2. 权限问题
  3. 环境变量
此社区维基旨在汇总crontab脚本未按预期执行的主要原因。请将每个原因写在单独的答案中。
请每个答案只写一个原因 - 关于为什么没有执行的详细信息 - 以及针对该原因的修复方法。
请仅编写与cron特定的问题,例如从shell正常执行但通过cron错误执行的命令。

18你必须关闭crontab -e以使cron生效。例如,使用vim编辑文件并使用:w保存,但是直到退出后,作业才会添加到cron中。因此,在我也执行:q之前,我将看不到该作业。 - DutGRIFF
我认为调试cron的最佳方法是检查系统日志并找出问题所在。 - Suneel Kumar
停电 - Josef Klimuk
请检查这个链接:https://askubuntu.com/a/1223213/297387 - Vadim
只有一个问题,让我花了一些时间才找到答案:我写成了'0 * * * * /pathtoscript/script'。我忽视了脚本没有在预期的文件夹中运行。简单的 'cd /pathtoscript' 解决了我的问题...花了我几个小时才弄明白。 - BerndGit
47个回答

不同的环境

Cron将一组最小的环境变量传递给您的作业。要查看差异,请添加一个类似这样的虚拟作业:

* * * * * env > /tmp/env.output

等待/tmp/env.output被创建,然后再删除该作业。现在将/tmp/env.output的内容与在常规终端中运行env命令的输出进行比较。

这里一个常见的问题是PATH环境变量的不同。也许您的cron脚本使用了在/opt/someApp/bin中找到的somecommand命令,并且您已经将其添加到/etc/environmentPATH中?cron会忽略来自该文件的PATH,因此在使用cron运行脚本时,从脚本中运行somecommand将失败,但在终端中运行时却可以正常工作。值得注意的是,来自/etc/environment的变量将传递给cron作业,只是cron自身设置的变量(如PATH)不会传递。

为了解决这个问题,只需在脚本顶部设置自己的PATH变量。例如:

#!/bin/bash
PATH=/opt/someApp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# rest of script follows

有些人喜欢在所有命令中使用绝对路径。我不建议这样做。想象一下,如果你想在另一个系统上运行你的脚本,而在那个系统上,命令位于/opt/someAppv2.2/bin。你将不得不遍历整个脚本,将/opt/someApp/bin替换为/opt/someAppv2.2/bin,而不仅仅是在脚本的第一行进行小的编辑。
你也可以在crontab文件中设置PATH变量,这将适用于所有cron作业。例如:
PATH=/opt/someApp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

15 1 * * * backupscript --incremental /home /root

7我想我刚刚陷入了这种情况,并且接二连三,简直是一波未平一波又起。 - WernerCD
+1 这就是我在使用 grep 和 ls 的 cron 脚本中头疼的主要原因,路径在 cron 的环境下为空,呵呵。 - DM8
9+1 对于 env,我完全忘记了那个命令,并且以为 PATH 是有效的。实际上,在我的情况下稍微有点不同。 - Izkata
依赖于PATH路径进行计算是一个糟糕的主意。因为某个可执行文件可能在其他计算机上的其他位置,这并不能否定“我希望我的定时任务运行我指定的确切可执行文件,而不是之前列在PATH路径中的其他可执行文件”。请仔细思考一下,有人可能会将一个新的(被篡改的)版本的"bash"、"mail"或者其他你调用的程序放入/opt/someApp/bin目录中,然后当ROOT定时任务运行他们被篡改的可执行文件而不是应该运行的那个时,他们就控制了你的系统。 - pbr
9如果这样的目录对其他人可写,系统已经受到了威胁。 - geirha
@geirha 另一位系统管理员可能会无意中将新的可执行文件放在目录中 - 例如,可能是更新的语言解释器版本,但这些版本可能无法与由 cron 调用的旧代码配合使用。 此外 - 在您的示例中,在路径的前面添加了一个非标准目录 - /opt/someApp/bin - 谁知道它的所有者/组以及权限是什么。 很容易出现意外的“漏洞”,攻击者可以利用这个漏洞。 我坚持我的原始观点 - 依赖 PATH 是一个糟糕的主意,应该使用完整的路径来指定可执行文件。 - pbr
6@pbr 一个系统管理员可能会无意中删除根文件系统。你无法防止系统管理员犯下愚蠢的错误。如果你安装了一个与旧版本不兼容的解释器的更新版本,我预计会出现故障。处理这个问题的明智方法是将其安装为不同的命令。例如,你拥有 python 版本 2.x 并安装了python 3,你将其安装为 python3,而不是 python。至于 /opt/someApp/bin,为什么它不具备合理的权限/所有权呢?任何明智的管理员都会确保系统文件具备合理的权限/所有权。 - geirha
@geirha,我们可以就这个问题来回争论一整年,但我不会这样做——这将是我对此事的最后更新。我同意你无法防范系统管理员犯愚蠢错误的情况。比如,在他们的cron规范中设置了一个路径而不是使用完整路径名。/opt/someApp/bin既不是“系统文件”也不是“系统目录”,它是一个第三方应用程序目录被放在PATH的前面,用于crontab的示例中。如果那是ROOT的crontab,那真的是一个非常糟糕的主意。总之,这其实很简单——最佳实践是使用完整路径名,而不是依赖于PATH设置。 - pbr
2@pbr 看起来我们可以无休止地继续下去,是的。但我仍然不明白为什么使用PATH是个坏主意。如果你觉得有必要在适合讨论的平台上进一步探讨这个问题,你可以在irc.freenode.net上的#ubuntu和#bash等频道找到我。 - geirha
写脚本比启动cron花的时间更长。正如你上面所建议的,确实是路径问题。谢谢! - eggie5
当我尝试从cronjob执行sh文件时,出现了这个错误:“sh: 0: 无法打开 /root/scritps/cron_job.sh”。请注意:cron_job.sh只是执行echo命令。你有任何关于这个问题的想法吗? - Bipin Vayalu
@BipinVayalu,这可能意味着它要么无法访问该文件,要么路径不存在。也许应该是scripts而不是scritps - geirha
我理解得对吗?将/opt/someApp/bin添加到PATH环境变量时,最好是添加到末尾而不是开头。提前编辑完成。 - ulidtko
@ulidtko,我会说不,你通常希望/opt/someApp/bin中的可执行文件优先于其他目录中具有相同名称的命令。这个顺序是有意为之的。 - geirha
我倾向于持有不同的看法。这些以/opt风格命名的软件包通常并非旨在覆盖系统命令,它们通常提供自己独特的命令名称。这意味着它们在PATH的末尾是可以接受的。然而,正如@pbr所合理地指出的那样,故意覆盖系统命令很可能是恶意软件的特征。个人而言,我更倾向于默认选择安全方案(即将其放在PATH的末尾),只有在完全检查软件包/目录内容并且确实有合适的覆盖动机之后,才将其移到前面。 - ulidtko
总结为以下结论:对于不关心的用户,建议 结束 PATH。只有在这种方式不起作用时,尝试前置 -- 但首先要注意系统安全。 - ulidtko
当你安装了一个名为foo的软件包,并且它恰好在/usr/bin目录下安装了一个与someApp中使用的命令同名但功能完全不同的命令时,cronjob突然停止工作。这可能会产生灾难性的影响。显然,你必须相信/opt/someApp/bin目录中的命令没有恶意行为。如果那里面有恶意命令,系统管理员的工作就做得不好。 - geirha
嗯,那也是一个合理的反对意见。 - ulidtko
有时候我会像这样输入 * * * * * source ~/.bashrc; command 来获取与我的正常登录会话相同的环境设置。 - mcfedr
特别是,图形(X11)命令将无法工作;请参阅http://askubuntu.com/a/92195/56280。 - ntc2
1谢谢你,太棒了。解决了我的一个问题,因为我从来没有想到那些缺失的路径。 - Hugh
你还可以通过cron将变量传递给你的脚本,方法如下: "*/5 * * * * env HOME=/root /path/to/script" - Tony Barganski
如果您在系统上修改了时区,那么必须重新启动所有关心时间的服务,或者重新启动。这是我从下面的回答中复制的评论。 - Arun
使用PATH=/usr/sbin无法工作。我必须执行export PATH=/usr/sbin - PouJa

我的顶级陷阱:如果你忘记在crontab文件的末尾添加换行符。换句话说,crontab文件应该以空行结尾。
以下是关于这个问题的man页面中的相关部分(man crontab然后跳到最后):
   Although cron requires that each entry in a crontab end  in  a  newline
   character,  neither the crontab command nor the cron daemon will detect
   this error. Instead, the crontab will appear to load normally. However,
   the  command  will  never  run.  The best choice is to ensure that your
   crontab has a blank line at the end.

   4th Berkeley Distribution      29 December 1993               CRONTAB(1)

126这真是个令人瞩目的问题,为什么在这么多年的定时任务中还没有修复呢? - Capi Etheriel
2在Vixie cron中似乎已经修复了:Ubuntu 10.10上的man crontab表示"cron要求crontab中的每个条目以换行符结尾。如果最后一个条目缺少换行符,cron将认为crontab(至少部分)已损坏并拒绝安装它。"(并且日期在2010年4月19日)。 - Marius Gedminas
21@barraponto 这实际上是新文本编辑器中的一个错误。"换行"字符应该是一个“行终止”字符,所以文本文件中的最后一行应该以一个不在编辑器中显示的换行字符结尾。Vi和vim正确使用了这个字符,而cron是在新编辑器开始出现奇怪行为之前构建的...因此,为了保险起见,我们包含了一个空行。 - Izkata
1直到现在,我一直对crontabs(定时任务表)感到困惑...真是个大发现...和@barraponto有着相同的想法。 - Ben
9如果你使用 crontab -e 编辑 crontab,它会在允许保存之前检查文件的语法,包括换行符的检查。 - Tom Harrison Jr
1@TomHarrisonJr 这是误导性的。'crontab -e' 使用你在环境中设置的编辑器。如果是vi,那么它会在最后一行添加换行符。但是crontab不会检查行是否以换行符结尾,这在答案中的man页面摘录中明确说明了。 - Chan-Ho Suh
2根据man页面的说法,cron要求crontab中的每个条目都以换行字符结束。如果crontab的最后一个条目缺少换行符,cron将认为该crontab(至少部分)已损坏,并拒绝安装它。这种行为将在使用-e选项编辑并保存crontab时触发,并且与编辑器无关。 - Tom Harrison Jr
谢谢你的回答。我遇到过很多次crontab -e没有告诉我没有换行符的情况。只需要在行末按下回车键,然后保存就可以了! - Terrance
谢谢您的小费。我在Ubuntu 12.04.1 LTS上遇到这个问题。 - Mohamed Ennahdi El Idrissi
这很奇怪,我在多台服务器上工作过,其中一台出现了问题,我按照建议在末尾添加了新行,并且问题得到了解决。奇怪的是另一台服务器的crontab正常运行,最后一个条目没有包含新行,更奇怪的是所有服务器都在同一个操作系统上运行。 - Mohyaddin Alaoddin
感谢您的修复。就好像开源社区的人故意想让每个人的生活变得更加困难一样。一个缺失的换行符导致cron静默停止工作,没有任何错误或警告?再次感谢您引导我们度过这场疯狂。 - nicbou
我刚刚注意到一个类似的问题:如果你调用一个没有以换行符结尾的shell脚本。 - user114676
什么……但为什么? - Nabin
我意识到这可能是非常晚的建议。我遇到了这个问题。在文件末尾没有注释行是很重要的。在某些特殊情况下,它会停止工作。我只是在文件末尾添加了实际的cron命令,而没有注释。末尾还需要一个空行。如果你正在使用用户,你还需要验证用户。cron是否可以访问这个环境? - contributorpw

Cron守护程序没有运行。几个月前我真的搞砸了。
类型:
pgrep cron 

如果你看不到任何数字(即cron的主PID),那么cron没有在运行。可以使用sudo /etc/init.d/cron start来启动cron。 编辑:与其通过/etc/init.d调用init脚本,建议使用service实用程序,例如:
sudo service cron start

编辑:在现代Linux中,你也可以使用systemctl。
sudo systemctl start cron

58谢谢你给我展示pgrep。我一直在用ps -ef | grep foo。 - ripper234
4你也可以使用 pidof cron,这将省略其他同样带有'cron'这个词的应用程序(如crontab)的结果。 - Pithikos
奇怪,所有这些都没有显示 cron 正在运行,但如果我运行 sudo service cron start,我会得到 start: Job is already running: cron - Colleen
3如果是CentOS/RHEL系统,请执行命令service crond start - Srihari Karanth

在`cron.d/`、`cron.daily/`、`cron.hourly/`等目录中,脚本文件名不应包含点号(.),否则run-parts将跳过它们。
参见run-parts(8)。
   If neither the --lsbsysinit option nor the --regex option is given then
   the names must consist entirely of upper and lower case  letters,  dig‐
   its, underscores, and hyphens.

   If  the  --lsbsysinit  option  is given, then the names must not end in
   .dpkg-old  or .dpkg-dist or .dpkg-new or .dpkg-tmp, and must belong  to
   one  or more of the following namespaces: the LANANA-assigned namespace
   (^[a-z0-9]+$);   the   LSB   hierarchical   and   reserved   namespaces
   (^_?([a-z0-9_.]+-)+[a-z0-9]+$);  and  the  Debian cron script namespace
   (^[a-zA-Z0-9_-]+$).

所以,如果你有一个cron脚本backup.sh,analyze-logs.pl在cron.daily/目录中,最好去掉文件扩展名。

12这是一项特性而不是错误-它可以防止 myscript.backup、myscript.original 或 myscript.rpm-new 等文件与 myscript 相邻地运行。 - pbr
@pbr: 有道理。至少如果run-parts --test(或者其他类似--debug的虚构选项)能够输出跳过的文件及其原因,那么对于调试来说会很有帮助。 - Rabarberski
14如果这是一个功能,那可不是个好的功能 :( 很多人在文件名中使用点号(backup.sh 是最常见的)。 如果你想停止执行一个脚本,最合理的方法就是将其从 "cron.d" 目录中移除。 - MatuDuke
12这个功能太糟糕了,实际上它就是一个错误。常见做法是要求使用特定的结尾(比如".list"或".cron"等),这样人们才能确保事情只在预期时运行。随意选择点作为".bak"或".temp"等的分隔符,除了会可靠地让人们感到困惑之外,完全没有可预测性。而像".sh"和".pl"这样的合法结尾已经广泛使用了几十年。然而,有很多人使用"_bak"、"_temp"或"-bak"代替点。这是一个糟糕的设计选择;最好说它是一个设计缺陷。 - Teekin
等一下,这是不是意味着即使我将这个命令添加到crontab中,我的shell脚本pull-data.sh也永远不会执行:/bin/bash /home/userName/pull-data.sh? - RodrikTheReader

在许多环境中,cron使用sh来执行命令,而很多人却认为它会使用bash
以下是对于执行失败的命令进行测试或修复的建议:
  • 尝试在sh中运行命令,看看是否有效:

    sh -c "mycommand"
    
  • 将命令包装在bash子shell中,以确保在bash中运行:

    bash -c "mybashcommand"
    
  • 通过在crontab顶部设置shell,告诉cron在bash中运行所有命令:

    SHELL=/bin/bash
    
  • 如果命令是一个脚本,请确保脚本包含shebang:

    #!/bin/bash
    

1命令提示符的建议非常有帮助,已经解决了我的定时任务问题。 - Maxim Galushka
1这让我费了一个小时的时间来摆弄和排除故障。更令人困惑的是,如果你不知道这个问题,脚本在手动运行时会正常工作,只要你的典型shell是bash,但在cron中却不行。谢谢! - Hendy
1很久以前我遇到了一个相关的问题:在bash中有source命令,但在sh中没有。在cron/sh中,应该使用点号:. envfile而不是source envfile - kungphu
@Clockwork sh "mycommand" 告诉 sh 以脚本文件的形式运行 mycommand。你是不是想说 sh -c "mycommand"?无论如何,这个回答似乎是关于让命令在 bash 中运行的,所以为什么在这里添加了 sh 的命令呢? - Olorin
1从我的理解来看,第一点的目标是尝试使用sh来运行它,以查看问题是否真的是因为cron使用sh而不是bash来运行它。然而,我对这个问题了解甚少,所以可能是我错了。 - Clockwork

我在时区方面遇到了一些问题。Cron是按照最初安装的时区运行的。解决办法是重新启动cron:
sudo service cron restart

8是的,在更改系统时区后,必须重新启动所有关心时间的服务,或者重新启动。我更喜欢重新启动,以确保我已经处理了所有事情。 - pbr
哦,天哪,花了好几个小时在这上面。尝试了服务重启后的 * * * * * touch /tmp/cronworks,却没有任何效果,但是cronlog中确实有RELOAD - Evgeniy Chekan
这解决了,尽管我的时区没有问题。 - Aven Desta
哇!这真是救了我的命。 - Arun
当你认为找到根本原因并修复它比快速重启的魔力更快时,你会浪费很多时间。 - undefined

应该使用绝对路径来引用脚本:
例如,应该使用/bin/grep而不是grep
# m h  dom mon dow   command
0 0 *  *  *  /bin/grep ERROR /home/adam/run.log &> /tmp/errors

不是:

# m h  dom mon dow   command
0 0 *  *  *  grep ERROR /home/adam/run.log &> /tmp/errors

这个特别棘手,因为当从shell执行时,相同的命令会起作用。原因是cron没有与用户相同的PATH环境变量。

3请参考geirha的回答,你可以(必须)定义cron的路径。 - Capi Etheriel
11嗡嗡声。你不需要定义路径 - 在这里使用绝对路径是最佳实践。"因为可执行文件可能在其他计算机上的其他位置"并不能取代"我希望它仅运行这个程序,而不是在我的原始程序前面放置在路径中的其他程序"。 - pbr
1是的,对我来说就是这样了,在cron之外,我可以直接运行命令,在cron之内,它需要完整的/usr/bin/whatever路径。 - Anentropic

如果您的crontab命令中包含一个%符号,cron会尝试解释它。因此,如果您正在使用任何带有%的命令(例如对日期命令的格式规范),您需要对其进行转义。
这里还有其他一些注意事项:
http://www.pantz.org/software/cron/croninfo.html

这就是导致我的 Cron 任务在过去一周内失败的原因。终于弄清楚了,我的日期没有转义字符(对于其他人寻找转义字符的人来说,是反斜杠)。耶! - Valien
3请参阅如何在cron任务中执行date - Jared Beck

Cron正在调用一个不可执行的脚本。
通过运行chmod +x /path/to/script,脚本变得可执行,这应该解决这个问题。

6这不仅仅是cron的问题,只需在命令行中尝试执行/path/to/script即可轻松追踪到。 - Adam Matan
4如果您习惯使用. scriptnamesh scriptnamebash scriptname执行脚本,那么这就成为一个“cron”特定的问题。 - Eliah Kagan

用户的密码可能已过期。即使是root的密码也可以过期。您可以使用tail -f /var/log/cron.log命令查看cron失败并显示密码已过期。您可以通过执行以下操作将密码设置为永不过期:passwd -x -1 <用户名>

在某些系统(Debian,Ubuntu)中,默认情况下未启用cron的日志记录。在/etc/rsyslog.conf/etc/rsyslog.d/50-default.conf文件中,找到以下行:

# cron.*                          /var/log/cron.log

应该编辑(sudo nano /etc/rsyslog.conf)取消注释为:
cron.*                          /var/log/cron.log

之后,您需要通过重新启动rsyslog来完成。
/etc/init.d/rsyslog restart

或者

service rsyslog restart 

来源:在Debian Linux中启用crontab日志记录

在一些系统(如Ubuntu)中,默认情况下未启用cron的单独日志文件,但与cron相关的日志会出现在syslog文件中。您可以使用以下方法

cat /var/log/syslog | grep cron -i

查看与cron相关的消息。

我有Debian(wheezy),但没有/etc/init.d/rsyslog,只有inetutils-syslogd和sysklogd。我需要安装什么东西还是只需重启其中之一? - hgoebl

  • 相关问题