本文介紹是學習 Shell 第一節,主要介紹 Shell 變數的定義與運算:

  • 變數名和變數型別
  • echo命令和跳脫字元
  • 字串在單引號和雙引號下表現不同
  • {}用於限定字串的範圍
  • 關鍵字unsetreadonly

1 變數定義

Shell 中變數定義如下:

var_name=variable

其中,var_name是變數名,variable是變數值。變數名和變數值之間的=左右一定不能有空格

1.1 變數名

Shell 變數名需滿足如下規則:

  • 只能是字母、數字、下劃線,不能數字開頭
  • 大小寫敏感
  • 不要包含 Shell 關鍵字(可用help命令獲取)

根據以上規則,合法的變數名:

hello
hello000
hello_world_00
_konichiwa
nihao_

非法變數名:

help # 关键字
ni hao # 有空格
9am # 数字开头

1.2 變數值

Shell 中變數值分字串和陣列兩種。

變數值來源有直接賦值和來自命令輸出兩種。

1.2.1 字串

字串是 Shell 中常見的資料型別。Shell 中沒有int bool各種型別。除了陣列(下面會介紹)之外都是字串。

下面所有變數都是字串:

a="xy"
b=1
c="1"
d="hello world"
e="x\n"
f='x'
g='x\n'

在定義字串的值時,可以新增引號來顯式確定範圍,實際結果中不會包含這個引號。需要注意單引號和雙引號的區別(下面會介紹)。

a="xy"
a='xy'
a=xy

上面三個定義中a的值都是xy(不帶引號)

當然,也可以用其它命令輸出作為變數值,用`cmd`(反引號,esc下面那個鍵)或者$(cmd)的方式:

x=`pwd`
x=$(pwd)

1.2.1 陣列

用圓括號()定義,元素之間用空格分開:

var_list=(1 2 3)

Shell 中的陣列類似 Python 中的 dict,有 key 和 value 之分,只是恰好這個 key 是整數。所以也可以如下定義陣列:

var_list=(0=[hhh] 1=[2])

陣列的元素是下面介紹的字串。Shell 只支援一維陣列,不支援高維陣列。

當然,Shell 中陣列也可以來自命令輸出,用`cmd`(反引號,esc下面那個鍵)或者$(cmd)的方式:

a=`ls -a`
b=$(ls -a)
echo 'files are `ls -a`'
echo "files are `ls -a`"
echo "files are $(ls -a)"

1.3 變數引用

定義一個變數後,在其它地方要使用這個變數的值。

1.3.1 echo命令

在介紹 Shell 中變數引用之前,先介紹 Shell 中的echo命令,它的作用是列印變數值。(類似 Python 中的print和 C++中的printf)。

echo "haha" # haha

echo命令也有參數:加上-e則有轉義,加上-E參數則沒有轉義(預設無轉義)。e是 escape 的意思。Shell 中跳脫字元如下:

跳脫字元含義
\\反斜槓
\a警報,響鈴
\b退格(刪除前面一個字元)
\f換頁(FF),將當前位置移到下頁開頭
\n換行
\r回車
\t水平製表符(tab 鍵)
\v垂直製表符

關於\n\r的區別,可以參考另外一篇文章。

echo -e "x\n"
# x
#

echo -E "\n"
# \n

1.3.2 變數引用

  • 引用已經定義好的變數:在變數名前面新增$(定義變數或變數重新賦值時不加$
  • $獲取變數時候,可新增{}來顯式界定變數範圍

為說明這個問題,先介紹 Shell 中字串的拼接操作,將兩個字串寫在一起。

a=5
b=6
echo a # output a
echo $a$b # output 56
echo $a # output 5
echo ${a}x # output 5x
echo $ax # output None(no output)

大括號{}用來界定範圍,${a}x界定變數是a而不是ax

Shell 中變數可以用引號括起來,實際內容不會包含引號。其中單雙引號並不能通用:

  • 單引號中的內容,也不會計算變數值,是啥樣就是啥樣
  • 雙引號中的內容,會先計算變數結果,然後輸出
a=5
echo 'a'  # output a
echo "a" # output a
echo '$a' # output $a
echo "$a" # output 5
echo $a # output 5

1.3.3 變數替換

Shell 中對於沒有定義的變數強行 echo,則會啥也不輸出,也不會報錯。 為解決變數沒有定義造成了的問題,Shell 中有變數替換的語法,用於處理變數沒有引用時候的場景。

形式說明
${var}var的值
${var:-word}var若被定義,則輸出var的值;var若為空,或者為定義或已被刪除(unset),返回word,但不改變var的值。
${var:=word}var若被定義,則輸出var的值;var若為空或已被刪除(unset),返回word,並將var的值設定為 word
${var:?message}var若被定義,則輸出var的值;var若為空或已被刪除(unset),訊息message送到標準錯誤輸出。該操作可以用來檢測變數var是否可以被正常賦值。若此替換出現在 shell script 中,則 shell script 將停止執行。
${var:+word}var若被定義,那麼返回word,但不改變 var 的值。如果var沒有被定義,則返回空。

1.4 變數unsetreadonly

Shell 中定義一個變數後,可以通過unset的方式刪掉。

a=4
echo $a # output 4
unset a
echo $a # output None(no output)

在 Shell 中如果一個變數被刪除掉,再引用則會得到的 None 的返回。這和大多數程式語言不一樣,大多數程式語言在處理沒有定義的變數時會直接報錯。(Shell 這種機制也增加了 Shell script 的除錯難度。)

定義變數時候新增readonly關鍵字:

readonly x=5
x=6 # readonly variable

變數前新增readonly之後,是無法刪除掉的。

2 字串運算

Shell 變數都是陣列和字串。對於變數,除了獲取變數的值之外,還可以進行更多操作。常見的操作有:

  • 字串求長度
  • 字串拼接
  • 字串擷取
  • 字串比較
  • 數字字串的運算

2.1 字串求長度

x="hello world"
echo ${#x} # 11

#是 shell 中表示註釋的符號,如果在一個變數前面,左右又被{}包圍,則是求長度的意思。

2.2 字串拼接

就是將多個字串合併為一個字串,直接排列即可。

x=5
y='u'
z=$x$y
echo $z # 5u
z=${x}${y}
echo $z # 5u

2.3 字串擷取

從原來的字串中按照一定規則取出來一部分。

2.3.1 按長度擷取

對變數x進行擷取:

${x:start:length}
  • 從左往右擷取
  • start是開始的 index
    • Shell 中陣列最開始元素的 index 是 0
    • start可以是一個數,表示是開始的 index。
    • start也可以是0-n樣子,則是從後往前找開始的字元
  • 如果length長度超出了整個字串結尾,則擷取start開始的所有字元
x="hello world"
y=${x:1:2} # 1 is the start index left to right, 2 is the length
echo $y # el
y=${x:0-5:2} # right to left
echo $y # wo

2.3.2 按特定字元擷取

  • #擷取右邊字元,%擷取左邊字元
  • 一個#(或%)表示從左往右第一個,兩個表示從左往右最後一個
  • *表示任意字元
# 使用#截取从左往右的右边的字符
url="http://aoi.ai/index.html"
echo ${url#*/}    # /aoi.ai/index.html,从左往右第一个右边的字符
echo ${url##*/}   # index.html,从左往右最后一个右边的字符

str="---aa+++aa@@@"
echo ${str#*aa}   # +++aa@@@
echo ${str##*aa}  # @@@

# 使用%截取从左往右左边的字符
url="https://aoi.ai/index.html"
echo ${url%/*}  # https://aoi.ai
echo ${url%%/*}  # http:

str="---aa+++aa@@@"
echo ${str%aa*}  # ---aa+++
echo ${str%%aa*}  # ---

2.4 字串比較

2.4.1 testif函式

可以在 Shell 中對字串進行比較。判斷的結果可以傳遞給if等函式。例子如下:

# use test function
if test "$a" = "$b";then
 command_1
 else
 command_2
fi

# use [ ]
if [ "$a" = "$b" ]; then
command_1
else
command_2
fi

分號;並非必要。 test "$a" = "$b"就是用於比較的意思,比較的結果傳遞給if函式,如果test中的結果為真,則執行command_1,否則執行command_2。在實際使用中test可以用中括號[]來表示。

2.4.2 字串相等與不等

如果兩個字串完全一樣,則是相等;如果不一樣則不等。判斷相等用 = (左右有空格),判斷不等用 != (左右有空格)。

a=5
b=5
if [ $a = $b ]; then
echo "equal"
fi

在實際使用中,還會遇到雙等號(==)情況:

  • 在 bash 中,單等號和雙等號是等效的
  • 在其他 shell 中,則單等號表示相等

所以推薦的寫法是用單等號。

2.4.3 數字字串操作

在 Shell 中,雖然沒有特別的數字型別資料,但是如果字串符合整數形式,則可以按照數字來處理:除了可以比較相等與否,還可以比較大小:

含義表示方式
equal 等於-eq
not equal 不等於-ne
greater than 大於-gt
less than 小於-lt
greater equal 大於等於-ge
less equal 小於等於-le

需要注意的是,這些運算左右一定要是整數,否則就會報錯。

a=3
b=2
if [ $a -eq $b ]; then
echo "$a equals $b"
else
echo "$a does not eqaul $b"
fi

2.4.4 數字四則運算

Shell 中沒有數字這種型別,數字也被當成字串處理。如果非要進行數字四則運算,則需要藉助額外的工具。

整數運算

使用expr函式,規則是expr var_a oprator var_b,常見整數運算子:

運算子表示方式
整數加法+
整數減法-
整數乘法\*
整數求模/
整數取餘數%
左括號\(
右括號\)
  • 1 求模就是整數除法,結果是整數,直接去掉後面的小數
    expr -5 / -3 # 1
    expr -5 / -3 # -1
    expr 5 / -3 # -1
    expr 5 / 3 # 1
  • 2 求餘數以第一個變數符號為準
    expr 5 % 3 # 2
    expr 5 % -3 # 2
    expr -5 % -3 # -2
    expr -5 % 3 # -2
  • 3 括號用於改變運算優先順序,變數之間要新增空格
    expr \( 5 + 3 \) \* 2
  • 4 疊加expr運算子,用於連續計算
    expr $(expr 4 + 5) \* 8 # 72
    expr `expr 4 + 5` \* 8 # 72

浮點運算

浮點運算要用bc

echo 5.5 + 5.5 + 5.5 | bc # 16.5
echo 5.5 \* \( 5.5 + 5.5 \) | bc # 60.5

其中|是管道命令,將前一個命令的輸出作為後一個命令的輸入。新增scale可以設定保留的位數。

echo 5.5 / 10 | bc # 0
echo "scale=4; 5.5 / 10" | bc # 0.5500

5 陣列運算

3.1 陣列訪問

3.1.1 訪問某一個元素

${a[index]},Shell 中陣列元素index0開始。

a=(1 2 3)
echo "${a[2]}" # 3

index可以是正整數,表示從左往右數的第幾個數。如果index指代的索引不存在,則返回空。

a=(1 2 3)
echo "${a[4]}" # 输出一行空白,表示没有

當然,也可以像字串擷取一樣,使用0-n的形式,表示從右到左,此時從1開始記數:

a=(1 2 3)
echo "${a[0-1]}" # 3

3.1.2 陣列遍歷

要同時輸入陣列中的所有元素,可以用@或者*

a=(1 3 5)
echo "${a[@]}" # 1 3 5
echo "${a[*]}" # 1 3 5

Shell 中還有一個用於遍歷訪問的函式for

for i in ${a[@]};
do
command_1
done

其中a是一個陣列。in後面的物件不能是陣列,而要用${a[@]}或者${a[*]}的形式:

a=(1 2 3)

for i in ${a[@]};
do
echo $i
done
# 1
# 2
# 3

for i in ${a[*]};
do
echo $i
done
# 1
# 2
# 3

for i in $a;
do
echo $i
done
# 1

$a[*]$a[@]都表示陣列中所有元素,它們有什麼區別呢:

  • 不被雙引號(” “)包含時,都以"$1" "$2" … "$n" 的形式輸出所有參數。
  • 當它們被雙引號(” “)包含時:
    • "$a[*]" 會將所有的參數作為一個整體,以"$1 $2 … $n"的形式輸出所有參數
    • "$a[@]" 會將各個參數分開,以"$1" "$2""$n" 的形式輸出所有參數
a=(1 2 3)

for i in "${a[@]}";
do
echo $i
done
# 1
# 2
# 3

for i in "${a[*]}";
do
echo $i
done
# 1 2 3

藉助*@符號,可以獲得陣列中所有元素。和獲獲取字串長度類似,也可以獲得整個陣列中的元素個數:

a=(1 2 3)
echo "${#a[@]}"

3.1.3 陣列擷取

意思是說取得陣列的部分,類似字串訪問:

a=(1 2 3)
echo "${a[@]:1:2}" # 2 3
echo "${a[@]:0-1:2}" # 3

3.2 陣列修改

修改陣列中某一個元素,用賦值的方式去修改(不要新增美元符號)。

a=(1 2 3)
echo "${a[1]}" # 2
a[1]=4
echo "${a[1]}" # 4

往陣列後面新增一個元素,只需要將index設定為陣列長度後面一個就行。

a=(1 2 3)
echo "${#a[@]}"  # 3
a[3]=4
echo "${#a[@]}"  # 4
a[8]=6
echo "${#a[@]}"  # 5

這個index可以是任意正數。陣列a本身長度為3,此時可以設定a[3]的值,也可以設定a[5]的值。這個時候,陣列又有點類似 Python 的字典了,其中key就是index,如果index不存在則返回空。 雖然強行新增了a[8],但是整個陣列的長度還是 5。

3.3 陣列刪除

使用unset

a=(1 2 3)
unset a[0]
echo "${a[0]}" # 空白

3.4 陣列元素替換

陣列中的元素也可以替換,替換的是 value,不是 key。而且不會修改原來陣列。

a=(1 2 3)
echo "${a[@]/3/777}" # 1 2 777
echo "${a[@]}"  # 1 2 3

總結

本文紹了 Shell 中變數定義和的運算:包含了字串的運算和陣列的運算,字串的運算中包括判斷是否相等,整數的四則運算,浮點數四則運算;陣列的運算包含了陣列的增刪查改。