一文學完Linux Shell程式設計(強烈建議收藏)

語言: CN / TW / HK

一、 Shell 程式設計

1. 簡介

Shell 是一個用 C 語言編寫的程式,通過 Shell 使用者可以訪問作業系統核心服務。

Shell 既是一種命令語言,又是一種程式設計語言。

Shell script 是一種為 shell 編寫的指令碼程式。Shell 程式設計一般指 shell 指令碼程式設計,不是指開發 shell 自身。

Shell 程式設計跟 java、php 程式設計一樣,只要有一個能編寫程式碼的文字編輯器和一個能解釋執行的指令碼直譯器就可以了。

Linux 的 Shell 直譯器 種類眾多,一個系統可以存在多個 shell,可以通過 cat /etc/shells 命令檢視系統中安裝的 shell 直譯器。

Bash 由於易用和免費,在日常工作中被廣泛使用。同時,Bash 也是大多數 Linux 系統預設的 Shell。

shell 直譯器

java 需要 虛擬機器直譯器 , 同理 shell 指令碼也需要 解析器 ,如下所示:

[[email protected] shells] cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh

2. 快速入門

1) 編寫指令碼

新建 /export/hello.sh 檔案,內容如下:

#!/bin/bash

echo 'hello world'

#!是一個約定的標記,它告訴系統這個指令碼需要什麼直譯器來執行,即使用哪一種 Shell。

echo 命令用於向視窗輸出文字。

2) 執行 shell 指令碼

執行方式一

[[email protected] shells] /bin/sh 01.sh
hello world

[[email protected] shells] /bin/bash 01.sh
hello world

問題:bash 和 sh 是什麼關係?

答:sh 是 bash 的 快捷方式

執行方式二

方式一的簡化方式:

[[email protected] shells] bash hello.sh
hello world

[[email protected] shells] sh hello.sh
hello world

問題:為什麼可以省略 /bin/

答:因為 PATH 環境變數中增加了 /bin/目錄, 所以 使用/bin/sh 等類似指令時, 可以省略 /bin

執行方式三

./檔名

[[email protected] shells] ./hello.sh
-bash: ./01.sh: 許可權不夠

問題:許可權不夠怎麼辦?

[[email protected] shells] chmod 755 hello.sh

# 再次執行:
[[email protected] shells] ./hello.sh
hello world!

3. shell 變數

1) 簡介

在 shell 指令碼中, 定義變數時,變數名不加美元符號 $ ,如:

your_name="runoob.com"

注意 : 變數名和等號之間不能有空格 ,這可能和你熟悉的所有程式語言都不一樣。

同時,變數名的命名須遵循如下規則:

  • 命名只能使用英文字母,數字和下劃線,首個字元不能以數字開頭。

  • 中間不能有空格,可以使用下劃線 _
  • 不能使用標點符號。

  • 不能使用 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 下目錄的檔名迴圈出來。

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 當成一個變數(其值為空),程式碼執行結果就不是我們期望的樣子了。

推薦給所有變數加上花括號,這是個好的程式設計習慣。

已定義的變數,可以被重新定義,如:

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 myUrl
echo $myUrl

以上例項執行將沒有任何輸出。

4) 只讀變數

使用 readonly 命令可以將變數定義為只讀變數,只讀變數的值不能被改變。

下面的例子嘗試更改只讀變數,結果報錯:

#!/bin/bash

myUrl="http://www.google.com"
readonly myUrl
myUrl="http://www.runoob.com"

執行指令碼,結果如下:

/bin/sh: NAME: This variable is read only.

4. 字串

字串是 shell 程式設計中最常用最有用的資料型別(除了數字和字串,也沒啥其它型別好用了),字串可以用單引號,也可以用雙引號,也可以不用引號。

1) 單引號

skill='java'

str='I am goot at $skill'

echo $str

輸出結果為:

I am goot at $skill

單引號字串的限制:

  • 單引號裡的任何字元都會原樣輸出,單引號字串中的 變數是無效 的;

  • 單引號字串中不能出現單獨一個的單引號(對單引號使用轉義符後也不行),但可成對出現,作為字串拼接使用。

2) 雙引號

skill='java'

str="I am goot at $skill"

echo $str

輸出結果為:

I am goot at java

雙引號的優點:

  • 雙引號裡可以有變數

  • 雙引號裡可以出現轉義字元

3) 獲取字串長度

skill='java'

echo ${skill} # 輸出結果: java

echo ${#skill} # 輸出結果: 4

或者: expr length "iamlilei" #輸出結果: 8

4) 提取子字串

substring(2)

substring(2,3)

以下例項從字串第 2 個字元開始擷取 4 個字元:

str="I am goot at $skill"

echo ${str:2} # 輸出結果為: am goot at java 從第二個字元開始擷取,到結尾

echo ${str:2:2} # 輸出結果為: am 從第二個字元開始擷取,擷取2個字元

5) 查詢子字串

查詢字元 io 的位置( 哪個字母先出現就計算哪個 ):

str="I am goot at  $skill"
echo `expr index "$str" am` # 輸出是: 3

或者:
expr index "iamlilei" am #輸出結果: 2 返回在STRING中找到CHARS字串的位置;否則,返回0

注意:以上指令碼中 ` 是反引號(Esc 下面的),而不是單引號 ',不要看錯了哦。

5. 傳遞引數

我們可以在執行 Shell 指令碼時,向指令碼傳遞引數,指令碼內獲取引數的格式為: $n

n代表一個數字,1 為執行指令碼的第一個引數,2 為執行指令碼的第二個引數,以此類推……

例項

以下例項我們向指令碼傳遞三個引數,並分別輸出,其中 $0 為執行的檔名:

vim /export/sh/param.sh

#!/bin/bash

echo "Shell 傳遞引數例項!";

echo "執行的檔名:$0";

echo "第一個引數為:$1";

echo "第二個引數為:$2";

echo "第三個引數為:$3";

為指令碼設定可執行許可權,並執行指令碼,輸出結果如下所示:

$ chmod 755 param.sh

$ ./param.sh 1 2 3

Shell 傳遞引數例項!

執行的檔名:./param.sh

第一個引數為:1

第二個引數為:2

第三個引數為:3

另外,還有幾個特殊字元用來處理引數:

引數處理 說明
$# 傳遞到指令碼的引數個數
$* 以一個單字串顯示所有向指令碼傳遞的引數。如 "$*" 用「"」括起來的情況、以 "$1 $2 … $n" 的形式輸出所有引數。
$$ 指令碼執行的當前程序 ID 號
$! 後臺執行的最後一個程序的 ID 號
[email protected] $* 相同,但是使用時加引號,並在引號中返回每個引數。   如 "[email protected]" 用「"」括起來的情況、以 "$1" "$2" … "$n" 的形式輸出所有引數。
$- 顯示 Shell 使用的當前選項,與 set 命令功能相同。
$? 顯示最後命令的退出狀態。0 表示沒有錯誤,其他任何值表明有錯誤。
#!/bin/bash

echo "Shell 傳遞引數例項!";

echo "第一個引數為:$1";

echo "引數個數為:$#";

echo "傳遞的引數作為一個字串顯示:$*";

執行指令碼,輸出結果如下所示:

$ chmod +x test.sh

$ ./test.sh 1 2 3

Shell 傳遞引數例項!

第一個引數為:1

引數個數為:3

傳遞的引數作為一個字串顯示:1 2 3

$*[email protected] 區別:

  • 相同點:都是引用所有引數。

  • 不同點:只有在雙引號中體現出來。假設在指令碼執行時寫了三個引數 1、2、3,,則 " * " 等價於 "1 2 3"(傳遞了一個引數),而 "@" 等價於 "1" "2" "3"(傳遞了三個引數)。

#!/bin/bash

echo "-- $* 演示 ---"
for i in "$*"; do
echo $i
done

echo "-- [email protected] 演示 ---"
for i in "[email protected]"; do
echo $i
done

執行指令碼,輸出結果如下所示:

$ chmod +x test.sh

$ ./test.sh 1 2 3

-- $* 演示 ---
1 2 3

-- [email protected] 演示 ---
1
2
3

6. Shell 算術運算子

1) 簡介

Shell 和其他程式設計一樣, 支援 包括:算術、關係、布林、字串等運算子。

原生 bash 不支援 簡單的數學運算,但是可以通過其他命令來實現,例如 expr。

expr 是一款表示式計算工具,使用它能完成表示式的求值操作。

例如,兩個數相加:

val=`expr 2 + 2`
echo $val

注意:

表示式和運算子之間要有空格,例如 2+2 是不對的,必須寫成 2 + 2

完整的表示式要被 ` 包含,注意不是單引號,在 Esc 鍵下邊。

下表列出了常用的算術運算子,假定變數 a 為 10,變數 b 為 20:

運算子 說明 舉例
+ 加法 expr $a + $b 結果為 30。
- 減法 expr $a - $b 結果為 -10。
* 乘法 expr $a * $b 結果為 200。
/ 除法 expr $b / $a 結果為 2。
% 取餘 expr $b % $a 結果為 0。
= 賦值 a=$b 將把變數 b 的值賦給 a。
== 相等。用於比較兩個數字,相同則返回 true。 [ $a == $b ] 返回 false。
!= 不相等。用於比較兩個數字,不相同則返回 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
...
commandN
fi

demo

[[email protected] export]# cat if_test.sh
#!/bin/bash

a=20

if [ $a -gt 10 ]; then
echo "a 大於 10"
fi

末尾的 fi 就是 if 倒過來拼寫,後面還會遇到類似的。

1.2 if else

if else 語法格式:

if condition; then
command1
command2
...
commandN
else
command
fi

1.3 if else-if else

if else-if else 語法格式:

if condition1; then
command1
elif condition2; then
command2
else
commandN
fi

以下例項判斷兩個變數是否相等:

關係運算符

關係運算符只支援數字,不支援字串,除非字串的值是數字。

下表列出了常用的關係運算符,假定變數 a 為 10,變數 b 為 20:

運算子 說明 英文 舉例
-eq 檢測兩個數是否相等,相等返回 true。 equal [ $a -eq $b ] 返回 false。
-ne 檢測兩個數是否不相等,不相等返回 true。 not equal [ $a -ne $b ] 返回 true。
-gt 檢測左邊的數是否大於右邊的,如果是,則返回 true。 greater than [ $a -gt $b ] 返回 false。
-lt 檢測左邊的數是否小於右邊的,如果是,則返回 true。 less than [ $a -lt $b ] 返回 true。
-ge 檢測左邊的數是否大於等於右邊的,如果是,則返回 true。 Greater than or equal to [ $a -ge $b ] 返回 false。
-le 檢測左邊的數是否小於等於右邊的,如果是,則返回 true。 Less than or equal to [ $a -le $b ] 返回 true。

案例:

[[email protected] 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 迴圈

格式

for variable in (list); do
command
command
...
done

練習

# 需求1: 遍歷 1~5
# 需求2: 遍歷 1~100
# 需求3: 遍歷 1~100之間的奇數
# 需求4: 遍歷 根目錄 下的內容

程式碼如下:

#!/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之間的奇數
for i in {1..100..2}; do
echo $i
done
# 需求4: 遍歷 根目錄 下的內容
for f in `ls /`; do
echo $f
done

3) while 語句

while 迴圈用於不斷執行一系列命令,也用於從輸入檔案中讀取資料;命令通常為測試條件。其格式為:

while condition; do
command
done

需求: 計算 1~100 的和

#!/bin/bash

sum=0
i=1
while [ $i -le 100 ]; do
sum=$[ sum + i]
i=$[ i + 1 ]
done

echo $sum

執行指令碼,輸出:

使用中使用了 Bash let 命令,它用於執行一個或多個表示式,變數計算中不需要加上 $ 來表示變數,具體可查閱:Bash let 命令:http://www.runoob.com/linux/linux-comm-let.html。

4) 無限迴圈

無限迴圈語法格式:

while true; do
command
done
需求: 每隔1秒 列印一次當前時間
#!/bin/bash

while true; do
date
sleep 1
done

5) case(switch)

Shell case 語句為多選擇語句。可以用 case 語句匹配一個值與一個模式,如果匹配成功,執行相匹配的命令。case 語句格式如下:

casein

模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac

case 工作方式如上所示。取值後面必須為單詞 in,每一模式必須以右括號結束。取值可以為變數或常數。匹配發現取值符合某一模式後,其間所有命令開始執行直至 ;;

取值將檢測匹配的每一個模式。一旦模式匹配,則執行完匹配模式相應命令後不再繼續其他模式。如果無一匹配模式,使用星號 * 捕獲該值,再執行後面的命令。

下面的指令碼提示輸入 1 到 4,與每一種模式進行匹配:

echo '輸入 1 到 4 之間的數字:'

read aNum

case $aNum in
1) echo '你選擇了 1'
;;

2) echo '你選擇了 2'
;;

3) echo '你選擇了 3'
;;

4) echo '你選擇了 4'
;;

*) echo '你沒有輸入 1 到 4 之間的數字'
;;
esac

輸入不同的內容,會有不同的結果,例如:

輸入 1 到 4 之間的數字:

你輸入的數字為:

3

你選擇了 3

6) 跳出迴圈

在迴圈過程中,有時候需要在未達到迴圈結束條件時強制跳出迴圈,Shell 使用兩個命令來實現該功能:break 和 continue。

break命令

break 命令允許跳出所有迴圈(終止執行後面的所有迴圈)。

需求: 執行死迴圈 每隔1秒列印當前時間, 執行10次停止
#!/bin/bash
# 需求: 執行死迴圈 每隔1秒列印當前時間, 執行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 命令類似,只有一點差別,它不會跳出所有迴圈,僅僅跳出當前迴圈。

需求: 列印 1~30, 注意 跳過3的倍數
#!/bin/bash

# 需求: 列印 1~30, 注意 跳過3的倍數

for i in {1..30}; do
if test $[ i % 3 ] -eq 0; then
continue
fi
echo $i
done

8. 函式使用

1) 函式的快速入門

  • 格式

    [ function ] funname()
    {
    action;
    [return int;]
    }
    • 可以帶 function fun() 定義,也可以直接 fun() 定義,不帶任何引數。

    • 引數返回,可以顯示加:return 返回,如果不加,將以最後一條命令執行結果,作為返回值。return 後跟數值 n(0-255)

  • 快速入門

    #!/bin/bash

    demoFun () {

    echo "這是我的第一個 shell 函式!"

    }

    echo "-----函式開始執行-----"
    demoFun
    echo "-----函式執行完畢-----"

2) 傳遞引數給函式

在 Shell 中,呼叫函式時可以向其傳遞引數。在函式體內部,通過 $n 的形式來獲取引數的值,例如, $1 表示第一個引數, $2 表示第二個引數...

帶引數的函式示例:

#!/bin/bash

funWithParam(){
echo "第一個引數為 $1 !"
echo "第二個引數為 $2 !"
echo "第十個引數為 $10 !"
echo "第十個引數為 ${10} !"
echo "第十一個引數為 ${11} !"
echo "引數總數有 $# 個!"
echo "作為一個字串輸出所有引數 $* !"
}

funWithParam 1 2 3 4 5 6 7 8 9 34 73

輸出結果:

第一個引數為 1 !

第二個引數為 2 !

第十個引數為 10 !

第十個引數為 34 !

第十一個引數為 73 !

引數總數有 11 個!

作為一個字串輸出所有引數 1 2 3 4 5 6 7 8 9 34 73 !

注意, $10 不能獲取第十個引數,獲取第十個引數需要 ${10} 。當 n>=10 時,需要使用 ${n} 來獲取引數。

另外,還有幾個特殊字元用來處理引數:

引數處理 說明
$# 傳遞到指令碼的引數個數
$* 以一個單字串顯示所有向指令碼傳遞的引數
$$ 指令碼執行的當前程序 ID 號
$! 後臺執行的最後一個程序的 ID 號
[email protected] $* 相同,但是使用時加引號,並在引號中返回每個引數。
$- 顯示 Shell 使用的當前選項,與 set 命令功能相同。
$? 顯示最後命令的退出狀態。0 表示沒有錯誤,其他任何值表明有錯誤。

9. 陣列

1) 定義陣列

陣列中可以存放多個值。Bash Shell 只支援一維陣列 (不支援多維陣列),初始化時不需要定義陣列大小(。

與大部分程式語言類似,陣列元素的下標由 0 開始。

Shell 陣列用括號來表示,元素用 空格 符號分割開,語法格式如下:

array_name=(value1 value2 value3 ... valuen)

例項

#!/bin/bash

my_array=(A B "C" D)

我們也可以使用下標來定義陣列:

array_name[0]=value0

array_name[1]=value1

array_name[2]=value2

2) 讀取陣列

讀取陣列元素值的一般格式是:

${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]}"

執行指令碼,輸出結果如下所示:

$ chmod +x test.sh

$ ./test.sh

第一個元素為: A
第二個元素為: B
第三個元素為: C
第四個元素為: D

獲取陣列中的所有元素

使用 @* 可以獲取陣列中的所有元素,例如:

#!/bin/bash

my_array[0]=A
my_array[1]=B
my_array[2]=C
my_array[3]=D

echo "陣列的元素為: ${my_array[*]}"
echo "陣列的元素為: ${my_array[@]}"

執行指令碼,輸出結果如下所示:

$ chmod +x test.sh
$ ./test.sh

陣列的元素為: A B C D
陣列的元素為: A B C D

獲取陣列的長度

獲取陣列長度的方法與獲取字串長度的方法相同,例如:

#!/bin/bash
my_array[0]=A
my_array[1]=B
my_array[2]=C
my_array[3]=D

echo "陣列元素個數為: ${#my_array[*]}"
echo "陣列元素個數為: ${#my_array[@]}"

執行指令碼,輸出結果如下所示:

$ chmod +x test.sh
$ ./test.sh

陣列元素個數為: 4
陣列元素個數為: 4

3) 遍歷陣列

方式一

#!/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 進行迴圈列印輸出。

第一步: vim test1.sh

#!/bin/bash

my_arr=(AA BB CC)

第二步: vim test2.sh

#!/bin/bash

source ./test1.sh # 載入test1.sh 的檔案內容

for var in ${my_arr[*]}

do

echo $var

done

第三步: 執行 test2.sh

sh test2.sh

好處 :

  1. 資料來源 和 業務處理 分離

  2. 複用 程式碼擴充套件性更強

--END--