Shell 程式設計入門

Assassinの發表於2021-09-21

Shell 程式設計入門

Shell 是一個用 C 語言編寫的程式,它是使用者使用 Linux 的橋樑。它是作業系統最外層的介面,
負責直接面向使用者互動並提供核心服務。

一、變數

1、 定義

Shell 定義變數時,變數名不加美元符號,如:

content="hello world!"

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

  • 命名只能使用英文字母,數字和下劃線,首個字元不能以數字開頭。
  • 中間不能有空格,可以使用下劃線 _。
  • 不能使用標點符號。
  • 不能使用bash裡的關鍵字(可用help命令檢視保留關鍵字)。

2、 使用

使用一個定義過的變數,只要在變數名前面加美元符號即可,如:

content="hello world!"
echo $content
echo ${content}

變數名外面的花括號是可選的,加不加都行,加花括號是為了幫助直譯器識別變數的邊界。

content="hello world!"
echo "name:${content}!!!"

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

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

content="hello world!"
echo $content
content="hello shell!"
echo $content

3、 只讀變數

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

content="hello world!"
readonly content
content="hello shell!"

執行指令碼,結果如下:

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

4、 區域性變數

Shell 中預設定義的變數是全域性變數,可以使用 global 進行顯式宣告,其作用域從被定義的地方開始,一直到指令碼結束或者被刪除的地方。

local 可以定義區域性變數,在函式內部使用。

#!/bin/bash

name="global variable"
function func(){
    local name="local variable"
    echo $name
}

func
echo $name

# local variable
# global variable

5、 變數型別

shell 中會同時存在三種變數:

  • 區域性變數;
  • 環境變數;
  • shell 變數。

二、字串

字串是最常用最有用的資料型別,字串可以用單引號,也可以用雙引號,也可以不用引號

1、單引號

str='this is a string'
echo '$str'

# $str

單引號字串的限制:

  • 單引號裡的任何字元都會原樣輸出,單引號字串中的變數是無效的;
  • 單引號字串中不能出現單獨一個的單引號(對單引號使用轉義符後也不行),但可成對出現,作為字串拼接使用。

2、 雙引號

name="shell"
str="Hello, I know you are \"$name\"! \n"
echo $str

# Hello, I know you are "shell"!

雙引號的優點:

  • 雙引號裡可以有變數;
  • 雙引號裡可以出現轉義字元。

3、 字串長度

string="abcd"
echo ${#string} 

# 4

4、 提取子字串

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

string="huawei is a great compan"
echo ${string:1:4} 

# uawe

5、 查詢子字串

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

string="huawei is a great compan"
echo `expr index "$string" io`  

# 6

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

三、陣列

Shell 只支援一維陣列(不支援多維陣列),並且沒有限定陣列的大小。類似於 C 語言,陣列元素的下標由 0 開始編號。

1、 定義陣列

shell 中,用括號來表示陣列,陣列元素用"空格"符號分割開。

array=("value0" "value1" "value2" "value3")

還可以單獨定義陣列的各個元素:

array[0]="value0"
array[1]="value1"
array[n]="valuen"

2、 讀取陣列

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

value=${array_name[n]}

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

echo ${array_name[@]}

# value0 value1 value2 value3

3、 獲取長度

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

# 取得陣列元素的個數
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得陣列單個元素的長度
lengthn=${#array_name[n]}

注意:陣列不可以進行切割,錯誤用法 ${array[1:2]}

四、流程控制

1、 if 判斷

語法示例判斷兩個變數是否相等。

#!/bin/bash

a=10
b=20
if [ $a == $b ]; then
    echo "a 等於 b"
elif [ $a -gt $b ]; then
    echo "a 大於 b"
elif [ $a -lt $b ]; then
    echo "a 小於 b"
else
    echo "沒有符合的條件"
fi

# a 小於 b

不能使用 if [ $a > $b ],正確的方式是 if (( $a > $b ))

2、 for 迴圈

for 迴圈即執行一次所有命令,空格進行元素分割,使用變數名獲取列表中的當前取值。

示例,順序輸出當前列表中的數字:

#!/bin/bash

for loop in 1 2 3; do
    echo "The value is: $loop"
done

#The value is: 1
#The value is: 2
#The value is: 3

迴圈字串內容:

#!/bin/bash

for str in This is a string; do
    echo $str
done

# This
# is
# a
# string

迴圈陣列中元素:

#!/bin/bash

array=("value0" "value1" "value2" "value3")
# array[*]與array[@]兩者皆可
for loop in ${array[*]}; do    
    echo ${loop}
done

# value0
# value1
# value2
# value3

3、 while 迴圈

while 迴圈用於不斷執行一系列命令,也用於從輸入檔案中讀取資料。

以下是一個基本的 while 迴圈,測試條件是:如果 int 小於等於 5,那麼條件返回真。int 從 1 開始,每次迴圈處理時,int 加 1。執行上述指令碼,返回數字 1 到 5,然後終止。

int=1
while [ $int -le 5 ]; do
    echo $int
    let "int++"
done

無限迴圈

# 方式一
while :
do
    command
done

# 方式二
while true
do
    command
done

4、 break 終止

在迴圈語句中,可以使用 break 命令,允許跳出所有迴圈(終止執行後面的所有迴圈)。

#!/bin/bash

while :; do
    echo -n "輸入 1 到 5 之間的數字:"
    read aNum
    case $aNum in
    1 | 2 | 3 | 4 | 5)
        echo "你輸入的數字為 $aNum!"
        ;;
    *)
        echo "你輸入的數字不是 1 到 5 之間的! 遊戲結束"
        break
        ;;
    esac
done

5、 continue 繼續

continue命令與break命令類似,只有一點差別,它不會跳出所有迴圈,僅僅跳出當前迴圈。

#!/bin/bash

while :; do
    echo -n "輸入 1 到 5 之間的數字: "
    read aNum
    case $aNum in
    1 | 2 | 3 | 4 | 5)
        echo "你輸入的數字為 $aNum!"
        ;;
    *)
        echo "你輸入的數字不是 1 到 5 之間的!"
        continue
        echo "遊戲結束"
        ;;
    esac
done

執行程式碼發現,當輸入大於5的數字時,該例中的迴圈不會結束,語句 echo "遊戲結束" 永遠不會被執行。

五、函式

1、 函式定義

Shell 中可以使用者定義函式,然後在 shell 指令碼中可以隨便呼叫。

下面的例子定義了一個函式並進行呼叫:

#!/bin/bash

function demo(){
     echo "這是我的第一個 shell 函式!"
}
echo "-----函式開始執行-----"
demo
echo "-----函式執行完畢-----"

可以帶 function fun() 定義,也可以直接 fun() 定義,不帶任何引數。

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

函式指令碼執行結果:

-----函式開始執行-----
這是我的第一個 shell 函式!
-----函式執行完畢-----

2、 函式引數

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

帶引數的函式示例:

#!/bin/bash

function funWithParam(){
     echo "第一個引數為 $1 !"
     echo "第十個引數為 $10 !"
     echo "第十個引數為 ${10} !"
     echo "第十一個引數為 ${11} !"
     echo "引數總數有 $# 個!"
     echo "作為一個字串輸出所有引數 $* !"
}
funWithParam 11 22 3 4 5 6 7 8 9 34 73

輸出結果:

第一個引數為 11 !
第十個引數為 110 !
第十個引數為 34 !
第十一個引數為 73 !
引數總數有 11 個!
作為一個字串輸出所有引數 11 22 3 4 5 6 7 8 9 34 73 !

引數獲取時 $n${n} 還是有區別的,特別是第二行的列印。

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

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

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

6、運算子

1、算術運算子

下表列出了常用的算術運算子。

+	加法	
-	減法	
*	乘法	
/	除法
%	取餘	
=	賦值	
==	相等
!=	不相等

注意:條件表示式要放在方括號之間,並且要有空格,例如: [$a==$b] 是錯誤的,必須寫成 [ $a == $b ]

使用示例如下:

#!/bin/bash

a=10
b=20

val=$(expr $a + $b)
echo "a + b : $val"

val=$(expr $a - $b)
echo "a - b : $val"

val=$(expr $a \* $b)
echo "a * b : $val"

val=$(expr $b / $a)
echo "b / a : $val"

val=$(expr $b % $a)
echo "b % a : $val"

if [ $a == $b ]; then
    echo "a 等於 b"
fi
if [ $a != $b ]; then
    echo "a 不等於 b"
fi

還可以使用下面的運算子替換,結果都一致:

#!/bin/bash

a=10
b=20

val=$(expr $a + $b)
echo "a + b : $val"

var=$(($a + $b))
echo "a + b : $val"

var=$[$a + $b]
echo "a + b : $val"

注意:

  • 乘號(*)前邊必須加反斜槓()才能實現乘法運算;
  • $((表示式)) 此處表示式中的 "*" 不需要轉義符號 ""。

2、關係運算子

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

下表列出了常用的關係運算子。

-eq	檢測兩個數是否相等,相等返回 true。
-ne	檢測兩個數是否不相等,不相等返回 true。	
-gt	檢測左邊的數是否大於右邊的,如果是,則返回 true。	
-lt	檢測左邊的數是否小於右邊的,如果是,則返回 true。	
-ge	檢測左邊的數是否大於等於右邊的,如果是,則返回 true。	
-le	檢測左邊的數是否小於等於右邊的,如果是,則返回 true。

使用示例如下:

#!/bin/bash

a=10
b=20

if [ $a -eq $b ]; then
    echo "$a -eq $b : a 等於 b"
else
    echo "$a -eq $b: a 不等於 b"
fi
if [ $a -gt $b ]; then
    echo "$a -gt $b: a 大於 b"
else
    echo "$a -gt $b: a 不大於 b"
fi

運算子可以使用"=="、"! ="、">"替換:

#!/bin/bash

a=10
b=20

if [ $a == $b ]; then
    echo "$a -eq $b : a 等於 b"
else
    echo "$a -eq $b: a 不等於 b"
fi
if (($a > $b)); then
    echo "$a -gt $b: a 大於 b"
else
    echo "$a -gt $b: a 不大於 b"
fi

注意:">"、"> =" 、"<" 、"< =" 不能使用"[]"。

3、邏輯運算子

常用的邏輯運算子。

&&	邏輯的 AND	[[ $a -lt 100 && $b -gt 100 ]] 返回 false
||	邏輯的 OR	[[ $a -lt 100 || $b -gt 100 ]] 返回 true

使用示例如下:

#!/bin/bash

a=10
b=20

if [[ $a -lt 100 && $b -gt 100 ]]; then
    echo "返回 true"
else
    echo "返回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]; then
    echo "返回 true"
else
    echo "返回 false"
fi

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

返回 false
返回 true

4、字串運算子

下表列出了常用的字串運算子。

=	檢測兩個字串是否相等,相等返回 true。	
!=	檢測兩個字串是否不相等,不相等返回 true。	
-z	檢測字串長度是否為0,為0返回 true。	
-n	檢測字串長度是否不為 0,不為 0 返回 true。
$	檢測字串是否為空,不為空返回 true。	

字串運算子例項如下:

#!/bin/bash

if [ -z $a ]
then
   echo "-z $a : 字串長度為 0"
else
   echo "-z $a : 字串長度不為 0"
fi
if [ -n "$a" ]
then
   echo "-n $a : 字串長度不為 0"
else
   echo "-n $a : 字串長度為 0"
fi
if [ $a ]
then
   echo "$a : 字串不為空"
else
   echo "$a : 字串為空"
fi

5、檔案測試運算子

檔案測試運算子用於檢測 Unix 檔案的各種屬性。

-b file	檢測檔案是否是塊裝置檔案。	
-c file	檢測檔案是否是字元裝置檔案。	
-d file	檢測檔案是否是目錄。
-f file	檢測檔案是否是普通檔案。
-g file	檢測檔案是否設定了 SGID 位。
-k file	檢測檔案是否設定了粘著位(Sticky Bit)。
-p file	檢測檔案是否是有名管道。
-u file	檢測檔案是否設定了 SUID 位。
-r file	檢測檔案是否可讀。
-w file	檢測檔案是否可寫。
-x file	檢測檔案是否可執行。
-s file	檢測檔案是否為空。
-e file	檢測檔案。

七、輸入/輸出重定向

1、 輸出重定向

將命令的完整的輸出重定向在使用者檔案中。

# 覆蓋
$ echo "hello world" >./test.file

# 追加
$ echo "hello world" >>./test.file

2、 輸入重定向

從使用者檔案中的內容輸出到命令列。

$ wc -l  < ./test.file
1

可以與 while 語句結合,遍歷檔案內容,按行列印:

while read line; do
    echo $line
done < ./test.file

3、 標準輸入輸出

一般情況下,每個 Unix/Linux 命令執行時都會開啟三個檔案:

  • 標準輸入檔案(stdin):stdin的檔案描述符為0,Unix程式預設從stdin讀取資料。
  • 標準輸出檔案(stdout):stdout 的檔案描述符為1,Unix程式預設向stdout輸出資料。
  • 標準錯誤檔案(stderr):stderr的檔案描述符為2,Unix程式會向stderr流中寫入錯誤資訊。

預設情況下,command > file 將 stdout 重定向到 file,command < file 將stdin 重定向到 file。

如果希望 stderr 重定向到 file,可以這樣寫:

$ command 2>file

如果希望 stderr 追加到 file 檔案末尾,可以這樣寫:

$ command 2>>file

2 表示標準錯誤檔案(stderr)。

如果希望將 stdout 和 stderr 合併後重定向到 file,可以這樣寫:

$ command > file 2>&1

或者

$ command >> file 2>&1

如果希望對 stdin 和 stdout 都重定向,可以這樣寫:

$ command < file1 >file2

command 命令將 stdin 重定向到 file1,將 stdout 重定向到 file2。

八、eval 函式

當我們在命令列前加上 eval 時,shell 就會在執行命令之前掃描它兩次。eval 命令將首先會先掃描命令列進行所有的置換,然後再執行該命令。該命令適用於那些一次掃描無法實現其功能的變數。該命令對變數進行兩次掃描。

常見的使用場景如下:

1、普通情況

$ var=100
$ echo $var
100
$ eval echo $var

這樣和普通的沒有加 eval 關鍵字的命令的作用一樣。

2、字串轉換命令

$ cat file
helle shell
it is a test of eval

$ tfile="cat file"
$ eval $tfile
helle shell
it is a test of eval

從上面可以看出 eval 經歷了兩次掃描,第一次掃描替換了變數為字串,第二次掃描執行了字串內容。

3、獲取引數

$ cat t.sh
#!/bin/bash

eval echo \$$#

$ ./t.sh a b c
c
$ ./t.sh 1 2 3
3

通過轉義符 “|” 與 $# 結合,可以動態的獲取最後一個引數。

4、 修改指標

$ var=100
$ ptr=var
$ eval echo \$$ptr
100
$ eval $ptr=50
$ echo $val
50

推薦閱讀:

《Linux命令列與shell指令碼程式設計大全》

《谷歌shell編碼規範》

相關文章