一、 Shell 編程 1. 簡介 Shell 是一個用 C 語言編寫的程序,通過 Shell 用戶可以訪問操作系統(tǒng)內(nèi)核服務。
Shell 既是一種命令語言,又是一種程序設計語言。
Shell script 是一種為 shell 編寫的腳本程序。Shell 編程一般指 shell 腳本編程,不是指開發(fā) shell 自身。
Shell 編程跟 java、php 編程一樣,只要有一個能編寫代碼的文本編輯器和一個能解釋執(zhí)行的腳本解釋器就可以了。
Linux 的 Shell 解釋器 種類眾多,一個系統(tǒng)可以存在多個 shell,可以通過 cat /etc/shells 命令查看系統(tǒng)中安裝的 shell 解釋器。
Bash 由于易用和免費,在日常工作中被廣泛使用。同時,Bash 也是大多數(shù) Linux 系統(tǒng)默認的 Shell。
shell 解釋器
java 需要虛擬機解釋器 , 同理 shell 腳本也需要解析器 ,如下所示:
[root@node01 shells] cat /etc/shells /bin/sh /bin/bash /sbin/nologin /bin/dash /bin/tcsh /bin/csh
2. 快速入門 1) 編寫腳本 新建 /export/hello.sh 文件,內(nèi)容如下:
#!/bin/bash echo 'hello world'
#!是一個約定的標記,它告訴系統(tǒng)這個腳本需要什么解釋器來執(zhí)行,即使用哪一種 Shell。
echo 命令用于向窗口輸出文本。
2) 執(zhí)行 shell 腳本 執(zhí)行方式一 [root@node01 shells] /bin/sh 01.sh hello world
或
[root@node01 shells] /bin/bash 01.sh hello world
問題:bash 和 sh 是什么關系?
答:sh 是 bash 的 快捷方式
執(zhí)行方式二 方式一的簡化方式:
[root@node01 shells] bash hello.sh hello world [root@node01 shells] sh hello.sh hello world
問題:為什么可以省略 /bin/
答:因為 PATH 環(huán)境變量中增加了 /bin/目錄, 所以 使用/bin/sh 等類似指令時, 可以省略 /bin
執(zhí)行方式三 ./文件名
[root@node01 shells] ./hello.sh -bash: ./01.sh: 權限不夠
問題:權限不夠怎么辦?
[root@node01 shells] chmod 755 hello.sh# 再次執(zhí)行: [root@node01 shells] ./hello.sh hello world!
3. shell 變量 1) 簡介 在 shell 腳本中, 定義變量時,變量名不加美元符號$
,如:
your_name='runoob.com'
注意 : 變量名和等號之間不能有空格 ,這可能和你熟悉的所有編程語言都不一樣。
同時,變量名的命名須遵循如下規(guī)則:
命名只能使用英文字母,數(shù)字和下劃線,首個字符不能以數(shù)字開頭。 不能使用 bash 里的關鍵字(可用 help 命令查看保留關鍵字)。 有效的 Shell 變量名示例如下:
RUNOOB LD_LIBRARY_PATH _var var2
無效的變量命名:
?var=123 user*name=runoob
除了顯式地直接賦值,還可以用語句給變量賦值,如:
for file in `ls /etc`
或
for file in $(ls /etc)
以上語句將 /etc 下目錄的文件名循環(huán)出來。
2) 使用變量 使用一個定義過的變量,只要在變量名前面加美元符號即可,如:
your_name='zhangsan' echo $your_name echo ${your_name}
變量名外面的花括號是可選的,加不加都行,加花括號是為了幫助解釋器識別變量的邊界,比如下面這種情況:
for skill in java php python; do echo 'I am good at ${skill} Script' done
如果不給 skill 變量加花括號,寫成echo 'I am good at $skillScript'
,解釋器就會把$skillScript
當成一個變量(其值為空),代碼執(zhí)行結(jié)果就不是我們期望的樣子了。
推薦給所有變量加上花括號,這是個好的編程習慣。
已定義的變量,可以被重新定義,如:
your_name='tom' echo $your_name your_name='alibaba' echo $your_name
這樣寫是合法的,但注意,第二次賦值的時候不能寫$your_name='alibaba'
,使用變量的時候才加美元符。
3) 刪除變量 使用 unset 命令可以刪除變量。語法:
unset variable_name
變量被刪除后不能再次使用。unset 命令不能刪除只讀變量。
實例
#!/bin/sh myUrl='http://www.runoob.com' unset myUrlecho $myUrl
以上實例執(zhí)行將沒有任何輸出。
4) 只讀變量 使用 readonly 命令可以將變量定義為只讀變量,只讀變量的值不能被改變。
下面的例子嘗試更改只讀變量,結(jié)果報錯:
#!/bin/bash myUrl='http://www.google.com' readonly myUrl myUrl='http://www.runoob.com'
運行腳本,結(jié)果如下:
/bin/sh: NAME: This variable is read only.
4. 字符串 字符串是 shell 編程中最常用最有用的數(shù)據(jù)類型(除了數(shù)字和字符串,也沒啥其它類型好用了),字符串可以用單引號,也可以用雙引號,也可以不用引號。
1) 單引號 skill='java' str='I am goot at $skill' echo $str
輸出結(jié)果為:
I am goot at $skill
單引號字符串的限制:
單引號里的任何字符都會原樣輸出,單引號字符串中的變量是無效 的; 單引號字串中不能出現(xiàn)單獨一個的單引號(對單引號使用轉(zhuǎn)義符后也不行),但可成對出現(xiàn),作為字符串拼接使用。 2) 雙引號 skill='java' str='I am goot at $skill ' echo $str
輸出結(jié)果為:
I am goot at java
雙引號的優(yōu)點:
雙引號里可以出現(xiàn)轉(zhuǎn)義字符 3) 獲取字符串長度 skill='java' echo ${skill} # 輸出結(jié)果: java echo ${#skill} # 輸出結(jié)果: 4 或者: expr length 'iamlilei' #輸出結(jié)果: 8
4) 提取子字符串 substring(2)
substring(2,3)
以下實例從字符串第 2 個字符開始截取 4 個字符:
str='I am goot at $skill ' echo ${str:2} # 輸出結(jié)果為: am goot at java 從第二個字符開始截取,到結(jié)尾 echo ${str:2:2} # 輸出結(jié)果為: am 從第二個字符開始截取,截取2個字符
5) 查找子字符串 查找字符 i 或 o 的位置(哪個字母先出現(xiàn)就計算哪個 ):
str='I am goot at $skill ' echo `expr index '$str ' am` # 輸出是: 3 或者: expr index 'iamlilei' am #輸出結(jié)果: 2 返回在STRING中找到CHARS字符串的位置;否則,返回0
注意: 以上腳本中 ` 是反引號(Esc 下面的),而不是單引號 ',不要看錯了哦。
5. 傳遞參數(shù) 我們可以在執(zhí)行 Shell 腳本時,向腳本傳遞參數(shù),腳本內(nèi)獲取參數(shù)的格式為:$n
。
n 代表一個數(shù)字,1 為執(zhí)行腳本的第一個參數(shù),2 為執(zhí)行腳本的第二個參數(shù),以此類推……
實例
以下實例我們向腳本傳遞三個參數(shù),并分別輸出,其中 $0
為執(zhí)行的文件名:
vim /export/sh/param.sh
#!/bin/bash echo 'Shell 傳遞參數(shù)實例!' ;echo '執(zhí)行的文件名:$0 ' ;echo '第一個參數(shù)為:$1 ' ;echo '第二個參數(shù)為:$2 ' ;echo '第三個參數(shù)為:$3 ' ;
為腳本設置可執(zhí)行權限,并執(zhí)行腳本,輸出結(jié)果如下所示:
$ chmod 755 param.sh $ ./param.sh 1 2 3
Shell 傳遞參數(shù)實例!
執(zhí)行的文件名:./param.sh 第一個參數(shù)為:1 第二個參數(shù)為:2 第三個參數(shù)為:3
另外,還有幾個特殊字符用來處理參數(shù):
參數(shù)處理 說明 $#
傳遞到腳本的參數(shù)個數(shù) $*
以一個單字符串顯示所有向腳本傳遞的參數(shù)。如'$*'
用「'」括起來的情況、以'$1 $2 … $n'
的形式輸出所有參數(shù)。 $$
腳本運行的當前進程 ID 號 $!
后臺運行的最后一個進程的 ID 號 $@
與 $*
相同,但是使用時加引號,并在引號中返回每個參數(shù)。 如'$@'
用「'」括起來的情況、以'$1' '$2' … '$n'
的形式輸出所有參數(shù)。 $-
顯示 Shell 使用的當前選項,與 set 命令功能相同。 $?
顯示最后命令的退出狀態(tài)。0 表示沒有錯誤,其他任何值表明有錯誤。
#!/bin/bash echo 'Shell 傳遞參數(shù)實例!' ;echo '第一個參數(shù)為:$1 ' ;echo '參數(shù)個數(shù)為:$# ' ;echo '傳遞的參數(shù)作為一個字符串顯示:$*' ;
執(zhí)行腳本,輸出結(jié)果如下所示:
$ chmod +x test.sh $ ./test.sh 1 2 3 Shell 傳遞參數(shù)實例! 第一個參數(shù)為:1 參數(shù)個數(shù)為:3 傳遞的參數(shù)作為一個字符串顯示:1 2 3
$*
與 $@
區(qū)別:
不同點:只有在雙引號中體現(xiàn)出來。假設在腳本運行時寫了三個參數(shù) 1、2、3,,則 ' * ' 等價于 '1 2 3'(傳遞了一個參數(shù)),而 '@' 等價于 '1' '2' '3'(傳遞了三個參數(shù))。 #!/bin/bash echo '-- $* 演示 ---' for i in '$*' ; do echo $i done echo '-- $@ 演示 ---' for i in '$@ ' ; do echo $i done
執(zhí)行腳本,輸出結(jié)果如下所示:
$ chmod +x test.sh $ ./test.sh 1 2 3 -- $* 演示 --- 1 2 3 -- $@ 演示 --- 1 2 3
6. Shell 算術運算符 1) 簡介 Shell 和其他編程一樣,支持 包括:算術、關系、布爾、字符串等運算符。
原生 bash 不支持 簡單的數(shù)學運算,但是可以通過其他命令來實現(xiàn),例如 expr。
expr 是一款表達式計算工具,使用它能完成表達式的求值操作。
例如,兩個數(shù)相加:
val=`expr 2 + 2`echo $val
注意:
表達式和運算符之間要有空格 ,例如 2+2
是不對的,必須寫成 2 + 2
。
完整的表達式要被 ` 包含,注意不是單引號,在 Esc 鍵下邊。
下表列出了常用的算術運算符,假定變量 a 為 10,變量 b 為 20:
運算符 說明 舉例 + 加法 expr $a + $b
結(jié)果為 30。- 減法 expr $a - $b
結(jié)果為 -10。* 乘法 expr $a * $b
結(jié)果為 200。/ 除法 expr $b / $a
結(jié)果為 2。% 取余 expr $b % $a
結(jié)果為 0。= 賦值 a=$b
將把變量 b 的值賦給 a。== 相等。用于比較兩個數(shù)字,相同則返回 true。 [ $a == $b ]
返回 false。!= 不相等。用于比較兩個數(shù)字,不相同則返回 true。 [ $a != $b ]
返回 true。
注意: 條件表達式要放在方括號之間,并且要有空格,例如: [$a==$b]
是錯誤的,必須寫成 [ $a == $b ]
。
2) 例子 #!/bin/bash a=4 b=20#加法運算 each expr $a + $b #減法運算 echo expr $a - $b #乘法運算,注意*號前面需要反斜杠 echo expr $a \* $b #除法運算 echo $a / $b 此外,還可以通過(())、$(())、$[]進行算術運算。 ((a++))echo 'a = $a ' c=$((a + b)) d=$[a + b]echo 'c = $c ' echo 'd = $d '
7. 流程控制 1) if else 1.1 if
if 語句語法格式:
if condition; then command1 command2 ... commandNfi
demo
[root@hadoop01 export ]# cat if_test.sh #!/bin/bash a=20if [ $a -gt 10 ]; then echo 'a 大于 10' fi
末尾的 fi 就是 if 倒過來拼寫,后面還會遇到類似的。
1.2 if else
if else 語法格式:
if condition; then command1 command2 ... commandNelse command fi
1.3 if else-if else
if else-if else 語法格式:
if condition1; then command1elif condition2; then command2else commandNfi
以下實例判斷兩個變量是否相等:
關系運算符
關系運算符只支持數(shù)字,不支持字符串,除非字符串的值是數(shù)字。
下表列出了常用的關系運算符,假定變量 a 為 10,變量 b 為 20:
運算符 說明 英文 舉例 -eq 檢測兩個數(shù)是否相等,相等返回 true。 equal [ $a -eq $b ]
返回 false。-ne 檢測兩個數(shù)是否不相等,不相等返回 true。 not equal [ $a -ne $b ]
返回 true。-gt 檢測左邊的數(shù)是否大于右邊的,如果是,則返回 true。 greater than [ $a -gt $b ]
返回 false。-lt 檢測左邊的數(shù)是否小于右邊的,如果是,則返回 true。 less than [ $a -lt $b ]
返回 true。-ge 檢測左邊的數(shù)是否大于等于右邊的,如果是,則返回 true。 Greater than or equal to [ $a -ge $b ]
返回 false。-le 檢測左邊的數(shù)是否小于等于右邊的,如果是,則返回 true。 Less than or equal to [ $a -le $b ]
返回 true。
案例:
[root@hadoop01 export ]# cat if_test.sh #!/bin/bash a=20 b=10# 需求1: 判斷 a 是否 100 if [ $a > 100 ]; then echo '$a 大于 100' fi # 需求2: 判斷 a 是否等于 b if [ $a -eq $b ]; then echo '$a 等于 $b ' else echo '$a 不等于 $b ' fi # 需求3: 判斷 a 與 b 比較 if [ $a -lt $b ]; then echo '$a 小于 $b ' elif [ $a -eq $b ]; then echo '$a 等于 $b ' else echo '$a 大于 $b ' fi # 需求4: 判斷 (a + 10) 和 (b * b) 比較大小 if test $[ a + 10 ] -gt $[ b * b ]; then echo '(a+10) 大于 (b * b)' else echo '(a+10) 小于或等于 (b*b)' fi
2) for 循環(huán) 格式 for variable in (list); do command command ...done
練習 # 需求1: 遍歷 1~5 # 需求2: 遍歷 1~100 # 需求3: 遍歷 1~100之間的奇數(shù) # 需求4: 遍歷 根目錄 下的內(nèi)容
代碼如下:
#!/bin/bash # 需求1: 遍歷 1~5 for i in 1 2 3 4 5; do echo $i ;done # 需求2: 遍歷 1~100 for i in {1..100}; do echo $i done # 需求3: 遍歷 1~100之間的奇數(shù) for i in {1..100..2}; do echo $i done # 需求4: 遍歷 根目錄 下的內(nèi)容 for f in `ls /`; do echo $f done
3) while 語句 while 循環(huán)用于不斷執(zhí)行一系列命令,也用于從輸入文件中讀取數(shù)據(jù);命令通常為測試條件。其格式為:
while condition; do command done
需求: 計算 1~100 的和
#!/bin/bash sum=0 i=1while [ $i -le 100 ]; do sum=$[ sum + i] i=$[ i + 1 ]done echo $sum
運行腳本,輸出:
5050
使用中使用了 Bash let 命令,它用于執(zhí)行一個或多個表達式,變量計算中不需要加上 $
來表示變量,具體可查閱:Bash let 命令:http://www.runoob.com/linux/linux-comm-let.html。
4) 無限循環(huán) 無限循環(huán)語法格式:
while true ; do command done
需求: 每隔1秒 打印一次當前時間
#!/bin/bash while true ; do date sleep 1done
5) case(switch) Shell case 語句為多選擇語句??梢杂?case 語句匹配一個值與一個模式,如果匹配成功,執(zhí)行相匹配的命令。case 語句格式如下:
case 值 in 模式1) command1 command2 ... commandN ;; 模式2) command1 command2 ... commandN ;;esac
case 工作方式如上所示。取值后面必須為單詞 in,每一模式必須以右括號結(jié)束。取值可以為變量或常數(shù)。匹配發(fā)現(xiàn)取值符合某一模式后,其間所有命令開始執(zhí)行直至 ;;
。
取值將檢測匹配的每一個模式。一旦模式匹配,則執(zhí)行完匹配模式相應命令后不再繼續(xù)其他模式。如果無一匹配模式,使用星號 *
捕獲該值,再執(zhí)行后面的命令。
下面的腳本提示輸入 1 到 4,與每一種模式進行匹配:
echo '輸入 1 到 4 之間的數(shù)字:' read aNumcase $aNum in 1) echo '你選擇了 1' ;; 2) echo '你選擇了 2' ;; 3) echo '你選擇了 3' ;; 4) echo '你選擇了 4' ;; *) echo '你沒有輸入 1 到 4 之間的數(shù)字' ;;esac
輸入不同的內(nèi)容,會有不同的結(jié)果,例如:
輸入 1 到 4 之間的數(shù)字: 你輸入的數(shù)字為: 3 你選擇了 3
6) 跳出循環(huán) 在循環(huán)過程中,有時候需要在未達到循環(huán)結(jié)束條件時強制跳出循環(huán),Shell 使用兩個命令來實現(xiàn)該功能:break 和 continue。
break 命令break 命令允許跳出所有循環(huán)(終止執(zhí)行后面的所有循環(huán))。
需求: 執(zhí)行死循環(huán) 每隔1秒打印當前時間, 執(zhí)行10次停止
#!/bin/bash # 需求: 執(zhí)行死循環(huán) 每隔1秒打印當前時間, 執(zhí)行10次停止 i=0;while true ; do sleep 1 echo $i `date +'%Y-%m-%d %H:%M:%S' ` i=$[ i + 1] if [ $i -eq 10 ]; then break fi done
continue continue 命令與 break 命令類似,只有一點差別,它不會跳出所有循環(huán),僅僅跳出當前循環(huán)。
需求: 打印 1~30, 注意 跳過3的倍數(shù)
#!/bin/bash # 需求: 打印 1~30, 注意 跳過3的倍數(shù) for i in {1..30}; do if test $[ i % 3 ] -eq 0; then continue fi echo $i done
8. 函數(shù)使用 1) 函數(shù)的快速入門 格式
[ function ] funname () { action; [return int;] }
可以帶 function fun() 定義,也可以直接 fun() 定義,不帶任何參數(shù)。 參數(shù)返回,可以顯示加:return 返回,如果不加,將以最后一條命令運行結(jié)果,作為返回值。return 后跟數(shù)值 n(0-255) 快速入門
#!/bin/bash demoFun () { echo '這是我的第一個 shell 函數(shù)!' }echo '-----函數(shù)開始執(zhí)行-----' demoFunecho '-----函數(shù)執(zhí)行完畢-----'
2) 傳遞參數(shù)給函數(shù) 在 Shell 中,調(diào)用函數(shù)時可以向其傳遞參數(shù)。在函數(shù)體內(nèi)部,通過 $n
的形式來獲取參數(shù)的值,例如,$1
表示第一個參數(shù),$2
表示第二個參數(shù)...
帶參數(shù)的函數(shù)示例:
#!/bin/bash funWithParam (){ echo '第一個參數(shù)為 $1 !' echo '第二個參數(shù)為 $2 !' echo '第十個參數(shù)為 $10 !' echo '第十個參數(shù)為 ${10} !' echo '第十一個參數(shù)為 ${11} !' echo '參數(shù)總數(shù)有 $# 個!' echo '作為一個字符串輸出所有參數(shù) $* !' } funWithParam 1 2 3 4 5 6 7 8 9 34 73
輸出結(jié)果:
第一個參數(shù)為 1 ! 第二個參數(shù)為 2 ! 第十個參數(shù)為 10 ! 第十個參數(shù)為 34 ! 第十一個參數(shù)為 73 ! 參數(shù)總數(shù)有 11 個! 作為一個字符串輸出所有參數(shù) 1 2 3 4 5 6 7 8 9 34 73 !
注意,$10
不能獲取第十個參數(shù),獲取第十個參數(shù)需要${10}
。當n>=10
時,需要使用${n}
來獲取參數(shù)。
另外,還有幾個特殊字符用來處理參數(shù):
參數(shù)處理 說明 $#
傳遞到腳本的參數(shù)個數(shù) $*
以一個單字符串顯示所有向腳本傳遞的參數(shù) $$
腳本運行的當前進程 ID 號 $!
后臺運行的最后一個進程的 ID 號 $@
與$*
相同,但是使用時加引號,并在引號中返回每個參數(shù)。 $-
顯示 Shell 使用的當前選項,與 set 命令功能相同。 $?
顯示最后命令的退出狀態(tài)。0 表示沒有錯誤,其他任何值表明有錯誤。
9. 數(shù)組 1) 定義數(shù)組 數(shù)組中可以存放多個值。Bash Shell 只支持一維數(shù)組 (不支持多維數(shù)組),初始化時不需要定義數(shù)組大?。?。
與大部分編程語言類似,數(shù)組元素的下標由 0 開始。
Shell 數(shù)組用括號來表示,元素用空格 符號分割開,語法格式如下:
array_name=(value1 value2 value3 ... valuen)
實例 #!/bin/bash my_array=(A B 'C' D) 我們也可以使用下標來定義數(shù)組: array_name[0]=value0 array_name[1]=value1 array_name[2]=value2
2) 讀取數(shù)組 讀取數(shù)組元素值的一般格式是:
${array_name[index]}
實例 #!/bin/bash my_array=(A B 'C' D)echo '第一個元素為: ${my_array[0]} ' echo '第二個元素為: ${my_array[1]} ' echo '第三個元素為: ${my_array[2]} ' echo '第四個元素為: ${my_array[3]} '
執(zhí)行腳本,輸出結(jié)果如下所示:
$ chmod +x test.sh $ ./test.sh 第一個元素為: A 第二個元素為: B 第三個元素為: C 第四個元素為: D
獲取數(shù)組中的所有元素 使用@
或 *
可以獲取數(shù)組中的所有元素,例如:
#!/bin/bash my_array[0]=A my_array[1]=B my_array[2]=C my_array[3]=Decho '數(shù)組的元素為: ${my_array[*]} ' echo '數(shù)組的元素為: ${my_array[@]} '
執(zhí)行腳本,輸出結(jié)果如下所示:
$ chmod +x test.sh $ ./test.sh 數(shù)組的元素為: A B C D 數(shù)組的元素為: A B C D
獲取數(shù)組的長度 獲取數(shù)組長度的方法與獲取字符串長度的方法相同,例如:
#!/bin/bash my_array[0]=A my_array[1]=B my_array[2]=C my_array[3]=Decho '數(shù)組元素個數(shù)為: ${#my_array[*]} ' echo '數(shù)組元素個數(shù)為: ${#my_array[@]} '
執(zhí)行腳本,輸出結(jié)果如下所示:
$ chmod +x test.sh $ ./test.sh 數(shù)組元素個數(shù)為: 4 數(shù)組元素個數(shù)為: 4
3) 遍歷數(shù)組 方式一 #!/bin/bash my_arr=(AA BB CC)for var in ${my_arr[*]} do echo $var done
方式二 my_arr=(AA BB CC) my_arr_num=${#my_arr[*]} for ((i=0;i<my_arr_num;i++));do echo '-----------------------------' echo ${my_arr[$i]} done
10) 加載其它文件的變量 簡介 和其他語言一樣,Shell 也可以包含外部腳本。這樣可以很方便的封裝一些公用的代碼作為一個獨立的文件。
Shell 文件包含的語法格式如下:
. filename # 注意點號(.)和文件名中間有一空格 或source filename
練習 定義兩個文件 test1.sh 和 test2.sh,在 test1 中定義一個變量arr=(java c++ shell)
,在 test2 中對arr
進行循環(huán)打印輸出。
第一步: vim test1.sh
#!/bin/bash my_arr=(AA BB CC)
第二步: vim test2.sh
#!/bin/bash source ./test1.sh # 加載test1.sh 的文件內(nèi)容 for var in ${my_arr[*]} do echo $var done
第三步: 執(zhí)行 test2.sh
sh test2.sh
好處 :
數(shù)據(jù)源 和 業(yè)務處理 分離