1 搞清概念
1.1 cron與crond
cron 是linux下實現任務調度(定時任務)的一種服務,可以在無需人工干預的情況下運行作業。
crond 則是 cron 服務的守護進程,與windows下的計劃任務類似。
Linux系統會默認安裝cron服務工具,并自動啟動crond進程。
1.2 crontab
crontab 是Linux系統提供的用于設置定時任務的命令行工具。
crontab 也指 cron 服務的配置文件,是“cron table”的縮寫,語義上即為“任務調度列表”。
1.3 系統任務調度與用戶任務調度
Linux的任務調度主要分為兩類:
系統任務調度:系統周期性所要執行的工作,比如系統數據備份、臨時文件清理、緩存清理等。
用戶任務調度:某個用戶定期要執行的工作,比如用戶數據備份、定時郵件提醒等,這些工作可由每個用戶自行設置。
2 搞清權限
默認情況下,只有root可以使用crontab,而普通用戶不可以,會出現如下提示信息:
root可以通過/etc/cron.allow與/etc/cron.deny兩個文件來控制哪個用戶有權使用crontab。這兩個文件的內容與格式很簡單,將需要配置的用戶名寫入文件,每個用戶名占一行。
權限規則如下:
|
cron.allow |
cron.deny |
有權使用crontab的用戶范圍 |
1 |
文件不存在 |
文件不存在 |
僅root可使用crontab |
2 |
文件存在 |
文件不存在 |
僅cron.allow中的用戶可使用crontab |
3 |
文件不存在 |
文件存在 |
只要不在cron.deny中的用戶都可以使用crontab |
4 |
文件存在 |
文件存在 |
僅cron.allow中的用戶可使用crontab(cron.allow優先級高于cron.deny,此時cron.deny不起作用) |
由于cron.allow與cron.deny同時存在的情況下,cron.deny沒有實際意義。因此從邏輯、語義清晰的角度出發,不建議同時保留兩個文件。根據實際需求,白名單與黑名單保留一個就好。
3 搞清配置文件
系統任務調度對應的配置文件有/etc/crontab文件和/etc/cron.d/目錄下的一系列文件。
用戶任務調度對應的配置文件在/var/spool/cron/目錄下。
cron服務每隔1分鐘會去讀取這些文件中的調度任務。
這一個小節我們先從宏觀上認識這些配置文件,而這些文件的詳細配置語法我們留到下一個小節討論。
3.1 /etc/crontab
/etc/crontab是一個文件,root可以通過這個文件來維護系統級的調度任務。
一般情況下,不建議將所有的全局性的調度任務都維護在/etc/crontab這一個文件中,那樣會很混亂,因此有了/etc/cron.d/。
3.2 /etc/cron.d/
/etc/cron.d/是一個目錄,也由root維護。這個目錄下可以存放多個crontab任務文件,這樣我們就可以以文件的粒度對不同類別的任務調度進行管理了。
/etc/cron.d/目錄下的任務調度文件需要遵循以下命名規范才能被cron服務正確掃描:
- 只能包含字母、數字、下劃線和中劃線(尤其注意不能包含 . )
/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly 這些是什么?
系統預設了這四個目錄,用來放置每小時、每天、每周、每月要執行的可執行腳本文件。
注意:這四個目錄只是預設,并沒有完全啟用,并不是直接將可執行腳本文件放入其中就可以,還需要run-parts命令的配合。
筆者所使用的centos 7.2中,就只有 /etc/cron.hourly 這一個目錄作為系統樣例被啟用了(配置在了/etc/cron.d/目錄下的 0hourly 文件中)。
3.3 /var/spool/cron/
/var/spool/cron/是一個目錄,用來存放包括root在內的每個用戶獨有的crontab任務文件,文件名同用戶名,如用戶“pcserver10”的任務文件為“
/var/spool/cron/pcserver10”。
/var/spool/cron/目錄下的crontab任務文件不可以直接創建或直接修改,而是通過crontab命令來維護。
4 搞清語法
4.1 cron服務啟停
功能 |
命令 |
查看cron服務狀態 |
service crond status |
啟動cron服務 |
service crond start |
停止cron服務 |
service crond stop |
重啟cron服務 |
service crond restart |
調度任務沒有執行,我們首先要通過service crond status來確認cron服務是否已啟動。
在調度任務突然失效的情況下,我們可以嘗試service crond restart重啟cron服務來解決問題。
4.2 crontab命令
crontab命令用來維護用戶級的調度任務,其實就是維護/var/spool/cron/目錄下對應用戶的crontab任務文件,語法格式如下:
crontab [-u user] file
crontab [-u user] { -e | -l | -r }
復制代碼
參數說明:
- -u user:用來指定要維護哪一個用戶的crontab任務,此選項一般由root用戶來使用,普通用戶維護自己的crontab任務表時不需要此選項。
- file:file是文件名,我們可以提前維護好一個任務文件,然后通過crontab file命令將所指定的文件內容載入到crontab中。
- -e:進入文本編輯器,編輯當前或指定用戶的任務信息(本質上是在編輯一個臨時文件,這個臨時文件先讀取了/var/spool/cron/目錄下對應用戶的crontab任務文件的內容,完成編輯后,保存并退出文本編輯器的時候,會用臨時文件的內容覆蓋對應的crontab任務文件)。
- -l:顯示當前或指定用戶的任務信息。
- -r:刪除當前或指定用戶的全部任務信息(即刪除了/var/spool/cron/目錄下對應用戶的crontab任務文件)。
特別注意:
由于crontab -r刪除任務表后不可恢復,因此強烈建議為任務表做好備份。
也就我們在維護任務表時,不要因為方便而使用crontab -e直接編輯任務表,而是應該將任務表的內容維護至一個備份文件中,再通過crontab file命令更新任務表,這樣我們就能留有一份備份文件,以防誤操作使用了crontab -r而無法恢復。
4.3 crontab文件配置 - 調度任務部分
不管是/etc/crontab文件本身、/etc/cron.d/目錄下的任務文件,還是通過crontab命令維護的/var/spool/cron/目錄下的任務文件,這些文件的配置語法基本一致(僅/var/spool/cron/下的文件稍微有不同)。
以/etc/crontab為例,我們來看一下crontab任務文件的內容,可以將其分為環境變量和調度任務兩個部分:
4.3.1 cron任務的基本格式
每一個cron任務的格式如下:
<分鐘> <小時> <日期> <月份> <星期> [<用戶>] <指令>
復制代碼
- 前五個配置項用來表示任務執行的時間。
- 【用戶】用來指定執行任務的用戶,僅適用于系統級的任務調度文件。(通過crontab命令來維護的用戶級任務調度(/var/spool/cron/目錄下的任務文件)不存在此配置項 )
- 【指令】就是要執行的具體任務。
4.3.2 時間的配置
首先來看各個時間配置項的取值范圍:
配置項 |
取值范圍 |
分鐘 |
取值范圍:0-59 |
小時 |
取值范圍:0-23 |
日期 |
取值范圍:1-31 |
月份 |
取值范圍:1-12,也可以使用jan、feb、mar、apr、may、jun、jul、aug、sep、oct、nov、dec(不區分大小寫) |
星期 |
取值范圍:0-7(0與7均代表星期日),也可以使用mon、tue、wed、thu、fri、sat、sun(不區分大小寫) |
除了上述的取值范圍外,還支持以下特殊符號:
特殊符號 |
詳細說明 |
*(星號) |
“每一個”的意思,代表著取值范圍內的任何一個有效值都需要執行指令。 |
-(減號) |
用來表示時間范圍(只支持數字,不支持用名稱來表示的月份與星期),如:“3-6” |
,(逗號) |
可以用來分隔開一系列的值,來形成一個時間列表(只支持數字,不支持用名稱來表示的月份與星期),如:“1,3,5,7”、“0-4,8-12” |
/n(斜線 + 數字) |
用來指定時間間隔與頻率,也就是“每隔 n 個時間單位”的意思,如:“*/2”用在小時上代表著每2個小時、“0-29/2”用在分鐘上代表著前半個小時中每2分鐘 |
下面是一些例子:
表達式 |
說明 |
0 6 * * * |
每天早6點 |
0 */2 * * * |
每兩個小時 |
0 23-7/2,8 * * * |
每晚11點到早8點間的每兩個小時以及早8點 |
0 11 * * 1-3 |
每周一到周三的早11點 |
0 4 1 1 * |
1月1日的早4點 |
5,15,25,35,45,55 16,17,18 * * * |
每天下午4點、5點、6點的5分、15分、25分、35分、45分、55分 |
特別注意:
可以分別以周或以日月為單位,但不要使用 “幾月幾號且為星期幾” 的模式,如:“0 6 1 1 3”。從語法上這個時間表達式并沒有錯誤,可以運行。但是從語義上卻存在歧義,我們可能希望1月1號且為周三時執行,但系統有可能會處理為每年1月1號與每個周三分別執行。
我們還可以用以下“昵稱”來替代五個時間配置項:
昵稱 |
詳細說明 |
@reboot |
系統重啟時執行一次指令 |
@yearly |
每年1月1號0點0分執行一次指令,即“0 0 1 1 *” |
@annually |
同@yearly |
@monthly |
每月1號0點0分執行一次指令,即“0 0 1 * *” |
@weekly |
每周日0點0分執行一次指令,即“0 0 * * 0” |
@daily |
每天0點0分執行一次指令,即“0 0 * * *” |
@hourly |
每小時0分執行一次指令,即“0 * * * *” |
4.3.3 指令的形式
指令可以有以下幾種形式:
- 單條命令,如:* * * * * echo -e $(date '+%Y%m%d') >> /root/tmp.log
- 以;分隔的多條命令,如:* * * * * . /etc/profile;/bin/sh /root/scripts/test.sh
- 完整的可執行腳本路徑,如:* * * * * /root/scripts/test.sh
- 通過run-parts命令來執行某個目錄下的所有可執行腳本文件,如:* * * * * run-parts /root/scripts/cron.minitely
注意:%是crontab的特殊字符,第一個%后的所有數據都會被作為標準輸入。在指令中如需使用%,則需要進行轉義%,如:date '+%Y%m%d'。
4.3.4 如何實現秒級的任務?
crontab可配置的最小的時間粒度是分鐘,那么我們如何實現秒級的任務呢?有如下兩種方式:
- 相同的任務配置多條,通過sleep來制造執行的時間差,如每10秒執行一次:
* * * * * command
* * * * * sleep 10; command
* * * * * sleep 20; command
* * * * * sleep 30; command
* * * * * sleep 40; command
* * * * * sleep 50; command
復制代碼
- 僅配置一條任務,在任務所執行的腳本內,通過for循環實現每隔n秒執行一次的邏輯,如:
* * * * * script
復制代碼
#!/bin/sh
STEP=5
for ((i=0; i < 60; i+=STEP))
do
TIME_NOW=$(date "+%Y-%m-%d %H:%M:%S")
echo -e $TIME_NOW >> /root/scripts/logs/test.log
sleep $STEP
done
復制代碼
4.4 crontab文件配置 - 環境變量部分
4.4.1 基本說明
在crontab任務文件的頭部,可以設置以下環境變量:
環境變量 |
說明 |
由crond設置的默認值 |
SHELL |
指定系統要使用哪種shell解釋器 |
/bin/sh |
PATH |
指定系統執行命令的路徑 |
/usr/bin:/bin |
MAILTO |
指定cron將任務執行的相關信息通過電子郵件發送給哪個用戶 |
無 |
HOME |
指定執行命令或者腳本時使用的主目錄 |
根據crontab的擁有者從/etc/passwd文件中獲取 |
4.4.2 PATH 環境變量不起作用的巨坑
當我們的指令或腳本依賴于環境變量的時候,問題就出現了。在命令行中可以順利運行的指令或腳本,在crontab中卻無法正確執行。這是因為crontab不會從用戶的profile文件中讀取環境變量參數。
這時我們就會把目光轉向PATH,直接設置這個變量不就可以了嘛。然而,這個變量似乎就是個擺設,并不會按照我們想象的那樣去運行。
這里記錄一下PATH的實驗過程:
筆者的CentOS 7.2,系統默認安裝了OpenJDK,版本為號為1.8.0.181。
筆者又重新配置了1.8.0_144版本的JDK,寫入了/etc/profile系統環境變量中:
JAVA版本驗證如下:
現在我們寫一個定時任務,將java -version的輸出結果寫入日志中:* * * * * java -version 2>>
/root/scripts/logs/test_java.log 復制代碼 看一下結果:
這不是我們想要的結果,很明顯環境變量出了問題,我們把環境變量打印出來:
* * * * * echo -e "PATH="$PATH >>
/root/scripts/logs/test_java.log;java -version 2>> /root/scripts/logs/test_java.log 復制代碼 再看結果:
環境變量果然不對,當前是crontab文檔中說的由crond所設置的默認值。我們通過PATH手動修改一下吧:
PATH=
/sbin:/bin:/usr/sbin:/usr/bin:/usr/java/jdk1.8.0_144/bin:/usr/java/jdk1.8.0_144/jre/bin: * * * * * echo -e "PATH="$PATH >> /root/scripts/logs/test_java.log;java -version 2>> /root/scripts/logs/test_java.log 復制代碼 再看結果:
環境變量倒是設置成功了,但是實際上并沒有生效,所以,設置PATH感覺就是設置了個寂寞。
那么環境變量的問題該如何解決呢?有以下兩種方式:
- 修改cron任務,在原有的指令前增加讀取環境變量文件的命令:
* * * * * . /etc/profile; your command
復制代碼
- 在所要執行的腳本文件的開頭增加讀取所需的環境變量文件的命令:
#!/bin/sh
. /etc/profile
. ~/.bash_profile
復制代碼
4.4.3 crontab執行后發送的郵件
每一個調度任務執行完畢時,都會將標準輸出與錯誤輸出信息以電子郵件的形式發送給對應的用戶,/var/spool/mail/目錄下的與用戶同名的文件用來存放對應用戶所接收到的郵件信息,郵件內容如:
我們要討論的第一個問題就是MAILTO所能起到的作用。
(1)在沒有配置MAILTO的情況下:
- /etc/crontab文件和/etc/cron.d/目錄下的任務文件,配置調度任務時會指定執行用戶,電子郵件相應的也就會發送給這個用戶。
- 通過crontab命令維護的任務文件自身屬于哪個用戶,電子郵件就發送給哪個用戶。
(2)配置了MAILTO的情況下:
- MAILTO可以改變上述的默認行為,將電子郵件發送給指定的用戶。
- MAILTO作用于文件中的所有調度任務,且僅在當前文件中生效,不會跨文件生效。
- 僅定義MAILTO但不賦值(即MAILTO=),此時不會發送任何郵件。
調度任務執行后發送郵件,這樣日積月累,郵件文件會越來越大,嚴重的情況下可能會影響系統的運行。我們要討論的下一個問題就是妥善的處理郵件信息。
上面已經提到了,我們可以通過定義一個空的MAILTO,來禁止發送郵件的行為。但MAILTO會作用于文件中所有的任務,是否有辦法從單個任務的角度出發來解決這個問題呢?我們可以使用輸出重定向,在每條指定后增加: >/dev/null 2>&1。(當前,這么做的前提是,對所需要的正常的輸出已經做了處理,比如追加到某個日志文件中。)
4.4.4 體驗 HOME 的作用
雖然實際情況下,基本用不到也不建議使用HOME(建議所有的地方都使用絕對路徑,以屏蔽因路徑而引發的各類問題),但并不妨礙我們實踐并體驗一下HOME的作用。
我們在/root/scripts目錄下做4個測試腳本,并將HOME的值設置為此目錄,然后以4種不同的寫法做測試:
測試結果如下:
- sh home_test_01,因為已指定了HOME=/root/scripts,執行腳本時會到此路徑下去尋找腳本文件,因此能夠正確執行。
- sh $HOME/home_test_02和$HOME/home_test_04,在指令中引用了所設置的環境變量HOME,實際上相當于指明了絕對路徑,可以正確執行。
- home_test_03不能執行,不通過命令(sh)執行的腳本文件,只有寫完整的路徑才能執行。
上述測試過程僅為體驗,依然建議:所有的地方都使用絕對路徑!!!
4.5 其他注意事項
- 應該為調度任務編寫注釋,以#開頭的行即為注釋行。
- root用戶可以查看cron服務的運行日志,/var/log/cron。
作者:熙寧
鏈接:
https://juejin.cn/post/7065972818965430286