原文地址: 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...