羽夏 Bash 簡明教程(上)

寫在前面

  該文章根據 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結構,而是繼續判斷下一個條件。

小結

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