bash應該是目前Linux上最流行的shell指令碼解釋程式了(還有個shell叫dash,我太討厭這個東東了。),只要你在linux上工作,並且希望自己能夠工作得更愉悅,那麼你應該熟悉最基本的bash編程,因為它將給你的工作帶來足夠的幸福感。本文將總結一些我自己平時使用的bash基本編程知識,和大家分享,也便於自己查詢。
變數
1、bash的變數名是區分大小寫,並且變數名首字元不能是數字。看的各種代碼也不少了,說實話,我還真沒見到誰的代碼用數字開頭的變數名,我認為即使語言允許,這樣做的人也很少,除非你真的很特別。
2、變數定義與賦值
aaa=123
這裡需要注意定義變數時等號前後都不能有空格,必須緊靠著寫。雖然等號後面有空格的情況,文法可能不會出錯,但結果絕對是錯誤的。
3、變數拼接
bbb=${aaa}123
很多時候,我們可能需要用一些變數、常量字串等來拼接出一個新的變數,這時需要注意用來拼接的變數可能需要加上{},否則可能會出現變數識別錯誤從而找不到變數的情況。這種情況,我傾向於所有變數一股腦的全加上{}。
4、local和export
變數定義時還有兩個常用的關鍵字——local和export。export在下文再說,定義局部變數的local,我卻基本不用,等我使用的時候再來補上總結。
變數的定義也就那麼回事,不去咬文嚼字的吭細節的話,懂這麼一點點就夠用了。如果,你的目標是成為shell高手,那麼就需要專業級的學習,linux系統中有不少龐大的shell可以學習。
條件判斷
if條件表達中長涉及到的比較有字串、整數和檔案屬性比較等。
if語句格式有:
if [ expr ] ; then
do something
fi
if [ expr ] ; then
do something
else
do something
fi
if [ expr ] ; then
do something
elif [ expr ] ; then
do something
else
so something
fi
if語句和其他語言(c,java)相比,是行不同但神似。then關鍵可以另起一行,那樣條件運算式後的分號就可以省略了。這裡最需要注意的是 " [ " 和 " ] "前後至少需要一個空格來分割。
1、整數比較
整數大小比較涉及的操作符有—— -lt、-le、-eq、-gt、-ge和 -ne。
例子:
a=1
b=2
if [ $a -lt $b ] ; then
echo "a < b"
else
echo "a >= b"
fi
if [ $a -ne 3 ] ; then
echo "a != 3"
else
echo "a == 3"
fi
if [ 1 -gt 3 ] ; then
echo "1 > 3"
else
echo "1 <= 3"
fi
使用整數大小比較的6個操作符時,涉及到的兩個運算元將會作為數值來處理,而不是字串,即使你使用雙引號將比較對象給引起來,也是如此。
2、字串比較
字串比較可能用到的操作符有—— =、!=、>、<、-n和-z。
例子:
s1=aaa
s2=bbb
if [ $s1 \< $s2 ] ; then
echo "$s1 < $s2"
else
echo "$s1 >= $s2"
fi
if [[ $s1 < $s2 ]] ; then
echo "$s1 < $s2"
else
echo "$s1 >= $s2"
fi
if [ $s1 = $s2 ] ; then
echo "$s1 == $s2"
else
echo "$s1 != $s2"
fi
if [ -n $s1 ] ; then
echo $s1
fi
用於字串大小比較的>和<兩個操作符比較特別,在[ ]中書寫時需要轉義,否則可以使用[[ ]]來替代[ ]。不轉義,>和<會被解釋為IO重新導向操作。其次,if語句體不可為空,必須至少做一件事情。
操作符-n 用於判斷字串不為空白,即長度不為0。-z判斷字串為空白,即長度為0。
3、檔案屬性判斷
檔案屬性判斷涉及到的操作符比較多,如下:
-e、-a:檔案存在
-d:是目錄
-f:是檔案
-r:可讀
-w:可寫
-x:可執行
f1 -nt f2:f1比f2新
f1 -ot f2:f1比f2老
這些操作符的使用方法和字串操作符基本一致,在這幾個操作符中我最常用的還是-e和-f,其他用得較少。
其實,除了這三類常用的判斷以外,還應該有執行命令結果的判斷也比較常用,本文將不予總結了。
for迴圈
常用的for迴圈有兩種主要形式
形式一
for e in [list]
do
do something
done
這種格式的for常用來遍曆一個list集合中的所有元素,並加以處理。比如:
1、遍曆一個目錄中的所有檔案
for file in `ls dir`
do
echo $file
done
2、遍曆一個給定的集合
list="a b c d e f g"
for e in $list
do
echo $e
done
for in格式在遍曆集合時,其實是根據空白字元來分隔字串,取得每個元素的,上例中的`ls dir`和$list得到的都是一個帶空白字元的字串。
形式二
for ((i = 0; i < n; i++))
do
do something
done
這一種for的形式和C語言基本一致,只是需要雙括弧罷了,它更擅長做確定次數的迴圈計算。比如:
for ((i = 0; i < 10; i++))
do
echo $i
done
for (( ; ; ))
do
echo "aaaa"
done
沒有計數器的for迴圈就是一個死迴圈的實現,這和C語言的寫法也是一致的,真有親切感。
掌握了這點for迴圈語句,我們就可以做很多的事情了,基本足夠我們玩了。
while迴圈
這鳥蛋,我平時基本不用,一切需要迴圈的地方都用for。在這裡列舉while迴圈的原因,就是想借它噴一下各種語言裡的各種while, do while,還有什麼until等。一個迴圈非得搞出各種不同的表達方式不可,這一點還是Golang做得最到位。文法糖太多,總有一天會膩死你。哈哈。
case語句
case語句相當於絕大多數語言裡的switch語句。這玩意除了具備if-elif的功能外,還支援萬用字元,這個相當有用。我們直接看例子。
例子:
url=www.tmall.com
case $url in
www.taobao.com) echo 1;;
*.taobao.com) echo 2;;
*.tmall.com) echo 3;;
www.tmall.com) echo 4;;
*) echo 5;;
esac
上例中條件分支不光有常量字串,還有含萬用字元的字串,這一點用來進行模式比對非常便利。其次,需要注意case語言在匹配的過程中是從第一個開始逐一匹配,所有上例的輸出結果是3,而不是精確匹配的4。我認為這算一個小小的遺憾,要是支援精確匹配優先就更好玩了。
其次,需要注意的是每個條件分支體的結束必須用雙分號。
好了,case語言比較簡單,但很實用。
函數函數定義
function hello()
{
echo "hello world"
}
函數定義實用關鍵字function,函數名後面的括弧可有可無。
函數調用
無參數的函數調用只需要給出函數名就ok了,上面定義的函數直接用hello調用即可。有參數的函數,只需要將參數依次在函數名後面給出即可,如:func_name arg1 arg2。
函數參數
函數調用的時候可以給函數傳遞參數,那麼函數體中又如何擷取這些參數呢? 函數參數的擷取和指令碼程式參數的擷取一致,都是通過$1、$2等來取得。比如:
function add()
{
n=$1
m=$2
echo $((n + m))
}
調用add 1 2,將輸出3。
在函數中,可以通過$#的值來判斷函數調用的時候,傳遞了幾個參數。
bash函數裡很少使用return這種方式來傳回值。不過可以這樣調用函數來擷取計算結果,比如:
res=`add 1 2`
變數res的值就是3了。注意上面不是單引號,而是數字1旁邊的字元。
字串處理
字串處理差不多是整個電腦世界裡做得最頻繁的一件事情了。玩C語言的人很多事情都是在編寫字串處理常式。玩java的人大多數時候雖然不用自己去編寫字串處理常式,但也基本總是在調用字串處理方法。linux其實有著非常強大的工具讓我們去做字串處理,不熟悉之前,每個工具貌似長得都非常複雜的樣子。這裡簡單的總結一下自己使用過的字串相關的東東。
1、求子串
str=abcdefg
echo ${str:2:3} 將得到bcd,運算式中的2代表位移量,3代表長度。
2、求字串長度
str=123456
echo ${#str}
3、字串替換
${變數/pattern/xx} 將變數中的第一個匹配替換為xx。
${變數//pattern/xx} 將變數中的所有匹配替換為xx。
str=aaabbbccc
echo ${str/aaa/xxx}
echo ${str/a/x}
有關字串的處理,有著很多的工具,比如:你可以使用cut, awk等程式去拆分一個字串等。
整數運算
整數運算一般使用$(( expr ))來進行。比如:
echo $((1 + 2))
a=1
echo $((a + 2))
echo $((a++))
echo $((++a))
b=2
echo $(((a + b) / 2))
可以看到只需要將計算運算式塞到$(())中就可以了,參與計算的變數並不需要$符號。整數運算涉及到的運算子有:++ 、--、+、-、*、/、%、+=、**(冪運算); 還有不常用的:<<、 >>、 ^、 &、 !、 |、 ~,這些在C語言中倒挺常用的,shell用來做這些位元運算的需求也太2b了。 除了這些運算還支援邏輯:<、>、<=、>=、==、!=、&&、||。
$(( expr ))運算式只能計算整數,不能用於小數的計算。如果,要做小數的計算,可以使用linux上的命令列計算機bc來完成。
例子:
echo "1.5 + 1" | bc
代碼產生
程式員應該是”懶惰“的,”懶惰“的程式員可以創造出更多的自動化工具。我喜歡用bash來輔助我完成一些代碼的自動產生,當然不是所有的代碼都可以自動產生,如果那樣也就不需要程式員了。我認為好的程式一定是可擴充的,最好的可擴充程式是”完成程式架構等開發後的功能需求開發階段不再需要過分的寫代碼,只需要填代碼“,能夠填的代碼,一定是可以自動產生的,這樣的程式開發就將形成一個良性的迴圈。最近我非常熱衷於重構My Code,盡量讓My Code可以用bash指令碼來自動產生,這將極大的提高我的開發效率。
如何自動產生代碼,產生什麼樣的代碼,是根據自己的實際情況來完成的,這裡沒法介紹具體的代碼產生,只能介紹bash用來完成代碼產生的工具——cat。使用過linux的人都知道cat,這裡就不詳細說明了,只展示一段簡單的bash指令碼輸出hello world程式。
#!/bin/sh
cat << END > hello.c
#include <stdio.h>
int main(void)
{
printf("hello world\n");
return 0;
}
END
在cat到END之間,你可以像在編輯器裡一樣編寫自己的代碼,這裡寫成什麼樣子,輸出到源檔案裡就是什麼樣子,包括縮排等格式。當然,真正有意義的代碼產生不會像這裡的hello world這麼簡單,你必須得根據自己的實際情況去”拼湊“出自動化的代碼。這裡僅僅是展示使用cat來產生一個源碼檔案而已。要想自動產生真正有意義的,能提高自己開發效率的代碼,必須得學習絕大多數的bash編程知識。
source(.)與export
使用linux的人都知道,我們敲打命令的終端其實就是運行在一個shell裡的,這個shell裡定義了很多的環境變數,比如:PATH,HOME等。然後,我在終端運行一個bash指令碼程式後,這個bash指令碼其實是運行在終端所在的shell fork出來的一個子shell進程裡。那麼這個子shell為什麼也能夠取得PATH,HOME這樣的環境變數呢?這就是因為這些變數在其父shell裡被聲明為了export。export指令可以讓變數傳遞到子shell裡,這在一個bash程式調用另外一個bash程式時,共用變數是非常的有用的。
export可以讓變數傳遞到子shell裡,但卻不能讓子shell的變數傳遞到父shell裡,寫到這裡,我覺得我這個轉折句說的很屁話。不過,bash中可以使用source命令來完成類似的功能。比如:我們可以在終端裡用source來運行一個bash程式,那麼這個程式中定義的變數,在程式結束後,依然存在。這是因為,通過source來執行一個指令碼,並不會去fork出一個子shell來執行,而是直接在當前的shell裡執行指令碼中的所有語句。說到這裡,我想你也會和我一樣認為,source就相當於C語言中的include指令。
當一個指令碼過大時,我熱衷於將它模組化,然後將一些模組獨立出來作為一個單獨的檔案,再在主檔案中通過source指令來執行之。如果,你和我一樣的懶,懶得連source都不想寫,那你完全可以使用點號(.)來替代source。
逐行讀檔案
當你在做文本處理的時候,可能需要將一個檔案的內容一行一行的依次讀出,然後加以處理,bash做這個事情是非常的方便的。
例子:
while read line
do
echo $line
done < txt.log
例子展示的是從檔案txt.log中逐行的讀出來賦值給變數line。如果,你的檔案內容是結構化的,比如:每行都是兩列,你想單獨的處理每一列的內容,那麼bash提供了更友好的方式去逐行讀檔案。
例子:
while read c1 c2
do
echo $c1 : $c2
done
這樣read檔案,第一列就賦值給了c1,第二列就賦值給了c2。這有點ruby,python,go等優秀語言支援的多變數賦值的味道。
格式化輸出
bash中最常用的輸出莫過於echo了,可是echo不能格式化的輸出,在自動產生代碼這樣的情況下,我想你有可能需要格式化輸出,讓代碼更加的整齊美觀。C語言有函數printf支援格式化輸出,linux也有printf工具,讓你如C語言一般隨心所欲的輸出。
printf的大致用法舉例:
printf "my name is %s, I am %d years old.\n" skoo 25
我想linux printf的使用和C語言幾乎沒太大的區別,詳細的用法可以man printf去。
附:sed和awk
我想sed和awk,絕對是兩大神器,不過真正能駕馭它們的人並不多。我也只是略知皮毛,因此就不在這裡廢話了。學習任何一門程式設計語言,到體現生產力的時候,都是拼庫。bash沒有庫函數的說法,但卻有著豐富的linux命令,要寫出真正強大有意義的bash程式,不得不學習一些linux命令,只有掌握了足夠的linux命令,才能駕輕就熟。就像大多數java程式員一樣,沒有豐富的庫,只掌握文法的他們也許什麼有意義的事情都幹不了。C/C++程式員也不例外,雖然他們熱衷於用僅有的一點文法去重造輪子。