您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)如何解析Linux Shell編程,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
很多腳本在實際使用的時候往往是以定時任務(wù)的方式運行,而非手工運行。但是實現(xiàn)同樣功能的腳本在這兩種運行方式下可能遇到的問題不盡相同。
以定時任務(wù)方式運行的腳本往往會遇到以下幾個問題。
路徑問題:當(dāng)前目錄往往不是腳本文件所在目錄。因此,腳本在引用其使用的外部文件,如配置文件和其它腳本文件時,無法方便得使用相對路徑。
命令找不到問題:腳本中使用到的一些外部命令,在手工執(zhí)行腳本的時候可以正常調(diào)用。但是在定時任務(wù)下運行則可能出現(xiàn)腳本解析器找不到相關(guān)命令的問題。
腳本重復(fù)運行問題:一次腳本的執(zhí)行未結(jié)束,而下一次腳本的運行已經(jīng)開始。導(dǎo)致系統(tǒng)中有多個進程在同時運行同一個腳本。
下面分享定時任務(wù)腳本開發(fā)中上述幾個常見問題的處理方法。
定時任務(wù)下當(dāng)前路徑往往不是腳本文件所在目錄。因此我們需要用絕對路徑來引用。即先獲取腳本所在目錄,然后以該目錄為基礎(chǔ)采用絕對路徑的方式去引用腳本所需的外部文件。方法如下面代碼所示。
1 2 3 4 5 6 7 | #!/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 中添加該腳本。定時任務(wù)運行輸出如下。
Current path is: /home/viscent
The script is located at: /opt/demo/scripts/auto-task
定時任務(wù)下運行的腳本可能出現(xiàn)腳本解析器找不到相關(guān)命令的問題。比如 Oracle 數(shù)據(jù)庫中的 sqlplus 命令,腳本在調(diào)用該命令時若沒有特殊處理則在定時任務(wù)下執(zhí)行會使腳本解析器無法找到這個命令,出現(xiàn)如下所示的錯誤提示:
sqlplus: command not found
這是因為腳本在定時任務(wù)下執(zhí)行時腳本是由非登錄式 Shell 來執(zhí)行的,并且執(zhí)行腳本的父 Shell 并非 Oracle 用戶的 Shell。因此,此時 Oracle 用戶的.profile 文件并沒有被調(diào)用。故解決的方法是在腳本的開頭添加以下代碼:
1 | source /home/oracle/.profile |
也就說,對于外部命令找不到的問題,可以通過在腳本的開頭加一個 source 用戶的.profile 文件的語句來解決。
定時任務(wù)腳本的另外一個常見問題是腳本重復(fù)運行的問題。比如,一個腳本被設(shè)置為每 5 分鐘運行一次。若某一次該腳本的運行無法在 5 分鐘內(nèi)結(jié)束的話,定時任務(wù)服務(wù)仍然會新啟一個進程來執(zhí)行該腳本。這時就出現(xiàn)了運行同一個腳本的多個進程。而這可能導(dǎo)致腳本功能紊亂。并且浪費了系統(tǒng)資源。 避免腳本重復(fù)運行的方法通常有兩種。一是在腳本執(zhí)行時先檢查系統(tǒng)是否存在運行該腳本的其它進程。若存在,則終止當(dāng)前腳本的運行。二是,腳本運行時檢查系統(tǒng)中是否存在其它進程運行該腳本。若存在,則結(jié)束那個進程(此方法有一定風(fēng)險,慎用?。?。這兩種方法均需要在腳本的開頭檢查系統(tǒng)是否已經(jīng)存在運行當(dāng)前腳本的進程,若存在這樣的進程則獲取該進程的 PID。示例代碼如下清單 3 所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #!/usr/bin/ksh
main(){ selfPID="$$" scriptFile="$0"
typeset existingPid existingPid=`getExistingPIDs $selfPID "$scriptFile"`
if [ ! -z "$existingPid" ]; then echo "The script already running, exiting..." exit -1 fi
doItsTask
}
#獲取除本身進程以外其它運行當(dāng)前腳本的進程的 PID getExistingPIDs(){ 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,以模擬腳本在執(zhí)行需要長時間完成的任務(wù) }
main $* |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #!/usr/bin/ksh
main(){ selfPID="$$" scriptFile="$0"
typeset existingPid existingPid=`getExistingPIDs $selfPID "$scriptFile"`
if [ ! -z "$existingPid" ]; then echo "The script already running, killing it..." kill -9 "$existingPid" #此方法有一定風(fēng)險,慎用! fi
doItsTask
}
#獲取除本身進程以外其它運行當(dāng)前腳本的進程的 PID getExistingPIDs(){ selfPID="$1" scriptFile="$2"
}
doItsTask(){ echo "Task is now being executed..." sleep 20 #睡眠 20s,以模擬腳本在執(zhí)行需要長時間完成的任務(wù) }
main $* |
雖然 Shell 開發(fā)的一個普遍問題是調(diào)試?yán)щy,缺乏有效的調(diào)試工具。但是,我們可以采取一些能夠一定程度上幫助我們規(guī)避調(diào)試?yán)щy的開發(fā)與調(diào)試的方式。 由于是腳本開發(fā),不少人習(xí)慣于從直接地一行行地寫代碼,一個腳本里面甚至于一個函數(shù)都沒有。雖然這種方式在語法上和功能上并無問題。但這增加了調(diào)試的難度。相反,如果采用模塊化的方式去編寫腳本,則使代碼結(jié)構(gòu)清晰、便于調(diào)試。這點,可以看這樣一個例子。
假設(shè)下面的腳本的功能是收集生產(chǎn)環(huán)境中的相關(guān)日志文件,用于定位問題。需要收集的日志文件包括操作系統(tǒng)日志、中間件日志以及應(yīng)用系統(tǒng)本身的日志。這些文件會被壓縮成一個 gz 文件。
1 2 3 4 5 6 7 8 | #!/usr/bin/ksh
main(){ collectSyslog #收集系統(tǒng)日志文件 collectMiddlewareLog #收集中間件日志文件 collectAppLog #收集應(yīng)用系統(tǒng)日志文件 tar -zcf logs.tgz syslog.zip mdwlog.zip applog.zip #將三中類型的日志打包,方便下載 } |
若腳本執(zhí)行報如下錯誤:
tar: applog.zip: Cannot stat: No such file or directory
我們可以很快鎖定 collectAppLog 這個函數(shù)。因為它負(fù)責(zé)輸出 applog.zip 這個文件。而沒有必要看代碼中的其它部分。
采用模塊化的方式的另一個好處是代碼調(diào)試的結(jié)果可以鞏固下來。比如上面的例子中,如果你已經(jīng)調(diào)試好了操作狀態(tài)日志收集的函數(shù)。接下來調(diào)試其它函數(shù)的時候,這些被調(diào)試的代碼盡管可能需要改動。但是這些改動影響到之前已經(jīng)調(diào)試好的代碼的可能并不大。相反,若是一個腳本中通篇都是語句,而不帶函數(shù),則改動其中一行代碼,收集三種日志的功能可能都受影響。
另外一個典型的場景是腳本編寫過程中,我們可能會因為不太確定一些問題如何處理而寫一些嘗試性的代碼。然后,通過反復(fù)的調(diào)試去確認(rèn)正確的處理方式。而事實上這些嘗試性的代碼可能就是一條語句甚至一個命令。但不少人是在大段的代碼中反復(fù)去調(diào)試這一小段代碼。這將非常耗時間。尤其是調(diào)試過程中代碼中的其它部分調(diào)試時出現(xiàn)錯誤時,作者還得先解決其它錯誤,否則可能會時我們真正要調(diào)試的代碼無法被執(zhí)行到。這種情形下,專門寫一個測試性的小腳本。
在該腳本中調(diào)試還我們不太確定該如何寫的代碼,如何將其”集成”到我們正在開發(fā)的腳本中。這樣可以提高調(diào)試效率,避免消耗本不該消耗的時間。比方說,我們在編寫過程中需要獲取腳本本身所在進程的進程 ID。而此時我們又不太確定這個獲取當(dāng)前進程 id 的代碼該怎么寫。那么,我們可以新建一個測試性的腳本在其中嘗試實現(xiàn)這個獲取進程 ID 的功能。找到正確的方法后,將代碼“移植”到我們真正要開發(fā)的腳本中。
腳本開發(fā)中經(jīng)常要處理的一個問題是輸出提示信息。當(dāng)然,對于簡短的提示信息輸出,使用 echo 命令就足夠了。但是,對于大段的提示信息輸出仍然使用 echo 命令處理則顯得不夠優(yōu)雅。一種更適合的方法是使用 cat 命令結(jié)合輸入重定向。下面通過一個具體例子來說明這點。
假設(shè)下面的腳本會將某個程序安裝到用戶指定的目錄下。若用戶指定的目錄不存在,則提示
用戶檢查指定的目錄是否正確,并重新執(zhí)行腳本。
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/usr/bin/ksh
path="$1"
if [ ! -d "$path" ]; then #這里還必需處理星號這個特殊字符的顯示 echo '****************************************************' echo ERROR
echo ${path} echo "Then re-run this script." echo '****************************************************' fi |
這種方式的代碼可讀性不是很好,閱讀者需要閱讀多個 echo 命令然后再進行"綜合"才能準(zhǔn)確理解提示信息是什么。另外,一旦提示信息需要改動。這種改動可能因為改動其中一個 echo 命令時不小心多了一個雙引號等特殊字符而引起語法錯誤,從而影響了整個腳本的執(zhí)行。
清單 7 的代碼則展示了如何使用 cat 命令和輸入重定向來更好地處理大段文本的輸出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/usr/bin/ksh
path="$1" if [ ! -d "$path" ]; then cat **************************************************** ERROR The destination directory not exists,make sure below directory you specified is correct: ${path} Then re-run this script. **************************************************** EOF fi |
顯然,這種處理方式的代碼更加簡潔,可讀性更好。閱讀者只需要看一條命令,就知道提示信息的具體內(nèi)容。并且,若要修改提示語,我們可以放心地在兩個文件終止符 EOF 之間的部分改。即便修改錯了,也不會影響到代碼中的其它部分。
新手在編寫 Shell 腳本時往往在不必要使用臨時文件的情況下使用了臨時文件。這不僅增加了而外的代碼編寫工作量(用于處理創(chuàng)建、讀取、和刪除臨時文件等),而且可能使腳本運行速度變慢(文件 I/O 畢竟不是快的操作)。
下面的例子中假設(shè)有個腳本的功能是往當(dāng)前目錄下所有的.txt 文件中添加如下一行文本:
–End of file name–
清單 8.和清單 9.中的代碼分別顯示了在不必要使用臨時文件的情況下使用臨時文件的代碼和不需要使用臨時文件的代碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #!/usr/bin/ksh
ls -lt *.txt | awk '{print $NF}' > tmp #將命令輸出重定向到臨時文件 tmp
cat tmp
typeset fileName
typeset lastLine
while read fileName #逐行讀取臨時文件中的每一行
do
lastLine=`tail -1 "$fileName"`
if [ ! "$lastLine" == "--End of $fileName--" ]; then
echo "--End of $fileName--" >> $fileName
fi
done
rm tmp #刪除臨時文件 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/usr/bin/ksh
typeset fileName
typeset lastLine
for fileName in $(ls -lt *.txt | awk '{print $NF}')
do
lastLine=`tail -1 "$fileName"`
if [ ! "$lastLine" == "--End of $fileName--" ]; then
echo "--End of $fileName--" >> $fileName
fi
done |
如果你的開發(fā)環(huán)境是在 Windows 操作系統(tǒng)下,而測試則是通過終端軟件(如 Putty)在 Linux上進行。這種情形下,不少開發(fā)者習(xí)慣于在終端軟件上直接編輯腳本(如使用 vi 命令)。顯然,這種方式編輯效率低下。并且,腳本開發(fā)往往需要邊修改邊測試。即使是一個語法錯誤,由于缺乏工具的支持,我們可能要通過運行腳本才能發(fā)現(xiàn)。因此,提高腳本編輯效率某種程度上便提高了開發(fā)效率。在 Windows 系統(tǒng)上開發(fā)腳本時提高腳本編輯效率的一個不錯的選擇是使用支持簡單的 FTP 功能的編輯器,如 Editplus 和 UltraEditor??梢允褂眠@些編輯器以 FTP 的方式“打開”(實際上就是下載)Linux 測試主機上的腳本文件。編輯好腳本后對腳本進行保存時,這些編輯器會自動將腳本上傳到測試主機上。接下來只需通過終端軟件對腳本進行測試。如果測試后腳本需要繼續(xù)修改,則可以利用編輯器的“重新載入文檔”的功能(通??梢詾樵摴δ茉O(shè)置快捷鍵)。
上述就是小編為大家分享的如何解析Linux Shell編程了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。