問題的由來
寫好的程式希望在崩潰之後能夠自啟動,於是利用linux的crontab功能,新增一個計劃任務,每分鐘執行一個指令碼檢視需要監控的程式是否還在,如果不在則啟動之,否則不做任何事情。這麼一個簡單的指令碼在crontab中執行和在shell終端手工執行的結果卻不一樣。
問題描述
以下是監控指令碼/home/watch.sh的內容:
#!/bin/sh shell_log_file=/home/start.log pid_count=`pidof video_checkup | wc -w` path=$(cd "$(dirname "$0")"; pwd) run_command="${path}/video_checkup" config_path="${path}/config.json" if [ $pid_count -eq 0 ]; then echo `date +%Y-%m-%d_%H:%M:%S`" run $run_command $config_path" >> $shell_log_file $run_command $config_path else echo `date +%Y-%m-%d_%H:%M:%S`" video_checkup already running" >> $shell_log_file fi
在shell終端中執行crontab -e 命令新增如下語句:
*/1 * * * * /home/watch.sh >/dev/null 2>&1
表示該指令碼每分鐘執行一次,指令碼的邏輯很簡單就是檢查程式video_checkup如不存在則執行之,可是在實際測試中卻發現,video_checkup程式不斷增多,每分鐘都被執行了一次。
問題分析
通過除錯發現指令碼中 if [ $pid_count -eq 0 ]; then
每次都會進入並執行video_checkup程式,也就是說 $pid_count -eq 0
這個判斷每次都是true。將 $pid_count 的值匯入到log檔案中發現確實是0 。
但是video_checkup明明在執行的啊,不可能是0的,將watch.sh在shell命令列上手工執行卻是正常的結果($pid_count就是實際的正在執行的video_checkup程式個數的值)。經過google發現,在crontab計劃任務中執行指令碼watch.sh的環境變數,和自己ssh登入到shell中手工執行watch.sh的環境變數是不同的,於是乎在watch.sh中加入下面的語句:echo `export` >> $shell_log_file
並分別在crontab中執行watch.sh,以及在ssh登入的shell中手工執行watch.sh發現果然export的結果不一樣。
在crontab中執行watch.sh的時候log檔案中顯示的export結果中PATH的值是: export PATH="/usr/bin:/bin"
而ssh登入到shell之後手工執行watch.sh之後log檔案中顯示的export結果中的PATH的值是: PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"
這個影響大嗎,難道這個PATH變數對 pid_count=`pidof video_checkup | wc -w`
執行的結果會有影響?
此時我想到有一種可能就是,pidof命令是在哪個目錄下? 在ssh的shell環境中執行:
[root@172-28-246-152 video_checkup]# which pidof /sbin/pidof
發現pidof命令是在 /sbin/目錄下,也就是說crontab執行的環境中 PATH="/usr/bin:/bin" 目錄中根本沒有pidof這個命令,那麼在crontab中執行 watch.sh中的 pid_count=`pidof video_checkup | wc -w`
就會失敗,但是居然連一個錯誤都沒有報告,而且pid_count變數中還被賦值了,難道pidof命令找不到的時候這個語句也能返回值?
我在ssh的shell中構造一個不存在的pidof路徑,試一下:
[root@172-28-246-152 video_checkup]# pid_count=`/xx/pidof video_checkup | wc -w` -bash: /xx/pidof: No such file or directory
果然報錯說No such file or directory找不到命令,但是此時pid_count中是否有值呢? 再試一下:
[root@172-28-246-152 video_checkup]# pid_count=`/xx/pidof video_checkup | wc -w` && echo $pid_count -bash: /xx/pidof: No such file or directory 0
結果徹底清楚了: 由於crontab在後臺執行,所以pidof命令不存在,我們根本看不到報錯資訊,因為報命令不存在的資訊是不會被通過管道傳遞給 wc -w 的,所以可以說出錯的時候wc -w沒有收到任何輸入,但是其執行的結果卻是 0 那麼變數pid_count的值就是 0 了。
問題解決
將ssh登入之後的shell環境中的PATH賦值到watch.sh指令碼中即可,這樣指令碼在執行的時候就可以正確找到 pidof 命令得出正確的結果了 (也即在指令碼watch.sh的開始處加入程式碼 PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"
即可)
一個小問題居然花了幾個小時查清楚原因,可見平時覺得簡單的問題在實際應用過程中還是有很多坑的