該系列將重點介紹Linux Shell中的高級使用技巧,其主要面向有一定經驗的Shell開發者、Linux系統管理員,以及Linux的愛好者。博客中的示例主要來源於網絡和一些經典書籍,在經過本人的收集和整理之後,以系列博客的形式呈現給諸位。如果大家有更多更好的Shell腳本經典示例,且願意在這裡與我們一同分享的話,可以以郵件、博客回復等形式與我聯系,我將會盡量保證該系列的持續更新。
一、將輸入信息轉換為大寫字符後再進行條件判斷: 我們在讀取用戶的正常輸入後,很有可能會將這些輸入信息用於條件判斷,那麼在進行比較時,我們將不得不考慮這些信息的大小寫匹配問題。
/> cat > test1.sh #!/bin/sh echo -n "Please let me know your name. " read name #將變量name的值通過管道輸出到tr命令,再由tr命令進行大小寫轉換後重新賦值給name變量。 name=`echo $name | tr [a-z] [A-Z]` if [[ $name == "STEPHEN" ]]; then echo "Hello, Stephen." else echo "You are not Stephen." fi CTRL+D /> ./test1.sh Please let me know your name.
stephen Hello, Stephen.
二、為調試信息設置輸出級別: 我們經常在調試腳本時添加一些必要的調試信息,以便跟蹤到程序中的錯誤。在完成調試後,一般都會選擇刪除這些額外的調試信息,在過了一段時間之後,如果腳本需要添加新的功能,那麼我們將不得不重新進行調試,這樣又有可能需要添加這些調試信息,在調試成功之後,這些信息可能會被再次刪除。如果我們能夠為我們的調試信息添加調試級別,使其只在必要的時候輸出,我想這將會是一件非常惬意的事情。
/> cat > test2.sh #!/bin/sh if [[ $# == 0 ]]; then echo "Usage: ./test2.sh -d debug_level" exit 1 fi #1. 讀取腳本的命令行選項參數,並將選項賦值給變量argument。 while getopts d: argument do #2. 只有到選項為d(-d)時有效,同時將-d後面的參數($OPTARG)賦值給變量debug,表示當前腳本的調試級別。 case $argument in d) debug_level=$OPTARG ;; \?) echo "Usage: ./test2.sh -d debug_level" exit 1 ;; esac done #3. 如果debug此時的值為空或者不是0-9之間的數字,給debug變量賦缺省值0. if [[ -z $debug_level || $debug_level != [0-9] ]]; then debug_level=0 fi echo "The current debug_level level is $debug_level." echo -n "Tell me your name." read name name=`echo $name | tr [a-z] [A-Z]` if [ $name = "STEPHEN" ];then #4. 根據當前腳本的調試級別判斷是否輸出其後的調試信息,此時當debug_level > 0時輸出該調試信息。 test $debug_level -gt 0 && echo "This is stephen." #do something you want here. elif [ $name = "ANN" ]; then #5. 當debug_level > 1時輸出該調試信息。 test $debug_level -gt 1 && echo "This is ann." #do something you want here. else #6. 當debug_level > 2時輸出該調試信息。 test $debug_level -gt 2 && echo "This is others." #do any other else. fi CTRL+D /> ./test2.sh Usage: ./test2.sh -d debug_level
/> ./test2.sh -d 1 The current debug level is 1.
Tell me your name.
ann /> ./test2.sh -d 2 The current debug level is 2.
Tell me your name.
ann This is ann.
三、判斷參數是否為數字: 有些時候我們需要驗證腳本的參數或某些變量的值是否為數字,如果不是則需要需要給出提示,並退出腳本。
/> cat > test3.sh #!/bin/sh #1. $1是腳本的第一個參數,這裡作為awk命令的第一個參數傳入給awk命令。 #2. 由於沒有輸入文件作為輸入流,因此這裡只是在BEGIN塊中完成。 #3. 在awk中ARGV數組表示awk命令的參數數組,ARGV[0]表示命令本身,ARGV[1]表示第一個參數。 #4. match是awk的內置函數,返回值為匹配的正則表達式在字符串中(ARGV[1])的起始位置,沒有找到返回0。 #5. 正則表達式的寫法已經保證了匹配的字符串一定是十進制的正整數,如需要浮點數或負數,僅需修改正則即可。 #6. awk執行完成後將結果返回給isdigit變量,並作為其初始化值。 #7. isdigit=`echo $1 | awk '{ if (match($1, "^[0-9]+$") != 0) print "true"; else print "false" }' ` #8. 上面的寫法也能實現該功能,但是由於有多個進程參與,因此效率低於下面的寫法。 isdigit=`awk 'BEGIN { if (match(ARGV[1],"^[0-9]+$") != 0) print "true"; else print "false" }' $1` if [[ $isdigit == "true" ]]; then echo "This is numeric variable." number=$1 else echo "This is not numeric variable." number=0 fi CTRL+D
/> ./test3.sh 12 This is numeric variable.
/> ./test3.sh 12r This is not numeric variable.
四、判斷整數變量的奇偶性: 為了簡化問題和突出重點,這裡我們假設腳本的輸入參數一定為合法的整數類型,因而在腳本內部將不再進行參數的合法性判斷。
/> cat > test4.sh #!/bin/sh #1. 這裡的重點主要是sed命令中正則表達式的寫法,它將原有的數字拆分為兩個模式(用圓括號拆分),一個前面的所有高位數字,另一個是最後一位低位數字,之後再用替換符的方式(\2),將原有數字替換為只有最後一位的數字,最後將結果返回為last_digit變量。 last_digit=`echo $1 | sed 's/\(.*\)\(.\)$/\2/'` #2. 如果last_digit的值為0,2,4,6,8,就表示其為偶數,否則為奇數。 case $last_digit in 0|2|4|6|8) echo "This is an even number." ;; *) echo "This is not an even number." ;; esac CTRL+D /> ./test4.sh 34 This is an even number.
/> ./test4.sh 345 This is not an even number.
五、將Shell命令賦值給指定變量,以保證腳本的移植性: 有的時候當我們在腳本中執行某個命令時,由於操作系統的不同,可能會導致命令所在路徑的不同,甚至是命令名稱或選項的不同,為了保證腳本具有更好的平台移植性,我們可以將該功能的命令賦值給指定的變量,之後再使用該命令時,直接使用該變量即可。這樣在今後增加更多OS時,我們只需為該變量基於新系統賦予不同的值即可,否則我們將不得不修改更多的地方,這樣很容易導致因誤修改而引發的Bug。
/> cat > test5.sh #!/bin/sh #1. 通過uname命令獲取當前的系統名稱,之後再根據OS名稱的不同,給PING變量賦值不同的ping命令的全稱。 osname=`uname -s` #2. 可以在case的條件中添加更多的操作系統名稱。 case $osname in "Linux") PING=/usr/sbin/ping ;; "FreeBSD") PING=/sbin/ping ;; "SunOS") PING=/usr/sbin/ping ;; *) ;; esac CTRL+D /> . ./test5.sh /> echo $PING /usr/sbin/ping
六、獲取當前時間距紀元時間(1970年1月1日)所經過的天數: 在獲取兩個時間之間的差值時,需要考慮很多問題,如閏年、月份中不同的天數等。然而如果我們能夠確定兩個時間點之間天數的差值,那麼再計算時分秒的差值時就非常簡單了。在系統提供的C語言函數中,獲取的時間值是從1970年1月1日0點到當前時間所流經的秒數,如果我們基於此計算兩個時間之間天數的差值,將會大大簡化我們的計算公式。
/> cat > test6.sh #!/bin/sh #1. 將date命令的執行結果(秒 分 小時 日 月 年)賦值給數組變量DATE。 declare -a DATE=(`date +"%S %M %k %d %m %Y"`) #2. 為了提高效率,這個直接給出1970年1月1日到新紀元所流經的天數常量。 epoch_days=719591 #3. 從數組中提取各個時間部分值。 year=${DATE[5]} month=${DATE[4]} day=${DATE[3]} hour=${DATE[2]} minute=${DATE[1]} second=${DATE[0]} #4. 當月份值為1或2的時候,將月份變量的值加一,否則將月份值加13,年變量的值減一,這樣做主要是因為後面的公式中取月平均天數時的需要。 if [ $month -gt 2 ]; then month=$((month+1)) else month=$((month+13)) year=$((year-1)) fi #5. year變量參與的運算是需要考慮閏年問題的,該問題可以自行去google。 #6. month變量參與的運算主要是考慮月平均天數。 #7. 計算結果為當前日期距新世紀所流經的天數。 today_days=$(((year*365)+(year/4)-(year/100)+(year/400)+(month*306001/10000)+day)) #8. 總天數減去紀元距離新世紀的天數即可得出我們需要的天數了。 days_since_epoch=$((today_days-epoch_days)) echo $days_since_epoch seconds_since_epoch=$(((days_since_epoch*86400)+(hour*3600)+(minute*60)+second)) echo $seconds_since_epoch CTRL+D /> . ./test6.sh 15310
1322829080
需要說明的是,推薦將該腳本的內容放到一個函數中,以便於我們今後計算類似的時間數據時使用。