原文地址: https://www.tony-yin.site/201...
做为linux
中的定时任务工具,crontab
被广大开发者所热爱和使用。该技术由来已久,至关成熟,可是在真正使用的时候会时不时地发现为何crontab
脚本没有按照预期那样执行?本文以本周笔者遇到一个crontab
不能运行的问题为引子,详细地介绍为何crontab
不运行的各类缘由。html
本周遇到一个crontab
不能执行的问题,发现缘由后以为甚是有趣。python
笔者经过一个python
脚本向/etc/cron.d
目录下的一个文件写入定时任务命令,每分钟调用一个脚本,调用的这个脚本是个python
文件,而后发现cron
并无按照预期每分钟执行一次。而后笔者就将原定时任务脚本aaa
拷贝了一份,并从新命名为bbb
,而后将定时任务中调用脚本改为了执行一个简单的echo
命令,而后保存退出,发现bbb
是能够正常定时运行的,这时候,笔者就经过file
命令想比较一下这两个文件有何不一样:linux
[root@tony cron.d]# file * aaa: ASCII text, with no line terminators bbb: ASCII text
这个时候咱们能够发现aaa
文件出现了比较奇怪的标识:git
with no line terminators
显而易见,这是在说cron
脚本中定时命令没有行终止符,致使这个问题是由于该cron
脚本由python
代码生成时没有添加换行符:github
with open('/etc/cron.d/aaa', 'w') as f: f.write('xxx')
而后笔者尝试性地在aaa
文件中在定时命令下新增一行后,发现定时任务能够正常运行了。不得不说,这是一个颇有意思的问题,crontab
竟然会由于一个换行符致使定时任务的不运行,后来google
了一下发现,crontab
的确存在这个机制,具体解释下面会提到。shell
在google
的同时,在ask unbuntu
上发现了这篇文章:《Why crontab scripts are not working?》,里面不少开发者罗列了他们遇到cron
不能正常运行的各类因素,笔者大体浏览了下,发现有遇到过,也有不少并不知道的,因此想把这些因素和解决方案一一罗列下来。ubuntu
cron
中的环境变量和系统的环境变量是不同的,咱们能够经过设置定时脚本将cron
中的环境变量打印出来:windows
* * * * * env > /tmp/env.output
能够看到cron
中的环境变量:安全
XDG_SESSION_ID=12952 SHELL=/bin/sh USER=root PATH=/usr/bin:/bin PWD=/root LANG=en_US.UTF-8 SHLVL=1 HOME=/root LOGNAME=root XDG_RUNTIME_DIR=/run/user/0 _=/usr/bin/env
查看系统的环境变量:bash
[root@tony cron.d]# env XDG_SESSION_ID=1140 HOSTNAME=tony TERM=xterm-256color SHELL=/bin/bash HISTSIZE=1000 USER=root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin MAIL=/var/spool/mail/root PWD=/etc/cron.d LANG=en_US.UTF-8 TMUX_PANE=%18 HISTCONTROL=ignoredups SHLVL=2 HOME=/root LOGNAME=root _=/usr/bin/env OLDPWD=/root
咱们能够看到cron
中的环境变量不少都和系统环境变量不同(cron
会忽略/etc/environment
文件),尤为是PATH
,只有/usr/bin:/bin
,也就是说在cron
中运行shell
命令,若是不是全路径,只能运行/usr/bin
或/bin
这两个目录中的标准命令,而像/usr/sbin
、/usr/local/bin
等目录中的非标准命令是不能运行的。
这个问题笔者也遇到不少次,因此不少非标准命令都选择了全路径,可是这个方法也有问题,由于不一样环境的命令所存在的目录是不同的。
方案1:
在cron
脚本文件头部声明PATH
#!/bin/bash PATH=/opt/someApp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # rest of script follows
方案2:
在定时脚本调用的脚本头部声明PATH
PATH=/opt/someApp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 15 1 * * * backupscript --incremental /home /root
这个因素就是笔者引子中提到的,官方解释(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)
简单翻译一下就是:
尽管crontab
要求cron
中的每一个条目都要以换行符结尾,但crontab
命令和cron
守护进程都不会检测到这个错误。相反,crontab
将正常加载。然而,命令永远不会运行。最好的选择是确保您的crontab
在末尾有一个空白行。
给cron
中每一个条目下面添加一个空行
注意:
除了没了换行符会致使cron
中的命令不会运行,即引子中所标识:
with no line terminators
可是由于非linux
操做系统致使的非\n
换行符一样会致使该问题,好比windows
的^M
、mac
的\r
等
with CR line terminators
解决方案:
windows
的话就经过dos2unix
命令转换;而mac
则能够经过mac2unix
来转换,mac2unix
也是dos2unix
软件中的一部分
不少时候crond
服务未开启,也会致使定时任务不会正常执行。
查看服务是否运行,若是未运行,启动crond
服务便可。
查看方式有两种:
1.经过进程查看
pgrep
至关于ps -ef | grep
pgrep cron
2.经过service
查看
service crond status
启动服务:
service crond start
从因素1
就知道cron
环境变量中的SHELL
是sh
而不是bash
,咱们知道不少shell
命令是能够在bash
中正常运行,可是不能在sh
中运行的,因此这个因素也会影响定时任务的正常运行。
方案1:
将cron
中须要执行的命令在sh
中执行确认
方案2:
将cron
中须要执行的命令外面加一个bash shell
的封装:
bash -c "mybashcommand"
方案3:
修改cron
中的SHELL
环境变量的值,让全部命令都用bash
解释器:
SHELL=/bin/bash
方案4:
若是定时任务执行的命令是shell
脚本,只要在脚本内添加bash
解释器:
#!/bin/bash
当修改系统时区后,不管是以前已经存在的cron
仍是以后新建立的cron
,脚本中设置的定时时间都以旧时区为准,好比原来时区是Asia/Shanghai
,时间为10:00
,而后修改时区为Europe/Paris
,时间变为3:00
,此时你设置11:00
的定时时间,cron
会在Asia/Shanghai
时区的11:00
执行。
方案1:
重启crond
服务
service crond restart
方案2:
kill crond
进程,由于crond
进程是可重生的
当cron
定时执行命令中,有百分号而且没有转义的时候,cron
执行会出错,好比执行如下cron
:
0 * * * * echo hello >> ~/cron-logs/hourly/test`date "+%d"`.log
会有以下报错:
/bin/sh: -c: line 0: unexpected EOF while looking for matching ``' /bin/sh: -c: line 1: syntax error: unexpected end of file
有的日志也会有以下报错:
(echo) ERROR (getpwnam() failed)
crontab manpage
中解释:
The "sixth" field (the rest of the line) specifies the command to be run. The entire command portion of the line, up to a newline or % character, will be executed by /bin/sh or by the shell specified in the SHELL variable of the cronfile. Percent-signs (%) in the command, unless escaped with backslash (\), will be changed into newline characters, and all data after the first % will be sent to the command as standard input.
即cron
中换行符或%
前的命令会被shell
解释器执行,可是%
会被认为新一行的字符,而且%
后全部的数据都会以标准输出的形式发送给命令。
为百分号作转义,即在%
前添加反斜杠\
Linux
下新建用户密码过时时间是从/etc/login.defs
文件中PASS_MAX_DAYS
提取的,普通系统默认就是99999
,而有些安全操做系统是90
。更改此处,只是让新建的用户默认密码过时时间变化,已有用户密码过时时间仍然不变。
当用户密码过时也会致使cron
脚本执行失败。
将用户密码有效期设置成永久有效期或者延长有效期
方案1:
chage -M <expire> <username>
方案2:
passwd -x -1 <username>
方案3:
手动修改/etc/login.defs
文件中PASS_MAX_DAYS
的值
不少时候解决方案都是采用root
用户执行cron
,可是有时候这并非一个很好的方式。若是采用非root
用户执行cron
,须要注意不少权限问题,好比cron
用户对操做的文件或目录是否存在权限等。
若是权限不够,cron
会拒绝执行:
sudo service cron restart grep -i cron /var/log/syslog|tail -2 2013-02-05T03:47:49.283841+01:00 ubuntu cron[49906]: (user) INSECURE MODE (mode 0600 expected) (crontabs/user)
# correct permission sudo chmod 600 /var/spool/cron/crontabs/user # signal crond to reload the file sudo touch /var/spool/cron/crontabs
一些特殊选项各个平台支持不同,有的支持,有的不支持,例如2/3
、1-5
、1,3,5
须要针对不一样平台作兼容性测试
将以前运行的Crontab Spec
在从一个Crontab
文件移动到另外一个Crontab
文件时可能会崩溃。有时候,缘由是你已经将Spec
从系统crontab
文件转移到用户crontab
文件,反之亦然。
cron
分为系统cron
和用户cron
,用户cron
指/var/spool/cron/username
或/var/spool/crontabs/crontabs/username
,系统cron
指/etc/crontab
以及/etc/crontab
,这二者是存在部分差别的。
系统crontab
在命令行运行以前有一个额外的字段user
。这会致使一些错误,好比你将/etc/crontab
中的命令或者/etc/cron.d
中的文件移动至用户crontab
会报错以下:
george; command not found
相反,当发生相反的状况时,cron
将显示/usr/bin/restartxyz is not a valid username
之类的错误。
当共享系统cron
或用户cron
时,注意用户的添加和删除。
虽然你能够在crontable
里面声明环境变量,可是在下面这种状况定时任务是不会执行的:
SOME_DIR=/var/log MY_LOG_FILE=${SOME_LOG}/some_file.log BIN_DIR=/usr/local/bin MY_EXE=${BIN_DIR}/some_executable_file 0 10 * * * ${MY_EXE} some_param >> ${MY_LOG_FILE}
这是由于在crontable
里面只能声明变量,不能对变量进行操做或者执行其余任何shell
命令的,因此上述的shell
字符串拼接是不会成功的,因此只能声明变量,而后在命令中引用变量。
方案1:
直接声明变量
SOME_DIR=/var/log MY_LOG_FILE=/var/log/some_file.log BIN_DIR=/usr/local/bin MY_EXE=/usr/local/bin/some_executable_file 0 10 * * * ${MY_EXE} some_param >> ${MY_LOG_FILE}
方案2:
声明多个变量,在命令中引用拼接
SOME_DIR=/var/log MY_LOG_FILE=some_file.log BIN_DIR=/usr/local/bin MY_EXE=some_executable_file 0 10 * * * ${BIN_DIR}/${MY_EXE} some_param >> ${SOME_DIR}/${MY_LOG_FILE}
若是你的cronjob
调用了相关GUI
应用时,你须要告诉它们应该使用什么DISPLAY
环境变量,从因素1
咱们能够知道cron
中的环境变量是和系统环境变量不同的,DISPLAY
一样如此,好比
Firefox launch with cron.
声明DISPLAY=:0
* * * * * export DISPLAY=:0 && <command>
目前主要总结了影响cron
运行的12
种因素,固然确定还存在其余影响因素,本文将持续更新,但愿这些坑可以被广大开发者所熟知。
你们若是有上述之外致使cron
不能正常运行的因素能够在博客下方留言,或者在Github
上面提pr
,笔者已经将本文在Github
上面建立了一个仓库,让咱们一块儿不断完善吧 -。-
Github
仓库地址:https://github.com/tony-yin/W...