爲何crontab不執行

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

因素

因素1:環境變量

場景及緣由

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

因素2:換行符

場景及緣由

這個因素就是筆者引子中提到的,官方解釋(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^Mmac\r

with CR line terminators

解決方案:

windows的話就經過dos2unix命令轉換;而mac則能夠經過mac2unix來轉換,mac2unix也是dos2unix軟件中的一部分

Refer

因素3:crond 服務

場景及緣由

不少時候crond服務未開啓,也會致使定時任務不會正常執行。

解決方案

查看服務是否運行,若是未運行,啓動crond服務便可。

查看方式有兩種:

1.經過進程查看

pgrep至關於ps -ef | grep

pgrep cron

2.經過service查看

service crond status

啓動服務:

service crond start

因素4:shell 解釋器

場景及緣由

從因素1就知道cron環境變量中的SHELLsh而不是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

因素5:時區

場景及緣由

當修改系統時區後,不管是以前已經存在的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進程是可重生的

因素6:百分號%

場景及緣由

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解釋器執行,可是%會被認爲新一行的字符,而且%後全部的數據都會以標準輸出的形式發送給命令。

解決方案

爲百分號作轉義,即在%前添加反斜槓\

Refer

因素7:密碼過時

場景及緣由

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的值

因素8:權限

場景及緣由

不少時候解決方案都是採用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

因素9:不一樣平臺

場景及緣由

一些特殊選項各個平臺支持不同,有的支持,有的不支持,例如2/31-51,3,5

解決方案

須要針對不一樣平臺作兼容性測試

因素10:不一樣 cron

場景及緣由

將以前運行的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時,注意用戶的添加和刪除。

因素11:crontable 變量

場景及緣由

雖然你能夠在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}

因素12:GUI

場景及緣由

若是你的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...

相關文章
相關標籤/搜索