十三、格式化輸出指定使用者的當前運行進程:
在這個例子中,我們通過指令碼參數的形式,將使用者列表傳遞給該指令碼,指令碼在讀取參數後,以樹的形式將使用者列表中使用者的所屬進程列印出來。
/> cat > test13.sh
#!/bin/sh
#1. 迴圈讀取指令碼參數,構造egrep可以識別的使用者列表變數(基於grep的擴充Regex)。
#2. userlist變數尚未賦值,則直接使用第一個參數為它賦值。
#3. 如果已經賦值,且指令碼參數中存在多個使用者,這裡需要在每個使用者名稱之間加一個豎線,在egrep中,豎線是分割的元素之間是或的關係。
#4. shift命令向左移動一個指令碼的位置參數,這樣可以使迴圈中始終操作第一個參數。
while [ $# -gt 0 ]
do
if [ -z "$userlist" ]; then
userlist="$1"
else
userlist="$userlist|$1"
fi
shift
done
#5. 如果沒有使用者列表,則搜尋所有使用者的進程。
#6. "^ *($userlist) ": 下面的調用方式,該正則的展開形式為"^ *(root|avahi|postfix|rpc|dbus) "。其含義為,以0個或多個空格開頭,之後將是root、avahi、postfix、rpc或dbus之中的任何一個字串,後面再跟隨一個空格。
if [ -z "$userlist" ]; then
userlist="."
else
userlist="^ *($userlist) "
fi
#7. ps命令輸出所有進程的user和命令資訊,將結果傳遞給sed命令,sed將刪除ps的title部分。
#8. egrep過濾所有進程記錄中,包含指定使用者列表的進程記錄,再將過濾後的結果傳遞給sort命令。
#9. sort命令中的-b選項將忽略前置空格,並以user,再以進程名排序,將結果傳遞個uniq命令。
#10.uniq命令將合并重複記錄,-c選項將會使每條記錄前加重複的行數。
#11.第二個sort將再做一次排序,先以user,再以重複計數由大到小,最後以進程名排序。將結果傳給awk命令。
#12.awk命令將資料進行格式化,並重複資料刪除的user。
ps -eo user,comm | sed -e 1d | egrep "$userlist" |
sort -b -k1,1 -k2,2 | uniq -c | sort -b -k2,2 -k1nr,1 -k3,3 |
awk ' { user = (lastuser == $2) ? " " : $2;
lastuser = $2;
printf("%-15s\t%2d\t%s\n",user,$1,$3)
}'
CTRL+D
/> ./test13.sh root avahi postfix rpc dbus
avahi 2 avahi-daemon
dbus 1 dbus-daemon
postfix 1 pickup
1 qmgr
root 5 mingetty
3 udevd
2 sort
2 sshd
... ...
rpc 1 rpcbind
十四、用指令碼完成which命令的準系統:
我們經常會在指令碼中調用其他的應用程式,為了保證指令碼具有更好的健壯性,以及錯誤提示的準確性,我們可能需要在執行前驗證該命令是否存在,或者說是否可以被執行。這首先要確認該命令是否位於PATH變數包含的目錄中,再有就是該檔案是否為可執行檔。
/> cat > test14.sh
#!/bin/sh
#1. 該函數用於判斷參數1中的命令是否位於參數2所包含的目錄列表中。需要說明的是,函數裡面的$1和$2是指函數的參數,而不是指令碼的參數,後面也是如此。
#2. cmd=$1和path=$2,將參數賦給有意義的變數名,是一個很好的習慣。
#3. 由於PATH環境變數中,目錄之間的分隔字元是冒號,因此這裡需要臨時將IFS設定為冒號,函數結束後再還原。
#4. 在for迴圈中,逐個變數目錄列表中的目錄,以判斷該命令是否存在,且為可執行程式。
isInPath() {
cmd=$1 path=$2 result=1
oldIFS=$IFS IFS=":"
for dir in $path
do
if [ -x $dir/$cmd ]; then
result=0
fi
done
IFS=oldifs
return $result
}
#5. 檢查命令是否存在的主功能函數,先判斷是否為絕對路徑,即$var變數的第一個字元是否為/,如果是,再判斷它是否有可執行許可權。
#6. 如果不是絕對路徑,通過isInPath函數判斷是否該命令在PATH環境變數指定的目錄中。
checkCommand() {
var=$1
if [ ! -z "$var" ]; then
if [ "${var:0:1}" = "/" ]; then
if [ ! -x $var ]; then
return 1
fi
elif ! isInPath $var $PATH ; then
return 2
fi
fi
}
#7. 指令碼參數的合法性驗證。
if [ $# -ne 1 ]; then
echo "Usage: $0 command" >&2;
fi
#8. 根據傳回值列印不同的資訊。我們可以在這雷根據我們的需求完成不同的工作。
checkCommand $1
case $? in
0) echo "$1 found in PATH." ;;
1) echo "$1 not found or not executable." ;;
2) echo "$1 not found in PATH." ;;
esac
exit 0
CTRL+D
/> ./test14.sh echo
echo found in PATH.
/> ./test14.sh MyTest
MyTest not found in PATH.
/> ./test14.sh /bin/MyTest
/bin/MyTest not found or not executable.
十五、驗證輸入資訊是否合法:
這裡給出的例子是驗證使用者輸入的資訊是否都是數字和字母。需要說明的是,之所以將其收集到該系列中,主要是因為它實現的方式比較巧妙。
/> cat > test15.sh
#!/bin/sh
echo -n "Enter your input: "
read input
#1. 事實上,這裡的巧妙之處就是先用sed替換了非法部分,之後再將替換後的結果與原字串比較。這種寫法也比較容易擴充。
parsed_input=`echo $input | sed 's/[^[:alnum:]]//g'`
if [ "$parsed_input" != "$input" ]; then
echo "Your input must consist of only letters and numbers."
else
echo "Input is OK."
fi
CTRL+D
/> ./test15.sh
Enter your input: hello123
Input is OK.
/> ./test15.sh
Enter your input: hello world
Your input must consist of only letters and numbers.
十六、整數驗證:
整數的重要特徵就是只是包含數字0到9和負號(-)。
/> cat > test16.sh
#!/bin/sh
#1. 判斷變數number的第一個字元是否為負號(-),如果只是則刪除該負號,並將刪除後的結果賦值給left_number變數。
#2. "${number#-}"的具體含義,可以參考該系列部落格中"Linux Shell常用技巧(十一)",搜尋索引鍵"變數模式比對運算子"即可。
number=$1
if [ "${number:0:1}" = "-" ]; then
left_number="${number#-}"
else
left_number=$number
fi
#3. 將left_number變數中所有的數字都替換掉,因此如果返回的字串變數為空白,則表示left_number所包含的字元均為數字。
nodigits=`echo $left_number | sed 's/[[:digit:]]//g'`
if [ "$nodigits" != "" ]; then
echo "Invalid number format!"
else
echo "You are valid number."
fi
CTRL+D
/> ./test16.sh -123
You are valid number.
/> ./test16.sh 123e
Invalid number format!
十七、判斷指定的年份是否為閏年:
這裡我們先列出閏年的規則:
1. 不能被4整除的年一定不是閏年;
2. 可以同時整除4和400的年一定是閏年;
3. 可以整除4和100,但是不能整除400的年,不是閏年;
4. 其他可以整除的年都是閏年。
#!/bin/sh
year=$1
if [ "$((year % 4))" -ne 0 ]; then
echo "This is not a leap year."
exit 1
elif [ "$((year % 400))" -eq 0 ]; then
echo "This is a leap year."
exit 0
elif [ "$((year % 100))" -eq 0 ]; then
echo "This is not a leap year."
exit 1
else
echo "This is a leap year."
exit 0
fi
CTRL+D
/> ./test17.sh 1933
This is not a leap year.
/> ./test17.sh 1936
This is a leap year.
十八、將單列顯示轉換為多列顯示:
我們經常會在顯示時將單行的輸出,格式化為多行的輸出,通常情況下,為了完成該操作,我們將加入更多的代碼,將輸出的結果存入數組或臨時檔案,之後再重新遍曆它們,從而實現單行轉多行的目的。在這裡我們介紹一個使用xargs命令的技巧,可以用更簡單、更高效的方式來完成該功能。
/> cat > test18.sh
#!/bin/sh
#1. passwd檔案中,有可能在一行內出現一個或者多個空白字元,因此在直接使用cat命令的結果時,for迴圈會被空白字元切開,從而導致一行的文本被當做多次for迴圈的輸入,這樣我們不得不在sed命令中,將cat輸出的每行文本進行全域替換,將空白字元替換為%20。事實上,我們當然可以將cat /etc/passwd的輸出以管道的形式傳遞給cut命令,這裡之所以這樣寫,主要是為了示範一旦出現類似的問題該如果巧妙的處理。
#2. 這裡將for迴圈的輸出以管道的形式傳遞給sort命令,sort命令將基於user排序。
#3. -xargs -n 2是這個技巧的重點,它將sort的輸出進行合并,-n選項後面的數字參數將提示xargs命令將多少次輸出合并為一次輸出,並傳遞給其後面的命令。在本例中,xargs會將從sort得到的每兩行資料合併為一行,中間用空格符分離,之後再將合并後的資料傳遞給後面的awk命令。事實上,對於awk而言,你也可以簡單的認為xargs減少了對它(awk)的一半調用。
#4. 如果打算在一行內顯示3行或更多的行,可以將-n後面的數字修改為3或其它更高的數字。你還可以修改awk中的print命令,使用更為複雜列印輸出命令,以得到更為可讀的輸出效果。
for line in `cat /etc/passwd | sed 's/ /%20/g'`
do
user=`echo $line | cut -d: -f1`
echo $user
done | \
sort -k1,1 | \
xargs -n 2 | \
awk '{print $1, $2}'
CTRL+D
/> ./test18.sh
abrt adm
apache avahi
avahi-autoipd bin
daemon daihw
dbus ftp
games gdm
gopher haldaemon
halt lp
mail nobody
ntp operator
postfix pulse
root rtkit
saslauth shutdown
sshd sync
tcpdump usbmuxd
uucp vcsa