1. 走進(jìn) Shell 編程的大門
1.1 為什么要學(xué)Shell?
學(xué)一個(gè)東西,我們大部分情況都是往實(shí)用性方向著想。從工作角度來講,學(xué)習(xí) Shell 是為了提高我們自己工作效率,提高產(chǎn)出,讓我們?cè)诟俚臅r(shí)間完成更多的事情。
很多人會(huì)說 Shell 編程屬于運(yùn)維方面的知識(shí)了,應(yīng)該是運(yùn)維人員來做,我們做后端開發(fā)的沒必要學(xué)。我覺得這種說法大錯(cuò)特錯(cuò),相比于專門做Linux運(yùn)維的人員來說,我們對(duì) Shell 編程掌握程度的要求要比他們低,但是shell編程也是我們必須要掌握的!
目前Linux系統(tǒng)下最流行的運(yùn)維自動(dòng)化語(yǔ)言就是Shell和Python了。
兩者之間,Shell幾乎是IT企業(yè)必須使用的運(yùn)維自動(dòng)化編程語(yǔ)言,特別是在運(yùn)維工作中的服務(wù)監(jiān)控、業(yè)務(wù)快速部署、服務(wù)啟動(dòng)停止、數(shù)據(jù)備份及處理、日志分析等環(huán)節(jié)里,shell是不可缺的。Python 更適合處理復(fù)雜的業(yè)務(wù)邏輯,以及開發(fā)復(fù)雜的運(yùn)維軟件工具,實(shí)現(xiàn)通過web訪問等。Shell是一個(gè)命令解釋器,解釋執(zhí)行用戶所輸入的命令和程序。一輸入命令,就立即回應(yīng)的交互的對(duì)話方式。
另外,了解 shell 編程也是大部分互聯(lián)網(wǎng)公司招聘后端開發(fā)人員的要求。下圖是我截取的一些知名互聯(lián)網(wǎng)公司對(duì)于 Shell 編程的要求。

1.2 什么是 Shell?
簡(jiǎn)單來說“Shell編程就是對(duì)一堆Linux命令的邏輯化處理”。
W3Cschool 上的一篇文章是這樣介紹 Shell的,如下圖所示。

1.3 Shell 編程的 Hello World
學(xué)習(xí)任何一門編程語(yǔ)言第一件事就是輸出HelloWord了!下面我會(huì)從新建文件到shell代碼編寫來說下Shell 編程如何輸出Hello World。
(1)新建一個(gè)文件 helloworld.sh :touch helloworld.sh,擴(kuò)展名為 sh(sh代表Shell)(擴(kuò)展名并不影響腳本執(zhí)行,見名知意就好,如果你用 php 寫 shell 腳本,擴(kuò)展名就用 php 好了)
(2) 使腳本具有執(zhí)行權(quán)限:chmod +x helloworld.sh
(3) 使用 vim 命令修改helloworld.sh文件:vim helloworld.sh(vim 文件------>進(jìn)入文件----->命令模式------>按i進(jìn)入編輯模式----->編輯文件 ------->按Esc進(jìn)入底行模式----->輸入:wq/q! (輸入wq代表寫入內(nèi)容并退出,即保存;輸入q!代表強(qiáng)制退出不保存。))
helloworld.sh 內(nèi)容如下:
#!/bin/bash
#第一個(gè)shell小程序,echo 是linux中的輸出命令。
echo "helloworld!"
shell中 # 符號(hào)表示注釋。shell 的第一行比較特殊,一般都會(huì)以#!開始來指定使用的 shell 類型。在linux中,除了bash shell以外,還有很多版本的shell, 例如zsh、dash等等...不過bash shell還是我們使用最多的。
(4) 運(yùn)行腳本:./helloworld.sh 。(注意,一定要寫成 ./helloworld.sh ,而不是 helloworld.sh ,運(yùn)行其它二進(jìn)制的程序也一樣,直接寫 helloworld.sh ,linux 系統(tǒng)會(huì)去 PATH 里尋找有沒有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的當(dāng)前目錄通常不在 PATH 里,所以寫成 helloworld.sh 是會(huì)找不到命令的,要用./helloworld.sh 告訴系統(tǒng)說,就在當(dāng)前目錄找。)

2. Shell 變量
2.1 Shell 編程中的變量介紹
Shell編程中一般分為三種變量:
- 我們自己定義的變量(自定義變量): 僅在當(dāng)前 Shell 實(shí)例中有效,其他 Shell 啟動(dòng)的程序不能訪問局部變量。
-
Linux已定義的環(huán)境變量(環(huán)境變量, 例如:
HOME 等..., 這類變量我們可以直接使用),使用
env命令可以查看所有的環(huán)境變量,而set命令既可以查看環(huán)境變量也可以查看自定義變量。 - Shell變量 :Shell變量是由 Shell 程序設(shè)置的特殊變量。Shell 變量中有一部分是環(huán)境變量,有一部分是局部變量,這些變量保證了 Shell 的正常運(yùn)行
常用的環(huán)境變量:
PATH 決定了shell將到哪些目錄中尋找命令或程序
HOME 當(dāng)前用戶主目錄
HISTSIZE 歷史記錄數(shù)
LOGNAME 當(dāng)前用戶的登錄名
HOSTNAME 指主機(jī)的名稱
SHELL 當(dāng)前用戶Shell類型
LANGUGE 語(yǔ)言相關(guān)的環(huán)境變量,多語(yǔ)言可以修改此環(huán)境變量
MAIL 當(dāng)前用戶的郵件存放目錄
PS1 基本提示符,對(duì)于root用戶是#,對(duì)于普通用戶是$
使用 Linux 已定義的環(huán)境變量:
比如我們要看當(dāng)前用戶目錄可以使用:echo $HOME命令;如果我們要看當(dāng)前用戶Shell類型 可以使用echo $SHELL命令??梢钥闯?,使用方法非常簡(jiǎn)單。
使用自己定義的變量:
#!/bin/bash
#自定義變量hello
hello="hello world"
echo $hello
echo "helloworld!"

Shell 編程中的變量名的命名的注意事項(xiàng):
- 命名只能使用英文字母,數(shù)字和下劃線,首個(gè)字符不能以數(shù)字開頭,但是可以使用下劃線(_)開頭。
- 中間不能有空格,可以使用下劃線(_)。
- 不能使用標(biāo)點(diǎn)符號(hào)。
- 不能使用bash里的關(guān)鍵字(可用help命令查看保留關(guān)鍵字)。
2.2 Shell 字符串入門
字符串是shell編程中最常用最有用的數(shù)據(jù)類型(除了數(shù)字和字符串,也沒啥其它類型好用了),字符串可以用單引號(hào),也可以用雙引號(hào)。這點(diǎn)和Java中有所不同。
單引號(hào)字符串:
#!/bin/bash
name='SnailClimb'
hello='Hello, I am '$name'!'
echo $hello
輸出內(nèi)容:
Hello, I am SnailClimb!
雙引號(hào)字符串:
#!/bin/bash
name='SnailClimb'
hello="Hello, I am "$name"!"
echo $hello
輸出內(nèi)容:
Hello, I am SnailClimb!
Shell 字符串常見操作
拼接字符串:
#!/bin/bash
name="SnailClimb"
# 使用雙引號(hào)拼接
greeting="hello, "$name" !"
greeting_1="hello, ${name} !"
echo $greeting $greeting_1
# 使用單引號(hào)拼接
greeting_2='hello, '$name' !'
greeting_3='hello, ${name} !'
echo $greeting_2 $greeting_3
輸出結(jié)果:

獲取字符串長(zhǎng)度:
#!/bin/bash
#獲取字符串長(zhǎng)度
name="SnailClimb"
# 第一種方式
echo ${#name} #輸出 10
# 第二種方式
expr length "$name";
輸出結(jié)果:
10
10
使用 expr 命令時(shí),表達(dá)式中的運(yùn)算符左右必須包含空格,如果不包含空格,將會(huì)輸出表達(dá)式本身:
expr 5+6 // 直接輸出 5+6
expr 5 + 6 // 輸出 11
對(duì)于某些運(yùn)算符,還需要我們使用符號(hào)\進(jìn)行轉(zhuǎn)義,否則就會(huì)提示語(yǔ)法錯(cuò)誤。
expr 5 * 6 // 輸出錯(cuò)誤
expr 5 \* 6 // 輸出30
截取子字符串:
簡(jiǎn)單的字符串截取:
#從字符串第 1 個(gè)字符開始往后截取 10 個(gè)字符
str="SnailClimb is a great man"
echo ${str:0:10} #輸出:SnailClimb
根據(jù)表達(dá)式截?。?/p>
#!bin/bash
#author:amau
var="http://www.runoob.com/linux/linux-shell-variable.html"
s1=${var%%t*}#h
s2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h
s3=${var%%.*}#http://www
s4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html
s5=${var##*/}#linux-shell-variable.html
2.3 Shell 數(shù)組
bash支持一維數(shù)組(不支持多維數(shù)組),并且沒有限定數(shù)組的大小。我下面給了大家一個(gè)關(guān)于數(shù)組操作的 Shell 代碼示例,通過該示例大家可以知道如何創(chuàng)建數(shù)組、獲取數(shù)組長(zhǎng)度、獲取/刪除特定位置的數(shù)組元素、刪除整個(gè)數(shù)組以及遍歷數(shù)組。
#!/bin/bash
array=(1 2 3 4 5);
# 獲取數(shù)組長(zhǎng)度
length=${#array[@]}
# 或者
length2=${#array[*]}
#輸出數(shù)組長(zhǎng)度
echo $length #輸出:5
echo $length2 #輸出:5
# 輸出數(shù)組第三個(gè)元素
echo ${array[2]} #輸出:3
unset array[1]# 刪除下標(biāo)為1的元素也就是刪除第二個(gè)元素
for i in ${array[@]};do echo $i ;done # 遍歷數(shù)組,輸出: 1 3 4 5
unset arr_number; # 刪除數(shù)組中的所有元素
for i in ${array[@]};do echo $i ;done # 遍歷數(shù)組,數(shù)組元素為空,沒有任何輸出內(nèi)容
3. Shell 基本運(yùn)算符
說明:圖片來自《菜鳥教程》
Shell 編程支持下面幾種運(yùn)算符
- 算數(shù)運(yùn)算符
- 關(guān)系運(yùn)算符
- 布爾運(yùn)算符
- 字符串運(yùn)算符
- 文件測(cè)試運(yùn)算符
3.1 算數(shù)運(yùn)算符

我以加法運(yùn)算符做一個(gè)簡(jiǎn)單的示例(注意:不是單引號(hào),是反引號(hào)):
#!/bin/bash
a=3;b=3;
val=`expr $a + $b`
#輸出:Total value : 6
echo "Total value : $val
3.2 關(guān)系運(yùn)算符
關(guān)系運(yùn)算符只支持?jǐn)?shù)字,不支持字符串,除非字符串的值是數(shù)字。

通過一個(gè)簡(jiǎn)單的示例演示關(guān)系運(yùn)算符的使用,下面shell程序的作用是當(dāng)score=100的時(shí)候輸出A否則輸出B。
#!/bin/bash
score=90;
maxscore=100;
if [ $score -eq $maxscore ]
then
echo "A"
else
echo "B"
fi
輸出結(jié)果:
B
3.3 邏輯運(yùn)算符

示例:
#!/bin/bash
a=$(( 1 && 0))
# 輸出:0;邏輯與運(yùn)算只有相與的兩邊都是1,與的結(jié)果才是1;否則與的結(jié)果是0
echo $a;
3.4 布爾運(yùn)算符

這里就不做演示了,應(yīng)該挺簡(jiǎn)單的。
3.5 字符串運(yùn)算符

簡(jiǎn)單示例:
#!/bin/bash
a="abc";
b="efg";
if [ $a = $b ]
then
echo "a 等于 b"
else
echo "a 不等于 b"
fi
輸出:
a 不等于 b
3.6 文件相關(guān)運(yùn)算符

使用方式很簡(jiǎn)單,比如我們定義好了一個(gè)文件路徑file="/usr/learnshell/test.sh" 如果我們想判斷這個(gè)文件是否可讀,可以這樣if [ -r $file ] 如果想判斷這個(gè)文件是否可寫,可以這樣-w $file,是不是很簡(jiǎn)單。
4. shell流程控制
4.1 if 條件語(yǔ)句
簡(jiǎn)單的 if else-if else 的條件語(yǔ)句示例
#!/bin/bash
a=3;
b=9;
if [ $a -eq $b ]
then
echo "a 等于 b"
elif [ $a -gt $b ]
then
echo "a 大于 b"
else
echo "a 小于 b"
fi
輸出結(jié)果:
a 小于 b
相信大家通過上面的示例就已經(jīng)掌握了 shell 編程中的 if 條件語(yǔ)句。不過,還要提到的一點(diǎn)是,不同于我們常見的 Java 以及 PHP 中的 if 條件語(yǔ)句,shell if 條件語(yǔ)句中不能包含空語(yǔ)句也就是什么都不做的語(yǔ)句。
4.2 for 循環(huán)語(yǔ)句
通過下面三個(gè)簡(jiǎn)單的示例認(rèn)識(shí) for 循環(huán)語(yǔ)句最基本的使用,實(shí)際上 for 循環(huán)語(yǔ)句的功能比下面你看到的示例展現(xiàn)的要大得多。
輸出當(dāng)前列表中的數(shù)據(jù):
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
產(chǎn)生 10 個(gè)隨機(jī)數(shù):
#!/bin/bash
for i in {0..9};
do
echo $RANDOM;
done
輸出1到5:
通常情況下 shell 變量調(diào)用需要加 $,但是 for 的 (()) 中不需要,下面來看一個(gè)例子:
#!/bin/bash
for((i=1;i<=5;i++));do
echo $i;
done;
4.3 while 語(yǔ)句
基本的 while 循環(huán)語(yǔ)句:
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
while循環(huán)可用于讀取鍵盤信息:
echo '按下 <CTRL-D> 退出'
echo -n '輸入你最喜歡的電影: '
while read FILM
do
echo "是的!$FILM 是一個(gè)好電影"
done
輸出內(nèi)容:
按下 <CTRL-D> 退出
輸入你最喜歡的電影: 變形金剛
是的!變形金剛 是一個(gè)好電影
無線循環(huán):
while true
do
command
done
5. shell 函數(shù)
5.1 不帶參數(shù)沒有返回值的函數(shù)
#!/bin/bash
hello(){
echo "這是我的第一個(gè) shell 函數(shù)!"
}
echo "-----函數(shù)開始執(zhí)行-----"
hello
echo "-----函數(shù)執(zhí)行完畢-----"
輸出結(jié)果:
-----函數(shù)開始執(zhí)行-----
這是我的第一個(gè) shell 函數(shù)!
-----函數(shù)執(zhí)行完畢-----
5.2 有返回值的函數(shù)
輸入兩個(gè)數(shù)字之后相加并返回結(jié)果:
#!/bin/bash
funWithReturn(){
echo "輸入第一個(gè)數(shù)字: "
read aNum
echo "輸入第二個(gè)數(shù)字: "
read anotherNum
echo "兩個(gè)數(shù)字分別為 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
echo "輸入的兩個(gè)數(shù)字之和為 $?"
輸出結(jié)果:
輸入第一個(gè)數(shù)字:
1
輸入第二個(gè)數(shù)字:
2
兩個(gè)數(shù)字分別為 1 和 2 !
輸入的兩個(gè)數(shù)字之和為 3
5.3 帶參數(shù)的函數(shù)
#!/bin/bash
funWithParam(){
echo "第一個(gè)參數(shù)為 $1 !"
echo "第二個(gè)參數(shù)為 $2 !"
echo "第十個(gè)參數(shù)為 $10 !"
echo "第十個(gè)參數(shù)為 ${10} !"
echo "第十一個(gè)參數(shù)為 ${11} !"
echo "參數(shù)總數(shù)有 $# 個(gè)!"
echo "作為一個(gè)字符串輸出所有參數(shù) $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
輸出結(jié)果:
第一個(gè)參數(shù)為 1 !
第二個(gè)參數(shù)為 2 !
第十個(gè)參數(shù)為 10 !
第十個(gè)參數(shù)為 34 !
第十一個(gè)參數(shù)為 73 !
參數(shù)總數(shù)有 11 個(gè)!
作為一個(gè)字符串輸出所有參數(shù) 1 2 3 4 5 6 7 8 9 34 73 !