避免定時任務指令碼的常見問題
很多指令碼在實際使用的時候往往是以定時任務的方式運行,而非手工運行。但是實現同樣功能的指令碼在這兩種運行方式下可能遇到的問題不盡相同。
以定時任務方式啟動並執行指令碼往往會遇到以下幾個問題。
路徑問題:目前的目錄往往不是指令檔所在目錄。因此,指令碼在引用其使用的外部檔案,如設定檔和其它指令檔時,無法方便得使用相對路徑。
命令找不到問題:指令碼中使用到的一些外部命令,在手工執行指令碼的時候可以正常調用。但是在定時任務下運行則可能出現指令碼解析器找不到相關命令的問題。
指令碼重複運行問題:一次指令碼的執行未結束,而下一次指令碼的運行已經開始。導致系統中有多個進程在同時運行同一個指令碼。
下面分享定時任務指令碼開發中上述幾個常見問題的處理方法。
路徑問題
定時任務下當前路徑往往不是指令檔所在目錄。因此我們需要用絕對路徑來引用。即先擷取指令碼所在目錄,然後以該目錄為基礎採用絕對路徑的方式去引用指令碼所需的外部檔案。方法如下面代碼所示。
清單 1. 擷取指令檔所在路徑
#!/usr/bin/ksh echo "Current path is: `pwd`"scriptPath=`dirname $0` #擷取指令碼所在路徑 echo "The script is located at: $scriptPath"cat "$scriptPath/readme" #使用絕對路徑引用外部檔案
將清單 1 中的指令碼置於目錄/opt/demo/scripts/auto-task 下,並在 cron 中添加該指令碼。定時任務運行輸出如下。
Current path is: /home/viscent
The script is located at: /opt/demo/scripts/auto-task
命令找不到問題
定時任務下啟動並執行指令碼可能出現指令碼解析器找不到相關命令的問題。比如 Oracle 資料庫中的 sqlplus 命令,指令碼在調用該命令時若沒有特殊處理則在定時任務下執行會使指令碼解析器無法找到這個命令,出現如下所示的錯誤提示:
sqlplus: command not found
這是因為指令碼在定時任務下執行時指令碼是由非登入式 Shell 來執行的,並且執行指令碼的父 Shell 並非 Oracle 使用者的 Shell。因此,此時 Oracle 使用者的.profile 檔案並沒有被調用。故解決的方法是在指令碼的開頭添加以下代碼:
清單 2. 解決找不到外部命令問題
source /home/oracle/.profile
也就說,對於外部命令找不到的問題,可以通過在指令碼的開頭加一個 source 使用者的.profile 檔案的語句來解決。
指令碼重複運行問題
定時任務指令碼的另外一個常見問題是指令碼重複啟動並執行問題。比如,一個指令碼被設定為每 5 分鐘運行一次。若某一次該指令碼的運行無法在 5 分鐘內結束的話,定時任務服務仍然會新啟一個進程來執行該指令碼。這時就出現了運行同一個指令碼的多個進程。而這可能導致指令碼功能紊亂。並且浪費了系統資源。 避免指令碼重複啟動並執行方法通常有兩種。一是在指令碼執行時先檢查系統是否存在運行該指令碼的其它進程。若存在,則終止當前指令碼的運行。二是,指令碼運行時檢查系統中是否存在其它進程運行該指令碼。若存在,則結束那個進程(此方法有一定風險,慎用!)。這兩種方法均需要在指令碼的開頭檢查系統是否已經存在運行當前指令碼的進程,若存在這樣的進程則擷取該進程的 PID。範例程式碼如下清單 3 所示。
清單 3. 防止指令碼重複運行方法 1
#!/usr/bin/ksh main(){selfPID="$$"scriptFile="$0" typeset existingPidexistingPid=`getExistingPIDs $selfPID "$scriptFile"` if [ ! -z "$existingPid" ]; then echo "The script already running, exiting..." exit -1fi doItsTask } #擷取除本身進程以外其它運行當前指令碼的進程的 PIDgetExistingPIDs(){selfPID="$1"scriptFile="$2" ps -ef | grep "/usr/bin/ksh ${scriptFile}" | grep -v "grep" | awk "{ if(\$2!=$selfPID) print \$2 }"} doItsTask(){echo "Task is now being executed..."sleep 20 #睡眠 20s,以類比指令碼在執行需要長時間完成的任務} main $*