羽夏 Bash 簡明教程(上)

寂靜的羽夏發表於2022-05-13

寫在前面

  該文章根據 the unix workbench 中的 Bash Programming 進行漢化處理並作出自己的整理,並參考 Bash 指令碼教程BashPitfalls 相關內容進行補充修正。一是我對 Bash 的學習記錄,二是對大家學習 Bash 有更好的幫助。如對該博文有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。本篇博文可能比較冗長,請耐心閱讀和學習。

Bash 簡介

  Bash 是 Unix 系統和 Linux 系統的一種 Shell(命令列環境),是目前絕大多數 Linux 發行版的預設 Shell。Shell 是一個程式,提供一個與使用者對話的環境。這個環境只有一個命令提示符,讓使用者從鍵盤輸入命令,所以又稱為命令列環境。Shell 接收到使用者輸入的命令,將命令送入作業系統執行,並將結果返回給使用者。其次,Shell 是一個命令直譯器,解釋使用者輸入的命令。它支援變數、條件判斷、迴圈操作等語法,所以使用者可以用 Shell 命令寫出各種小程式,又稱為指令碼,這個也是本篇重點講解的部分。

開發環境

  既然學的是 Bash ,那麼必然是任何 Linux 發行版。至於用什麼編輯器都可以。不過我個人建議使用VSCode + Bash Extension Pack進行學習,因為它有糾錯功能,並指出一些不合適的寫法,我也會在本篇末也會介紹一些。我羽夏使用的就是我建議使用的工具,我也預設你使用的是它,如果用其他的自己酌情參考。下面開始進入正題:

入門

  好,在陽光明媚、微風和煦(bushi)的一天,我們信心滿滿的開始了 Bash 的學習旅程。熟練的開啟 VSCode ,進入 Bash 工作區,然後新建了一個名字叫math.sh的乾淨純潔的檔案,然後輸入了以下內容:

#!/bin/bash
# File: math.sh

  注意,你不要複製和貼上這些行到檔案中,儘管你應該準確地輸入我輸入的的內容。寫程式碼時你應該儘可能多地自己練習,因為寫程式碼是一個實踐性十分強的專案,不能眼高手低。這兩行都以#開頭,在 Bash 程式語言中,在#之後鍵入的任何內容都將被忽略(除非位於花括號之間,但這僅在非常特定的情況下)。#允許你在程式碼中進行註釋,以便以後的你理解你當下寫的程式碼,也可以使其他人更快地知道你的程式是如何設計的。
  但是,上面的內容的第一行程式碼具有特殊的含義,雖然它是註釋。該行被稱為 Shebang 行。指令碼的第一行通常是指定直譯器,即這個指令碼必須通過什麼直譯器執行,而這一行以#!字元開頭,正如上面展示的。
  #!後面就是指令碼直譯器的位置,Bash 指令碼的直譯器一般是/bin/sh/bin/bash

#!/bin/sh

  或者

#!/bin/bash

  #!與指令碼直譯器之間有沒有空格,都是可以的。如果 Bash 直譯器不放在目錄/bin,指令碼就無法執行了。為了保險,可以寫成下面這樣。

#!/usr/bin/env bash

  上面命令使用env命令,這個命令總是在/usr/bin目錄),返回 Bash 可執行檔案的位置,從而避免了這個問題。
  Shebang 行不是必需的,但是建議加上這行。如果缺少該行,就需要手動將指令碼傳給直譯器。舉例來說,指令碼是script.sh,有 Shebang 行的時候,可以直接呼叫執行。

wingsummer@wingsummer-PC ~ → ./script.sh

  上面例子中,script.sh是指令碼檔名。指令碼通常使用.sh字尾名,不過這不是必需的。如果沒有 Shebang 行,就只能手動將指令碼傳給直譯器來執行。

wingsummer@wingsummer-PC ~ → /bin/sh ./script.sh

  或者

wingsummer@wingsummer-PC ~ → bash ./script.sh

  注意,“只要指定了 Shebang 行的指令碼,可以直接執行”這句話有個前提條件,就是指令碼需要有執行許可權,否則這行也是沒作用的。

數學運算

內容講解

  Bash 程式語言可以完成非常基本的演算法,現在你在 VSCode 開啟了math.sh這個檔案,我們開始輸入下面內容:

#!/usr/bin/env bash
# File: math.sh

expr 5 + 2
expr 5 - 2
expr 5 \* 2
expr 5 / 2

  儲存,並在終端去執行它,你將會得到如下結果:

7
3
10
2

  讓我們分析一下剛才建立的 Bash 指令碼中發生了什麼。Bash 按照從檔案的第一行到最後一行的順序執行程式。expr命令可用於計算 Bash 表示式。表示式只是一個有效的 Bash 程式碼字串,在執行時會生成一個結果。您已經熟悉的加法(+)、減法(-)和乘法(*)的算術運算子的工作方式與您預期的一樣。請注意:在進行乘法運算時,需要轉義星號字元,否則 Bash 會認為您正在嘗試建立正規表示式。由於5 / 2 = 2.5,除法運算子(/)的工作方式與預期不同。Bash 進行整數除法,這意味著一個數除以另一個數的結果總是向下舍入到最接近的整數。讓我們看一下命令列上的幾個示例:

expr 1 / 3
expr 10 / 3
expr 40 / 21
expr 40 / 20

  另一個你可能不熟悉的數值運算子是模運算子(%)。模運算子返回整數除法後的餘數。在整數除法中,如果A / B = CA % B = D,那麼B * C + D = A。讓我們看看命令列上的一些示例:

expr 1 % 3
expr 10 % 3
expr 40 % 21
expr 40 % 20

  然後是它的執行結果:

1
1
19
0

  注意,當一個數完全可被另一個數整除時,模的結果為零。如果你想做更復雜的數學運算,比如分數和小數,那麼我強烈建議將echo和名為bc的臺式計算器程式結合起來。開啟一個名為bigmath.sh的新檔案並輸入以下內容:

#!/usr/bin/env bash
# File: bigmath.sh

echo "22 / 7" | bc -l
echo "4.2 * 9.15" | bc -l
echo "(6.5 / 0.5) + (6 * 2.2)" | bc -l

  如下是計算結果:

3.14285714285714285714
38.430
26.20000000000000000000

  為了在計算中使用十進位制數,可以使用-l標誌將任何數學字串傳輸到bc

內容小結

  • Bash 程式從檔案的第一行到最後一行按順序執行。
  • #後面寫的任何東西都是註釋,Bash 不會執行。
  • 可以使用expr命令執行簡單的算術運算。
  • 通過使用echo將字串表示式傳輸到bc中,執行更復雜的數學運算。

小試牛刀

  1. 請使用命令列檢視bc的幫助手冊。
  2. bc互動控制檯進行一些數運算。
  3. 在一個檔案中寫一些等式,然後將該檔案作為引數提供給bc
? 點選檢視答案 ?
# 1:
wingsummer@wingsummer-PC ~ → man bc

# 2:略

# 3:

wingsummer@wingsummer-PC ~ → echo "1+8" > test.txt
wingsummer@wingsummer-PC ~ → bc test.txt
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
9

變數

內容講解

  Bash 變數分成環境變數和自定義變數兩類。環境變數是 Bash 環境自帶的變數,進入 Shell 時已經定義好了,可以直接使用。它們通常是系統定義好的,也可以由使用者從父 Shell 傳入子 Shell。使用env命令或printenv命令,可以顯示所有環境變數。建立變數的時候,變數名必須遵守下面的規則:

  • 變數儘量全為小寫
  • 變數開頭必須要以字母開頭
  • 變數只能包含英文字元和下劃線_
  • 變數之間的單詞最好要用下劃線分割,不允許出現空格和標點符號

  如果遵循這些規則,你就可以避免意外地覆蓋儲存在環境變數中的資料。
  可以使用等號(=)將資料分配給變數。儲存在變數中的資料可以是字串或數字。現在讓我們在命令列上建立一個變數:

myvar="hello world"

  變數名在等號的左側,儲存在該變數中的資料在等號的右側。請注意,等號兩邊都沒有空格,分配變數時不允許使用空格:

myvar = "hello world"   #錯誤!

  為了列印變數(也稱為變數值)中的資料,我們可以使用echo。要檢索變數的值,必須在變數名稱前使用美元符號$

wingsummer@wingsummer-PC ~ → myvar="helloworld"
wingsummer@wingsummer-PC ~ → echo $myvar
helloworld

  通過使用let命令,可以使用算術運算子修改變數的值:

wingsummer@wingsummer-PC ~ → myvar=5
wingsummer@wingsummer-PC ~ → let newvar=$myvar
wingsummer@wingsummer-PC ~ → echo $newvar
6

  有時,您可能希望像在命令列上一樣執行命令,並將該命令的結果儲存在變數中。我們可以通過將命令用美元符號和括號$())括起來來實現這一點。這種語法稱為命令替換。執行該命令,然後替換為執行該命令所產生的字串。例如,如果我們想在獲取math.sh檔案的行數:

math_lines=$(cat math.sh | wc -l)
echo $math_lines

  帶美元符號的變數名也可以在其他字串中使用,以便將變數的值插入字串:

wingsummer@wingsummer-PC ~ → myvar="world"
wingsummer@wingsummer-PC ~ → echo "hello $myvar"
hello world

  在編寫 Bash 指令碼時,可以自由使用一些變數。讓我們建立一個名為vars.sh的新檔案。使用下面的程式碼:

#!/usr/bin/env bash
# File: vars.sh

echo "Script arguments: $*"
echo "First arg: $1. Second arg: $2."
echo "Number of arguments: $#"

  現在,讓我們嘗試以幾種不同的方式執行指令碼:

wingsummer@wingsummer-PC ~ → bash vars.sh
Script arguments:
First arg: . Second arg: .
Number of arguments: 0
wingsummer@wingsummer-PC ~ → bash vars.sh red
Script arguments: red
First arg: red. Second arg: .
Number of arguments: 1
wingsummer@wingsummer-PC ~ → bash vars.sh red blue
Script arguments: red blue
First arg: red. Second arg: blue.
Number of arguments: 2
wingsummer@wingsummer-PC ~ → bash vars.sh red blue green
Script arguments: red blue green
First arg: red. Second arg: blue.
Number of arguments: 3

  你的指令碼可以像命令列程式一樣接受引數。指令碼的第一個引數儲存在$1中,第二個引數儲存在$2中……但如果指令碼的引數多於9個,那麼第10個引數可以用${10}的形式引用,以此類推。傳遞給指令碼的所有引數的陣列儲存在$*中,我們將在本章後面討論如何處理陣列。傳遞給指令碼的引數總數儲存在$#中。既然知道如何將引數傳遞給指令碼,我們就可以開始編寫自己的命令列工具了!
  下面我們繼續擴充套件一下這個特殊的變數:

變數 含義
$0 指令碼檔名
$1-$9 對應指令碼的第一個引數到第九個引數
$# 引數的總數
$* 全部的引數,引數之間使用變數$IFS值的第一個字元分隔,預設為空格,但是可以自定義
$@ 全部的引數,引數之間使用空格分隔
$? 上一個命令的退出碼,用來判斷上一個命令是否執行成功。返回值是0,表示上一個命令執行成功;如果不是零,表示上一個命令執行失敗
$$ 當前 Shell 的程式 ID
$_ 上一個命令的最後一個引數
$! 最近一個後臺執行的非同步命令的程式 ID
$- 當前 Shell 的啟動引數

  上面的特殊變數我就不一一做示例驗證了,感興趣的話可以自己試試。
  前面我們簡單提過讀取變數的值是前面加一個$,下面我們繼續討論讀取變數這個事情。
  讀取變數的時候,直接在變數名前加上$就可以了,但變數名也可以使用花括號{}包圍,比如$a也可以寫成${a}。這種寫法可以用於變數名與其他字元連用的情況,如下所示:

wingsummer@wingsummer-PC ~ → myvar="hello"
wingsummer@wingsummer-PC ~ → echo $myvar_world

wingsummer@wingsummer-PC ~ → echo ${myvar}_world
hello_world

  如果變數的值本身也是變數,可以使用${!varname}的語法,讀取最終的值。

wingsummer@wingsummer-PC ~ → myvar=USER
wingsummer@wingsummer-PC ~ → echo ${!myvar}
wingsummer

  如果變數值包含連續空格(或製表符和換行符),最好放在雙引號裡面讀取。示例如下:

wingsummer@wingsummer-PC ~ → a="1 2    3"
wingsummer@wingsummer-PC ~ → echo $a
1 2 3
wingsummer@wingsummer-PC ~ → echo "$a"
1 2    3

  這個也將會在篇末繼續討論。
  如果我使用了一個變數,我突然不想要了咋整。我們可以使用unset命令:

wingsummer@wingsummer-PC ~ → echo $myvar
USER
wingsummer@wingsummer-PC ~ → unset myvar
wingsummer@wingsummer-PC ~ → echo $myvar

  使用者建立的變數僅可用於當前 Shell,子 Shell 預設讀取不到父 Shell 定義的變數。有時我們有一種使用情況,我在一個 Shell 宣告的變數,我想讓它的子 Shell 也能用,我們可以將該變數進行匯出:

NAME=foo
export NAME

  上面命令輸出了變數NAME。變數的賦值和輸出也可以在一個步驟中完成。

export NAME=value

  上面命令執行後,當前 Shell 及隨後新建的子 Shell,都可以讀取變數$NAME
  對於引數,如果我想獲取,如果沒有返回預設值,這個我們通過 Shell 如何實現呢?如下是表格總結:

語法 含義
${varname:-word} 如果變數varname存在且不為空,則返回它的值,否則返回word。它的目的是返回一個預設值,比如${count:-0}表示變數count不存在時返回0
${varname:=word} 如果變數varname存在且不為空,則返回它的值,否則將它設為word,並且返回word。它的目的是設定變數的預設值,比如${count:=0}表示變數count不存在時返回0,且將count設為0
${varname:+word} 如果變數名存在且不為空,則返回word,否則返回空值。它的目的是測試變數是否存在,比如${count:+1}表示變數count存在時返回1(表示true),否則返回空值
${varname:?message} 如果變數varname存在且不為空,則返回它的值,否則列印出varname: message,並中斷指令碼的執行。如果省略了message,則輸出預設的資訊parameter null or not set.。它的目的是防止變數未定義,比如${count:?"undefined!"}表示變數count未定義時就中斷執行,丟擲錯誤,返回給定的報錯資訊undefined!

  宣告變數的方式不僅僅本篇介紹的,還有declarereadonly,由於感覺並不太常用就不介紹了,如果想詳細瞭解請閱讀 Bash 變數 相應的部分。

內容小結

  • 變數可以用等號運算子賦值。
  • 字串或數字可以分配給變數。
  • 變數的值可以在變數名前用美元符號$訪問。
  • 可以使用美元符號和括號語法(命令替換)執行命令並將輸出儲存在變數中。
  • 可以在自己的指令碼中使用美元符號和引數編號來訪問命令列引數。
  • Shell 有一些特殊的變數以訪問引數和其他資訊。
  • 定義變數有多種方式,比如直接等號賦值、使用declarereadonly宣告變數。
  • 定義的變數可刪除。

小試牛刀

  1. 編寫一個 Bash 程式,將兩個數字分配給不同的變數,然後程式列印這些變數的總和。
  2. 編寫一個 Bash 程式,將兩個字串分配給不同的變數,然後程式列印這兩個字串。要求列印兩次,第一次分行,第二次不分行。
  3. 編寫一個 Bash 程式,列印提供給該程式的引數個數乘以提供給該程式的第一個引數(假設該引數為整數)。
? 點選檢視答案 ?
# 1
num1=$1
num2=$2
echo $(($num1+$num2))

# 2
str1=$1
str2=$2
echo "$str1"
echo "$str2"
echo "$str1$str2"

#3
num1=$#
num2=$1
echo $(($num1*$num2))

算術運算

  由於前面的內容僅僅講解了簡單的算術運算作為入門,下面開始進行介紹算術運算:

算術表示式

  (())可以進行整數的算術運算,如下所示。

wingsummer@wingsummer-PC ~ → echo $((5+5))
10

  上面的$的作用就讀取算術運算的結果的意思,我們還可以拆成下面的程式碼:

wingsummer@wingsummer-PC ~ → ((NUM = 5+5))
wingsummer@wingsummer-PC ~ → echo $NUM
10

  它會自動忽略內部的空格,所以下面的寫法都正確,得到同樣的結果。

((2+2))          #寫法1
(( 2+2 ))        #寫法2
(( 2 + 2 ))      #寫法3

  它不返回值,命令執行的結果根據算術運算的結果而定。只要算術結果不是0,命令就算執行成功,至於是否成功我們可以使用環境變數$?進行獲取,這個會在之後的部分進行講解。
  (())支援的算術運算子如下:

  • + :加法
  • - :減法
  • * :乘法
  • / :除法(整除)
  • % :餘數
  • ** :指數
  • ++ :自增運算(字首或字尾)
  • -- :自減運算(字首或字尾)

  自增運算好自減運算的規則和C/C++是一樣的,作為字首是先運算後返回值,作為字尾是先返回值後運算。我們可以作出如下測試:

wingsummer@wingsummer-PC ~ → i=0
wingsummer@wingsummer-PC ~ → echo $i
0
wingsummer@wingsummer-PC ~ → echo $((i++))
0
wingsummer@wingsummer-PC ~ → echo $i
1
wingsummer@wingsummer-PC ~ → echo $((++i))
2
wingsummer@wingsummer-PC ~ → echo $i
2

數值的進位制

  Bash 的數值預設都是十進位制,但是在算術表示式中,也可以使用其他進位制。

  • number:沒有任何特殊表示法的數字是十進位制數(以10為底)。
  • 0number:八進位制數。
  • 0xnumber:十六進位制數。
  • base#number:base進位制的數。

  下面是一些例子:

wingsummer@wingsummer-PC ~ → echo $((0xff))
255
wingsummer@wingsummer-PC ~ → echo $((2#11111111))
255

位運算

  $(())支援以下的二進位制位運算子。

  • << :位左移運算,把一個數字的所有位向左移動指定的位。
  • >> :位右移運算,把一個數字的所有位向右移動指定的位。
  • & :位的與運算,對兩個數字的所有位執行一個AND操作。
  • | :位的或運算,對兩個數字的所有位執行一個OR操作。
  • ~ :位的反運算,對一個數字的所有位取反。
  • ^ :位的異或運算,對兩個數字的所有位執行一個XOR操作。

  如下是一些例子:

wingsummer@wingsummer-PC ~ → echo $((16>>2))
4
wingsummer@wingsummer-PC ~ → echo $((16<<2))
64
wingsummer@wingsummer-PC ~ → echo $((17&3))
1
wingsummer@wingsummer-PC ~ → echo $((17|3))
19
wingsummer@wingsummer-PC ~ → echo $((17^3))
18

邏輯運算

  $(())支援以下的邏輯運算子:

  • < :小於
  • > :大於
  • <= :小於或相等
  • >= :大於或相等
  • == :相等
  • != :不相等
  • && :邏輯與
  • || :邏輯或
  • ! :邏輯否
  • expr1?expr2:expr3 :三元條件運算子。若表示式expr1的計算結果為真,則執行表示式expr2,否則執行表示式expr3

  如下是幾個例子:

wingsummer@wingsummer-PC ~ → echo $((3 > 2))
1
wingsummer@wingsummer-PC ~ → echo $(( (3 > 2) || (4 <= 1) ))
1
wingsummer@wingsummer-PC ~ → a=0
wingsummer@wingsummer-PC ~ → echo $((a<1 ? 1 : 0))
1
wingsummer@wingsummer-PC ~ → echo $((a>1 ? 1 : 0))
0

賦值運算

  算術表示式$(())可以執行賦值運算,先看一個例子:

wingsummer@wingsummer-PC ~ → echo $((a=1))
1
wingsummer@wingsummer-PC ~ → echo $a
1

  上面例子中,a=1對變數a進行賦值。這個式子本身也是一個表示式,返回值就是等號右邊的值。
  $())支援的賦值運算子,有以下這些:

  • parameter = value :簡單賦值
  • parameter += value等價於parameter = parameter + value
  • parameter -= value :等價於parameter = parameter – value
  • parameter *= value :等價於parameter = parameter * value
  • parameter /= value:等價於parameter = parameter / value
  • parameter %= value:等價於parameter = parameter % value
  • parameter <<= value:等價於parameter = parameter << value
  • parameter >>= value:等價於parameter = parameter >> value
  • parameter &= value:等價於parameter = parameter & value
  • parameter |= value:等價於parameter = parameter | value
  • parameter ^= value:等價於parameter = parameter ^ value

  下面是一個例子:

wingsummer@wingsummer-PC ~ → foo=5
wingsummer@wingsummer-PC ~ → echo $((foo*=2))
10

  如果在表示式內部賦值,可以放在圓括號中,否則會報錯。

wingsummer@wingsummer-PC ~ → echo $(( a<1 ? (a+=1) : (a-=1) ))

求值運算

  逗號,$(())內部是求值運算子,執行前後兩個表示式,並返回後一個表示式的值,例子如下:

wingsummer@wingsummer-PC ~ → echo $((foo = 1 + 2, 3 * 4))
12
wingsummer@wingsummer-PC ~ → echo $foo
3

  上述命令逗號前後兩個表示式都會執行,然後返回後一個表示式的值12

使用者輸入

內容講解

  如果你正在為自己或其他人制作Bash程式,那麼獲得使用者輸入的一種方法就是指定使用者提供給您的程式的引數。您還可以通過使用read命令暫時停止程式的執行,要求使用者在命令列中鍵入字串。讓我們編寫一個小指令碼,從中可以瞭解read命令的工作原理:

#!/usr/bin/env bash
# File: letsread.sh

echo "Type in a string and then press Enter:"
read response
echo "You entered: $response"

  然後執行該指令碼:

Type in a string and then press Enter:
|

  上面的|表示游標的位置,我們輸入Hello!,然後回車:

Type in a string and then press Enter:
Hello!
You entered: Hello!

  read命令提示使用者鍵入字串,使用者提供的字串儲存在指令碼中指定給read命令的變數中,下面我們來看一下它的高階用法,首先得了解它的使用和引數,read命令的格式如下。

read [-options] [variable...]

  具體引數總結如下:

引數 含義
-t 設定超時的秒數。如果超過了指定時間,使用者仍然沒有輸入,指令碼將放棄等待,繼續向下執行
-p 指定使用者輸入的提示資訊
-a 把使用者的輸入賦值給一個陣列,從零號位置開始
-n 指定只讀取若干個字元作為變數值,而不是整行讀取
-e 允許使用者輸入的時候,使用readline庫提供的快捷鍵,比如自動補全。
-r 不把使用者輸入的反斜槓字元解釋為轉義字元
-s 使得使用者的輸入不顯示在螢幕上,這常常用於輸入密碼或保密資訊

  當然read的引數並不僅僅這些,剩下的就不太常用了,具體例子可以自己寫進行測試,這裡由於篇幅就不進行了。

內容小結

  • read儲存使用者在變數中提供的字串。

小試牛刀

  1. 編寫一個指令碼,要求使用者輸入形容詞、名詞和動詞,然後使用這些詞造句。
? 點選檢視答案 ?
read -r -p "請輸入形容詞:" adj
read -r -p "請輸入名詞:" n
read -r -p "請輸入動詞:" v

echo "$v $adj $v"

條件判斷

內容講解

  在編寫計算機程式時,您的程式能夠根據引數、檔案和環境變數等輸入做出決策通常很有用。Bash 提供了建立類似於數學方程的邏輯表示式的機制。可以對這些邏輯表示式求值,直到它們為真或假。事實上,truefalse都是簡單的 Bash 命令。現在我們測試一下:

true
false

  貌似看起來他們差不多。為了瞭解它們是如何工作的,我們需要稍微瞭解一下 Unix 的特性。每當在命令列上執行程式時,通常會發生以下兩種情況之一:要麼命令執行成功,要麼出現錯誤。就錯誤而言,程式可能會出現很多錯誤,Unix 可以根據發生的錯誤型別採取不同的操作。例如,如果我輸入了終端中不存在的命令名,那麼我將看到一個錯誤:

this_command_does_not_exist

  由於該命令不存在,它會建立一種特定型別的錯誤,該錯誤由程式的退出狀態指示。程式的退出狀態是一個整數,表示程式是否成功執行或是否發生錯誤。上次程式執行的退出狀態儲存在$?中。我們可以通過echo檢視最後一個程式的退出狀態:

echo $?

  這個特定的退出狀態向 Shell 發出指示,它應該向控制檯列印一條錯誤訊息。成功執行的程式的退出狀態是什麼?我們來看看:

echo I will succeed.
echo $?

  它的輸出如下:

I will succeed.
0

  因此,成功程式的退出狀態為0。現在我們來看一下truefalse的退出狀態:

wingsummer@wingsummer-PC ~ → true
wingsummer@wingsummer-PC ~ → echo $?
0
wingsummer@wingsummer-PC ~ → false
wingsummer@wingsummer-PC ~ → echo $?
1

  如您所見,true的退出狀態為0false的退出狀態為1。由於這些程式沒有做很多其他事情,所以可以將true定義為始終具有0退出狀態的程式,將false定義為始終具有1退出狀態的程式。
  在討論邏輯運算子時,瞭解這些程式的退出狀態很重要:AND運算子&&OR運算子||ANDOR運算子可用於在命令列上有條件地執行程式。當一個程式的執行取決於另一個程式的退出狀態時,就會發生條件執行。例如,對於AND運算子,只有當&&左側的程式的退出狀態為0時,才會執行&&右側的程式。讓我們來看一些小例子:

true && echo "Program 1 was executed."
false && echo "Program 2 was executed."

  由於false的退出狀態為1,因此echo "Program 2 was executed."不會被執行,因此不會為該命令向控制檯列印任何內容。可以將多個和運算子連結在一起,如下所示:

false && true && echo Hello
echo 1 && false && echo 3
echo Athos && echo Porthos && echo Aramis

  在由AND運算子連線在一起的一系列程式中,程式右側任何非零退出狀態的程式都不會執行。OR運算子||遵循一組類似的原則。||右側的命令只有在左側的命令失敗,因此退出狀態不是0時才會執行。讓我們看看它是如何工作的:

true || echo "Program 1 was executed."
false || echo "Program 2 was executed."

  結果只執行了echo "Program 2 was executed.",因為false的退出狀態為非零。你可以組合多個OR運算子,以便只執行退出狀態為0的第一個程式:

false || echo 1 || echo 2
echo 3 || false || echo 4
echo Athos || echo Porthos || echo Aramis

  可以在命令中組合ANDOR運算子,這些命令從左到右求值:

echo Athos || echo Porthos && echo Aramis
echo Gaspar && echo Balthasar || echo Melchior

  通過組合ANDOR運算子,可以精確控制執行某些命令的條件。
  使 Bash 指令碼能夠做出決策非常有用。條件執行允許您根據某些程式是成功還是失敗來控制執行這些程式的情況,但您也可以構造條件表示式,這些表示式是等價於truefalse的邏輯語句。條件表示式要麼比較兩個值,要麼詢問關於一個值的問題。條件表示式總是在雙中括號[[]]之間,它們要麼使用邏輯標誌,要麼使用邏輯運算子。例如,有幾個邏輯標誌可用於比較兩個整數。如果我們想知道一個整數是否大於另一個整數,我們可以使用-gt,即大於。在命令列中輸入以下簡單條件表示式:

[[ 4 -gt 3 ]]

  上面的邏輯表示式是這樣的:4是否大於3?沒有結果被列印到控制檯,所以讓我們檢查該表示式的退出狀態:

wingsummer@wingsummer-PC ~ → echo $?
0

  該程式的退出狀態似乎為0,與true的退出狀態相同。這個條件表示式表示[[ 4 -gt 3 ]]等價於true,我們當然知道這在邏輯上是一致的,4實際上是大於3的。讓我們看看如果我們翻轉表示式會發生什麼,我們問3是否大於4

[[ 3 -gt 4 ]]

  同樣,控制檯上沒有列印任何內容,因此我們將檢視退出狀態:

wingsummer@wingsummer-PC ~ → echo $?
1

  顯然3不大於4,所以這個錯誤的邏輯表示式導致退出狀態為1,這與false的退出狀態相同。因為它們具有相同的退出狀態[[ 3 -gt 4 ]]false本質上是等價的。為了快速測試條件表示式的邏輯值,我們可以使用ANDOR運算子,以便表示式在為真時列印t,在為假時列印f

[[ 4 -gt 3 ]] && echo t || echo f
[[ 3 -gt 4 ]] && echo t || echo f

  這是一個小技巧,可以用來快速檢視邏輯表示式的結果值。這些二進位制邏輯表示式比較兩個值,但也有一元邏輯表示式只檢視一個值。例如,可以使用-e邏輯標誌測試檔案是否存在:

wingsummer@wingsummer-PC ~ → cd ~ # 假設我們的 math.sh 在該目錄下
wingsummer@wingsummer-PC ~ → [[ -e math.sh ]] && echo t || echo f
t

  大多數情況下,在編寫 Bash 指令碼時,您不會比較兩個原始值,也不會試圖找出關於一個原始值的資訊,而是希望建立一個關於變數中包含的值的邏輯語句。變數的行為就像邏輯表示式中的原始值。讓我們看幾個例子:

number=7
[[ $number -gt 3 ]] && echo t || echo f
[[ $number -gt 10 ]] && echo t || echo f
[[ -e $number ]] && echo t || echo f

  7大於3,儘管它不大於10,而且這個目錄中沒有名為7的檔案。還有其他幾種不同的邏輯標誌,如下表格所示:

標誌 含義 示例
-gt 大於 [[ $planets -gt 8 ]]
-ge 大於等於 [[ $votes -ge 270 ]]
-eq 等於 [[ $fingers -eq 10 ]]
-ne 不等於 [[ $pages -ne 0 ]]
-le 小於等於 [[ $candles -le 9 ]]
-lt 小於 [[ $wives -lt 2 ]]
-e 檔案是否存在 [[ -e $taxes_2016 ]]
-d 資料夾是否存在 [[ -d $photos ]]
-z 字串長度是否為零 [[ -z $name ]]
-n 字串長度是否非零 [[ -n $name ]]

  在繼續下一節之前,請嘗試在命令列中使用這些標誌。除了邏輯標誌,還有邏輯運算子。最有用的邏輯運算子之一是正規表示式匹配運算子=~。正規表示式匹配運算子將字串與正規表示式進行比較,如果字串與正規表示式匹配,則表示式等價於true,否則等價於false。讓我們用兩種不同的方法測試這個操作符:

[[ rhythms =~ [aeiou] ]] && echo t || echo f
my_name=sean
[[ $my_name =~ ^s.+n$ ]] && echo t || echo f

  還有NOT運算子!,它反轉任何條件表示式的值。NOT運算子將真表示式轉換為假表示式,反之亦然。讓我們來看幾個使用NOT運算子的示例:

[[ 7 -gt 2 ]] && echo t || echo f
[[ ! 7 -gt 2 ]] && echo t || echo f
[[ 6 -ne 3 ]] && echo t || echo f
[[ ! 6 -ne 3 ]] && echo t || echo f

  下面是一些有用的邏輯運算子的列表,以供參考:

標誌 含義 示例
=~ 匹配正規表示式 [[ $consonants =~ [aeiou] ]]
= 判斷字串是否相同 [[ $password = "pegasus" ]]
!= 判斷字串是否不同 [[ $fruit != "banana" ]]
! 取反 [[ ! "apple" =~ ^b ]]

  條件表示式非常強大,因為可以使用它們來控制正在編寫的 Bash 程式的執行方式。Bash 程式設計中的一個基本構造是IF語句。IF語句中編寫的程式碼只有在特定條件為true時才會執行,否則程式碼將被跳過。讓我們用IF語句編寫一個小程式:

#!/usr/bin/env bash
# File: simpleif.sh

echo "Start program"

if [[ $1 -eq 4 ]]
then
  echo "You entered $1"
fi

echo "End program"

  首先,這個程式將列印Start program,然後IF語句將檢查條件表示式[[ $1 -eq 4 ]]是否為真。只有將4作為指令碼的第一個引數時,才是真。如果條件表示式為true,那麼它將執行在thenfi之間的程式碼,否則它將跳過該程式碼。最後,程式將列印End program。讓我們嘗試以幾種不同的方式執行這個 Bash 程式:

wingsummer@wingsummer-PC ~ → bash simpleif.sh
Start program
End program
wingsummer@wingsummer-PC ~ → bash simpleif.sh 77
Start program
End program
wingsummer@wingsummer-PC ~ → bash simpleif.sh 4
Start program
You entered 4
End program

  直到最後,由於該指令碼的第一個引數是44等於4,因此執行了IF語句中的程式碼。可以將IF語句與ELSE語句配對。ELSE語句僅在IF語句計算的條件表示式為false時執行。讓我們建立一個使用ELSE語句的簡單程式:

#!/usr/bin/env bash
# File: simpleifelse.sh

echo "Start program"

if [[ $1 -eq 4 ]]
then
  echo "Thanks for entering $1"
else
  echo "You entered: $1, not what I was looking for."
fi

echo "End program"

  我們繼續相同的操作:

wingsummer@wingsummer-PC ~ → bash simpleifelse.sh 4
Start program
Thanks for entering 4
End program
wingsummer@wingsummer-PC ~ → bash simpleifelse.sh 3
Start program
You entered: 3, not what I was looking for.
End program

  當第一個引數是4時,條件表示式[[ $1 -eq 4]]true,因此IF語句中的程式碼執行,而ELSE語句中的程式碼未執行。當引數改為3時,條件表示式[[ $1 -eq 4]]為為false,因此ELSE語句中的程式碼執行,IF語句中的程式碼未執行。
  在IFELSE語句之間,還可以使用ELIF語句。這些語句的行為類似於IF語句,除非它們僅在前面的IFELIF語句都計算值為假,條件表示式時才被計算。讓我們使用ELIF建立一個簡短的程式:

#!/usr/bin/env bash
# File: simpleelif.sh

if [[ $1 -eq 4 ]]
then
  echo "$1 is my favorite number"
elif [[ $1 -gt 3 ]]
then
  echo "$1 is a great number"
else
  echo "You entered: $1, not what I was looking for."
fi

  我們如法炮製:

wingsummer@wingsummer-PC ~ → bash simpleelif.sh 4
4 is my favorite number
wingsummer@wingsummer-PC ~ → bash simpleelif.sh 5
5 is a great number
wingsummer@wingsummer-PC ~ → bash simpleelif.sh 2
You entered: 2, not what I was looking for.

  由於篇幅,我這裡就不細說了。我們還可以組合條件執行、條件表示式和IF/ELIF/ELSE語句,條件執行運算子&&||可用於IFELIF語句。讓我們來看一個在IF語句中使用這些運算子的示例:

#!/usr/bin/env bash
# File: condexif.sh

if [[ $1 -gt 3 ]] && [[ $1 -lt 7 ]]
then
  echo "$1 is between 3 and 7"
elif [[ $1 =~ "Jeff" ]] || [[ $1 =~ "Roger" ]] || [[ $1 =~ "Brian" ]]
then
  echo "$1 works in the Data Science Lab"
else
  echo "You entered: $1, not what I was looking for."
fi

  現在,讓我們用幾個不同的引數來測試這個指令碼:

wingsummer@wingsummer-PC ~ → bash condexif.sh 2
You entered: 2, not what I was looking for.
wingsummer@wingsummer-PC ~ → bash condexif.sh 4
4 is between 3 and 7
wingsummer@wingsummer-PC ~ → bash condexif.sh 6
6 is between 3 and 7
wingsummer@wingsummer-PC ~ → bash condexif.sh Jeff
Jeff works in the Data Science Lab
wingsummer@wingsummer-PC ~ → bash condexif.sh Brian
Brian works in the Data Science Lab
wingsummer@wingsummer-PC ~ → bash condexif.sh Sean
You entered: Sean, not what I was looking for.

  條件執行操作符的工作方式與它們在命令列上的工作方式相同。如果整個條件表示式的計算結果等效於true,則執行If語句中的程式碼,否則將跳過它。
  最後,我們應該注意,IF/ELIF/ELSE語句可以巢狀在其他IF語句中。下面是一個帶有巢狀語句的程式的小示例:

#!/usr/bin/env bash
# File: nested.sh

if [[ $1 -gt 3 ]] && [[ $1 -lt 7 ]]
then
  if [[ $1 -eq 4 ]]
  then
    echo "four"
  elif [[ $1 -eq 5 ]]
  then
    echo "five"
  else
    echo "six"
  fi
else
  echo "You entered: $1, not what I was looking for."
fi

  這裡就不測試了,通過IF語句,我們可以實現比較強大的指令碼。

內容小結

  • 所有 Bash 程式都有退出狀態。true的退出狀態為0false的退出狀態為1
  • 條件執行使用兩個運算子:AND &&和 OR ||,您可以使用它們根據退出狀態控制執行相應的命令。
  • 條件表示式始終位於雙中括號[[]]內。如果包含true斷言,則退出狀態為0;如果包含false斷言,則退出狀態為1
  • IF語句計算條件表示式。如果表示式為true,則執行If語句中的程式碼,否則將跳過它。
  • ELIFELSE語句也有助於控制 Bash 程式的流,IF語句可以巢狀在其他IF語句中。

小試牛刀

  1. 編寫一個 Bash 指令碼,將字串作為引數,如果字串以大寫字母開頭,則列印大寫開頭
  2. 編寫一個 Bash 指令碼,假設輸入一個正整數引數。判斷如果是偶數,則列印偶數;如果是奇數,則列印奇數
  3. 編寫一個包含兩個引數的 Bash 指令碼。如果兩個引數都是純數字,則列印它們的和,否則只列印兩個引數。
  4. 寫一個 Bash 指令碼,如果今天是星期五,列印今天是星期五。(提示:看一下 date 程式幫助)。
? 點選檢視答案 ?
# 1
echo "請輸入英文單詞"
read -r INPUT
if [[ $INPUT =~ ^[A-Z] ]]
then
    echo "大寫開頭"
fi

# 2
num=$1
if ((num % 2 == 0)); then
    echo "偶數"
else
    echo "奇數"
fi

#3
num1=$1
num2=$2

if [[ $num1 =~ [[:digit:]] ]] && [[ $num2 =~ [[:digit:]] ]] ;then
echo $((num1+num2))
else
echo "$1 $2"
fi

# 4
day=$(date +%w)
if ((day==5));then
echo "今天是星期五"
fi

擴充

  上面都是比較簡單的編寫帶有條件判斷語句指令碼的基礎知識,當然不能僅僅會IF語句,下面我們對條件判斷進行擴充知識:

if 結構

  if是最常用的條件判斷結構,只有符合給定條件時,才會執行指定的命令。它的語法如下:

if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi

  ifthen寫在同一行時,需要分號分隔。分號是 Bash 的命令分隔符。它們也可以寫成兩行,這時不需要分號。除了多行的寫法,if結構也可以寫成單行。

if true; then echo 'hello world'; fi
if false; then echo "It's true."; fi

test 命令

  if結構的判斷條件,一般使用test命令,它有三種形式。

# 寫法一
test expression

# 寫法二
[ expression ]

# 寫法三
[[ expression ]]

  上面三種形式是等價的,但是第三種形式還支援正則判斷,前兩種不支援。
  上面的expression是一個表示式。這個表示式為真,test命令執行成功,返回值為0;表示式為假,test命令執行失敗,返回值為1。注意,第二種和第三種寫法,[]與內部的表示式之間必須有空格。下面把test命令的三種形式,用在if結構中,判斷一個檔案是否存在:

# 寫法一
if test -e /tmp/foo.txt ; then
  echo "Found foo.txt"
fi

# 寫法二
if [ -e /tmp/foo.txt ] ; then
  echo "Found foo.txt"
fi

# 寫法三
if [[ -e /tmp/foo.txt ]] ; then
  echo "Found foo.txt"
fi

判斷表示式

  if關鍵字後面,跟的是一個命令。這個命令可以是test命令,也可以是其他命令。命令的返回值為0表示判斷成立,否則表示不成立。因為這些命令主要是為了得到返回值,所以可以視為表示式。常用的判斷表示式有下面這些:

檔案判斷

  以下表示式用來判斷檔案狀態:

  • [ -a file ] :如果file存在,則為true
  • [ -b file ] :如果file存在並且是一個塊(裝置)檔案,則為true
  • [ -c file ] :如果file存在並且是一個字元(裝置)檔案,則為true
  • [ -d file ] :如果file存在並且是一個目錄,則為true
  • [ -e file ] :如果file存在,則為true
  • [ -f file ] :如果file存在並且是一個普通檔案,則為true
  • [ -g file ] :如果file存在並且設定了組ID,則為true
  • [ -G file ] :如果file存在並且屬於有效的組ID,則為true
  • [ -h file ] :如果 file 存在並且是符號連結,則為true
  • [ -k file ] :如果 file 存在並且設定了它的“sticky bit”,則為true。
  • [ -L file ] :如果file存在並且是一個符號連結,則為true
  • [ -N file ] :如果file存在並且自上次讀取後已被修改,則為true
  • [ -O file ] :如果file存在並且屬於有效的使用者ID,則為true
  • [ -p file ] :如果file存在並且是一個命名管道,則為true
  • [ -r file ] :如果file存在並且可讀(當前使用者有可讀許可權),則為true
  • [ -s file ] :如果file存在且其長度大於零,則為true
  • [ -S file ] :如果file存在且是一個網路socket,則為true
  • [ -t fd ] :如果fd是一個檔案描述符,並且重定向到終端,則為true。這可以用來判斷是否重定向了標準輸入/輸出/錯誤。
  • [ -u file ] :如果file存在並且設定了setuid位,則為true
  • [ -w file ] :如果file存在並且可寫(當前使用者擁有可寫許可權),則為true
  • [ -x file ] :如果file存在並且可執行(有效使用者有執行/搜尋許可權),則為true
  • [ file1 -nt file2 ] :如果FILE1FILE2的更新時間最近,或者FILE1存在而FILE2不存在,則為true
  • [ file1 -ot file2 ] :如果FILE1FILE2的更新時間更舊,或者FILE2存在而FILE1不存在,則為true
  • [ FILE1 -ef FILE2 ] :如果FILE1FILE2引用相同的裝置和inode編號,則為true

  下面是一個比較完整的示例:

#!/bin/bash

FILE=~/.bashrc

if [ -e "$FILE" ]; then
  if [ -f "$FILE" ]; then
    echo "$FILE is a regular file."
  fi
  if [ -d "$FILE" ]; then
    echo "$FILE is a directory."
  fi
  if [ -r "$FILE" ]; then
    echo "$FILE is readable."
  fi
  if [ -w "$FILE" ]; then
    echo "$FILE is writable."
  fi
  if [ -x "$FILE" ]; then
    echo "$FILE is executable/searchable."
  fi
else
  echo "$FILE does not exist"
  exit 1
fi

  上面程式碼中,$FILE要放在雙引號之中,這樣可以防止變數$FILE為空,從而出錯。因為$FILE如果為空,這時[ -e $FILE ]就變成[ -e ],這會被判斷為真。而$FILE放在雙引號之中,[ -e "$FILE" ]就變成[ -e "" ],這會被判斷為假。

字串判斷

  以下表示式用來判斷字串:

[ string ] :如果string不為空(長度大於0),則判斷為真。
[ -n string ] :如果字串string的長度大於零,則判斷為真。
[ -z string ] :如果字串string的長度為零,則判斷為真。
[ string1 = string2 ] :如果string1string2相同,則判斷為真。
[ string1 == string2 ] 等同於[ string1 = string2 ]
[ string1 != string2 ] :如果string1string2不相同,則判斷為真。
[ string1 '>' string2 ] :如果按照字典順序string1排列在string2之後,則判斷為真。
[ string1 '<' string2 ] :如果按照字典順序string1排列在string2之前,則判斷為真。

  注意:test命令內部的><,必須用引號引起來(或者是用反斜槓轉義)。否則,它們會被 shell 解釋為重定向操作符。下面是一個示例。

#!/bin/bash

ANSWER=maybe

if [ -z "$ANSWER" ]; then
  echo "There is no answer." >&2
  exit 1
fi
if [ "$ANSWER" = "yes" ]; then
  echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
  echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
  echo "The answer is MAYBE."
else
  echo "The answer is UNKNOWN."
fi

  上面程式碼中,首先確定$ANSWER字串是否為空。如果為空,就終止指令碼,並把退出狀態設為1。注意,這裡的echo命令把錯誤資訊There is no answer.重定向到標準錯誤,這是處理錯誤資訊的常用方法。如果$ANSWER字串不為空,就判斷它的值是否等於yesno或者maybe
  注意,字串判斷時,變數要放在雙引號之中,比如[ -n "$COUNT" ],否則變數替換成字串以後,test命令可能會報錯,提示引數過多。另外,如果不放在雙引號之中,變數為空時,命令會變成[ -n ],這時會判斷為真。如果放在雙引號之中,[ -n "" ]就判斷為假。

整數判斷

  下面的表示式用於判斷整數:

[ integer1 -eq integer2 ] :如果integer1等於integer2,則為true
[ integer1 -ne integer2 ] :如果integer1不等於integer2,則為true
[ integer1 -le integer2 ] :如果integer1小於或等於integer2,則為true
[ integer1 -lt integer2 ] :如果integer1小於integer2,則為true
[ integer1 -ge integer2 ] :如果integer1大於或等於integer2,則為true
[ integer1 -gt integer2 ] :如果integer1大於integer2,則為true

  下面是一個用法的例子:

#!/bin/bash

INT=-5

if [ -z "$INT" ]; then
  echo "INT is empty." >&2
  exit 1
fi
if [ $INT -eq 0 ]; then
  echo "INT is zero."
else
  if [ $INT -lt 0 ]; then
    echo "INT is negative."
  else
    echo "INT is positive."
  fi
  if [ $((INT % 2)) -eq 0 ]; then
    echo "INT is even."
  else
    echo "INT is odd."
  fi
fi

  上面例子中,先判斷變數$INT是否為空,然後判斷是否為0,接著判斷正負,最後通過求餘數判斷奇偶。

算術判斷

  Bash 還提供了(())作為算術條件,進行算術運算的判斷:

if ((3 > 2)); then
  echo "true"
fi

  上面程式碼執行後,會列印出true。注意,算術判斷不需要使用test命令,而是直接使用(())結構。這個結構的返回值,決定了判斷的真假。如果算術計算的結果是非零值,則表示判斷成立。這一點跟命令的返回值正好相反,需要小心。

wingsummer@wingsummer-PC ~ →  if ((1)); then echo "It is true."; fi
It is true.
wingsummer@wingsummer-PC ~ →  if ((0)); then echo "It is true."; else echo "it is false."; fi
It is false.

  上面例子中,((1))表示判斷成立,((0))表示判斷不成立。算術條件(())也可以用於變數賦值:

wingsummer@wingsummer-PC ~ →  if (( foo = 5 ));then echo "foo is $foo"; fi
foo is 5

  上面例子中,(( foo = 5 ))完成了兩件事情。首先把5賦值給變數foo,然後根據返回值5,判斷條件為真。注意,賦值語句返回等號右邊的值,如果返回的是0,則判斷為假。

wingsummer@wingsummer-PC ~ → if (( foo = 0 ));then echo "It is true.";else echo "It is false."; fi
It is false.

  下面是用算術條件改寫的數值判斷指令碼:

#!/bin/bash

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
  if ((INT == 0)); then
    echo "INT is zero."
  else
    if ((INT < 0)); then
      echo "INT is negative."
    else
      echo "INT is positive."
    fi
    if (( ((INT % 2)) == 0 )); then
      echo "INT is even."
    else
      echo "INT is odd."
    fi
  fi
else
  echo "INT is not an integer." >&2
  exit 1
fi

  只要是算術表示式,都能用於(())語法。

case 結構

  case結構用於多值判斷,可以為每個值指定對應的命令,跟包含多個elifif結構等價,但是語義更好。它的語法如下:

case expression in
  pattern )
    commands ;;
  pattern )
    commands ;;
  ...
esac

  上面程式碼中,expression是一個表示式,pattern是表示式的值或者一個模式,可以有多條,用來匹配多個值,每條以兩個分號;;結尾。

#!/bin/bash

echo -n "輸入一個1到3之間的數字(包含兩端)> "
read character
case $character in
  1 ) echo 1
    ;;
  2 ) echo 2
    ;;
  3 ) echo 3
    ;;
  * ) echo 輸入不符合要求
esac

  上面例子中,最後一條匹配語句的模式是*,這個萬用字元可以匹配其他字元和沒有輸入字元的情況,類似ifelse部分。下面是另一個例子:

#!/bin/bash

OS=$(uname -s)

case "$OS" in
  FreeBSD) echo "This is FreeBSD" ;;
  Darwin) echo "This is Mac OSX" ;;
  AIX) echo "This is AIX" ;;
  Minix) echo "This is Minix" ;;
  Linux) echo "This is Linux" ;;
  *) echo "Failed to identify this OS" ;;
esac

  上面的例子判斷當前是什麼作業系統。case的匹配模式還可以使用各種萬用字元,下面是一些例子:

  • a ) :匹配a
  • a|b ):匹配ab
  • [[:alpha:]] ) :匹配單個字母。
  • ??? ) :匹配3個字元的單詞。
  • *.txt ) :匹配.txt結尾。
  • * ) :匹配任意輸入,通過作為case結構的最後一個模式。

  然後我們看一下示例程式碼:

#!/bin/bash

echo -n "輸入一個字母或數字 > "
read character
case $character in
  [[:lower:]] | [[:upper:]] ) echo "輸入了字母 $character"
                              ;;
  [0-9] )                     echo "輸入了數字 $character"
                              ;;
  * )                         echo "輸入不符合要求"
esac

  上面例子中,使用萬用字元[[:lower:]] | [[:upper:]]匹配字母,[0-9]匹配數字。Bash 4.0之前,case結構只能匹配一個條件,然後就會退出case結構。Bash 4.0之後,允許匹配多個條件,這時可以用;;&終止每個條件塊:

#!/bin/bash
# test.sh

read -n 1 -p "Type a character > "
echo
case $REPLY in
  [[:upper:]])    echo "'$REPLY' is upper case." ;;&
  [[:lower:]])    echo "'$REPLY' is lower case." ;;&
  [[:alpha:]])    echo "'$REPLY' is alphabetic." ;;&
  [[:digit:]])    echo "'$REPLY' is a digit." ;;&
  [[:graph:]])    echo "'$REPLY' is a visible character." ;;&
  [[:punct:]])    echo "'$REPLY' is a punctuation symbol." ;;&
  [[:space:]])    echo "'$REPLY' is a whitespace character." ;;&
  [[:xdigit:]])   echo "'$REPLY' is a hexadecimal digit." ;;&
esac

  執行上面的指令碼,會得到下面的結果。

wingsummer@wingsummer-PC ~ → test.sh
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.

  可以看到條件語句結尾新增了;;&以後,在匹配一個條件之後,並沒有退出case結構,而是繼續判斷下一個條件。

小結

  由於本篇篇幅原因,暫時介紹這些,剩下的重要的知識點將會在下一篇繼續。

相關文章