跳轉到內容

Bash Shell 指令碼/Shell 算術

來自華夏公益教科書

Bash 中的算術表示式緊密模仿 C 中的表示式,因此它們與其他源自 C 的語言(如 C++、Java、Perl、JavaScript、C# 和 PHP)中的表示式非常相似。一個主要區別是 Bash 僅支援整數算術(整數),不支援浮點數算術(小數和分數);類似於 3 + 4 的東西意味著您所期望的(7),但類似於 3.4 + 4.5 的東西是語法錯誤。類似於 13 / 5 的東西是可以的,但執行整數除法,因此計算結果為 2 而不是 2.6。

算術擴充套件

[編輯 | 編輯原始碼]

也許使用算術表示式的最常見方式是在 *算術擴充套件* 中,其中算術表示式的結果用作命令的引數。算術擴充套件用 $(( … )) 表示。例如,此命令

echo $(( 3 + 4 * (5 - 1) ))

列印 19

expr (已棄用)

[編輯 | 編輯原始碼]

使用算術表示式的另一種方法是使用 Unix 程式 "expr",它在 Bash 支援數學之前很流行。[1] 與算術擴充套件類似,此命令

echo `expr 3 + 4 \* \( 5 - 1 \)`

列印 19。請注意,使用 "expr" 要求在乘法運算子 "*" 和括號之前使用跳脫字元 "\"。進一步注意每個運算子符號之間的空格,包括括號。

數值運算子

[編輯 | 編輯原始碼]

除了熟悉的表示法 +(加法)和 -(減法)之外,算術表示式還支援 *(乘法)、/(整數除法,如上所述)、%(模除,"餘數"運算;例如,11 除以 5 是 2 餘 1,所以 11 % 51)、以及 **("冪運算",即乘方;例如,24 = 16,所以 2 ** 416)。

運算子 +- 除了它們在 "二元"(兩個運算元)意義上的 "加法" 和 "減法" 之外,還有在 "一元"(一個運算元)意義上的 "正" 和 "負"。一元 + 基本上沒有任何效果;一元 - 反轉其運算元的符號。例如,-(3*4) 計算結果為 -12-(-(3*4)) 計算結果為 12

引用變數

[編輯 | 編輯原始碼]

在算術表示式中,可以直接引用 shell 變數,無需使用變數擴充套件(即無需使用美元符號 $)。例如,這個

i=2+3
echo $(( 7 * i ))

列印 35。(請注意,首先計算 i,產生 5,然後它乘以 7。如果我們寫的是 $i 而不是 i,則只會執行簡單的字串替換;7 * 2+3 等於 14 + 3,即 17——可能不是我們想要的。)

之前使用 "expr" 展示的示例

i=`expr 2 + 3`
echo `expr 7 \* $i`

列印 35

賦值給變數

[編輯 | 編輯原始碼]

Shell 變數也可以在算術表示式中賦值。此表示法類似於常規變數賦值,但 *要*靈活得多。例如,前面的示例可以像這樣改寫

echo $(( 7 * (i = 2 + 3) ))

除了設定 $i5 而不是 2+3。請注意,雖然算術擴充套件看起來有點像命令替換,但它 *不* 在子 shell 中執行;此命令實際上將 $i 設定為 5,以後的命令可以使用新值。(算術表示式內的括號只是括號的正常數學用法,用於控制運算順序。)

除了簡單的賦值運算子 = 之外,Bash 還支援複合運算子,例如 +=-=*=/=%=,它們執行一個運算,然後進行賦值。例如,(( i *= 2 + 3 )) 等效於 (( i = i * (2 + 3) ))。在每種情況下,整個表示式的計算結果都是變數的新值;例如,如果 $i4,則 (( j = i *= 3 ))$i$j 都設定為 12

最後,Bash 支援增量和減量運算子。增量運算子 ++ 將變數的值增加 1;如果它 *位於* 變數名稱 *之前*(作為 "前增量" 運算子),則表示式的計算結果為變數的 *新* 值,如果它 *位於* 變數名稱 *之後*(作為 "後增量" 運算子),則表示式的計算結果為變數的 *舊* 值。例如,如果 $i4,則 (( j = ++i ))$i$j 都設定為 5,而 (( j = i++ ))$i 設定為 5,將 $j 設定為 4。減量運算子 -- 完全相同,只是它將變數的值減少 1。前減量和後減量與前增量和後增量完全類似。

算術表示式作為它們自己的命令

[編輯 | 編輯原始碼]

一個命令可以完全由一個算術表示式組成,使用以下語法之一

(( i = 2 + 3 ))
let 'i = 2 + 3'

這兩個命令中的任何一個都會將 $i 設定為 5。兩種命令風格的命令都會返回一個退出狀態為零("成功" 或 "真")的值,如果表示式計算結果為非零值,則返回退出狀態為 1("失敗" 或 "假"),如果表示式計算結果為零,則返回退出狀態為 1。例如,這個

(( 0 )) || echo zero
(( 1 )) && echo non-zero

將列印這個

zero
non-zero

這種反直覺行為的原因是,在 C 中,零表示 "假",非零值(尤其是 1)表示 "真"。Bash 在算術表示式中保持該遺留值,然後在最後將其轉換為通常的 Bash 約定。

逗號運算子

[編輯 | 編輯原始碼]

算術表示式可以包含用逗號 , 分隔的多個子表示式。最後一個子表示式的結果成為整個表示式的總體值。例如,這個

echo $(( i = 2 , j = 2 + i , i * j ))

$i 設定為 2,將 $j 設定為 4,並列印 8

let 內建命令實際上直接支援多個表示式,而無需逗號;因此,以下三個命令是等效的

(( i = 2 , j = 2 + i , i * j ))
let 'i = 2 , j = 2 + i , i * j'
let 'i = 2' 'j = 2 + i' 'i * j'

比較、布林值和條件運算子

[編輯 | 編輯原始碼]

算術表示式支援整數比較運算子 <><=(表示 ≤)、>=(表示 ≥)、==(表示 =)和 !=(表示 ≠)。每個表示式對 "真" 計算結果為 1,對 "假" 計算結果為 0

它們還支援布林運算子 &&(“與”),如果其任一運算元為零,則計算結果為 0,否則為 1||(“或”),如果其任一運算元非零,則計算結果為 1,否則為 0;以及 !(“非”),如果其運算元為零,則計算結果為 1,否則為 0。除了使用零表示“假”和非零值表示“真”之外,這些運算子與我們在算術表示式之外看到的 &&||! 運算子完全相同。與這些運算子一樣,這些運算子也是“短路”運算子,如果其第一個引數足以確定結果,則不會計算其第二個引數。例如,(( ( i = 0 ) && ( j = 2 ) )) 不會計算 ( j = 2 ) 部分,因此不會將 $j 設定為 2,因為 && 的左側運算元為零(“假”)。

它們還支援條件運算子 b ? e1 : e2。如果 b 非零,則此運算子計算 e1 並返回其結果;否則,它計算 e2 並返回其結果。

這些運算子可以以複雜的方式組合

(( i = ( ( a > b && c < d + e || f == g + h ) ? j : k ) ))

算術 for 迴圈

[編輯 | 編輯原始碼]

上面,我們看到了一個 for 迴圈的樣式,它看起來像這樣

# print all integers 1 through 20:
for i in {1..20} ; do
  echo $i
done

Bash 還支援另一種樣式,它模仿 C 和相關語言中的 for 迴圈,使用 shell 算術

# print all integers 1 through 20:
for (( i = 1 ; i <= 20 ; ++i )) ; do
  echo $i
done

此 for 迴圈使用三個獨立的算術表示式,用分號 ; 分隔(而不是逗號 , - 這些是完全獨立的表示式,而不僅僅是子表示式)。第一個是初始化表示式,在迴圈開始之前執行。第二個是測試表達式;它在每次潛在的迴圈迭代之前(包括第一次)進行計算,如果它計算結果為零(“假”),則迴圈退出。第三個是計數表示式;它在每次迴圈迭代結束時進行計算。換句話說,這個 for 迴圈與這個 while 迴圈完全等效

# print all integers 1 through 20:
(( i = 1 ))
while (( i <= 20 )) ; do
  echo $i
  (( ++i ))
done

但是,一旦你習慣了語法,它就能更清楚地說明正在發生的事情。

按位運算子

[編輯 | 編輯原始碼]

除了常規的算術和布林運算子之外,Bash 還提供“按位”運算子,這意味著對整數進行操作的運算子,而不是作為整數,而是作為位字串。如果你還不熟悉這個概念,可以安全地忽略它們。

就像在 C 中一樣,按位運算子是 &(按位“與”)、|(按位“或”)、^(按位“異或”)、~(按位“非”)、<<(按位左移)和 >>(按位右移),以及 &=|=^=(它們包含賦值,就像 += 一樣)。

整數字面量

[編輯 | 編輯原始碼]

整數常量表示為整數字面量。我們已經看到了很多這樣的字面量;例如,34 是一個整數字面量,表示數字 34。我們所有的示例都是十進位制(十進位制)整數字面量,這是預設值;但實際上,字面量可以用 2-64 範圍內的任何進製表示,使用表示法 base#value(其中進位制本身用十進位制表示)。例如,這個

echo $(( 12 ))        # use the default of base ten (decimal)
echo $(( 10#12 ))     # explicitly specify base ten (decimal)
echo $(( 2#1100 ))    # base two (binary)
echo $(( 8#14 ))      # base eight (octal)
echo $(( 16#C ))      # base sixteen (hexadecimal)
echo $(( 8 + 2#100 )) # eight in base ten (decimal), plus four in base two (binary)

將列印 12 六次。(請注意,此表示法僅影響整數字面量的解釋方式。無論如何,算術擴充套件的結果仍然用十進位制表示。)

對於 11 到 36 進位制,英文字母 A 到 Z 用於表示 10 到 35 的數字值。它不區分大小寫。但是,對於 37 到 64 進位制,使用小寫英文字母表示 10 到 35 的數字值,使用大寫字母表示 36 到 61 的數字值,使用符號 @ 表示 62 的數字值,使用下劃線 _ 表示 63 的數字值。例如,64#@A3 表示 256259 (62 × 642 + 36 × 64 + 3).

還有兩種特殊表示法:在字面量前加上 0 表示八進位制(八進位制),在字面量前加上 0x0X 表示十六進位制(十六進位制)。例如,030 等效於 8#300x6F 等效於 16#6F

整數變數

[編輯 | 編輯原始碼]

一個變數可以宣告為整數變數 - 也就是說,它的“整數屬性”可以“設定” - 使用這種語法

declare -i n

執行上述命令後,對 n 的任何後續賦值將自動導致右側被解釋為算術表示式。例如,這個

declare -i n
n='2 + 3 > 4'

或多或少等效於這個

n=$((2 + 3 > 4))

除了第一個版本的 declare -i n 將繼續影響以後的賦值。

在第一個版本中,請注意賦值右側的引號。如果我們寫了 n=2 + 3 > 4,它將意味著“使用引數 3 執行命令 +,將環境變數 n 設定為 2 傳入,並將標準輸出重定向到檔案 4 中”;也就是說,設定變數的整數屬性不會影響賦值語句的整體解析,而只是控制最終分配給變數的值的解釋。

我們可以使用相反的命令“取消設定”變數的整數屬性,關閉此行為

declare +i n

declare 內建命令還有許多其他用途:變數可以具有一些其他屬性,declare 除了開啟和關閉屬性之外,還具有其他一些功能。此外,它的幾個屬性值得注意

  • localexport 一樣,引數可以是變數賦值;例如,declare -i n=2+3 設定 $n 的整數屬性並將其設定為 5
  • localexport 一樣,可以一次指定多個變數(和/或賦值);例如,declare -i m n 設定 $m 的整數屬性和 $n 的整數屬性。
  • 當在函式內部使用時,declare 隱式地將變數本地化(除非變數已經是本地的),這也具有在本地取消設定它的效果(除非使用賦值語法)。

非整數算術

[編輯 | 編輯原始碼]

如上所述,Bash shell 算術只支援整數算術。但是,外部程式通常可以用來為非整數值獲得類似的功能。特別是,常見的 Unix 實用程式 bc 通常用於此目的。以下命令

echo "$(echo '3.4 + 2.2' | bc)"

列印 5.6。不用說,由於 bc 與 Bash 的整合不像 shell 算術那樣緊密,因此它不像 shell 算術那樣方便;例如,像這樣的東西

# print the powers of two, from 1 to 512:
for (( i = 1 ; i < 1000 ; i *= 2 )) ; do
  echo $i
done

為了支援非整數,將變成類似這樣的東西

# print the powers of one-half, from 1 to 1/512:
i=1
while [ $( echo "$i > 0.001" | bc ) = 1 ] ; do
  echo $i
  i=$( echo "scale = scale($i) + 1 ; $i / 2" | bc )
done

部分原因是我們不能再使用算術 for 迴圈;部分原因是因為引用變數和為變數賦值現在更難了(因為 bc 不瞭解 shell 的變數,只瞭解它自己無關的變數);部分原因是 bc 只通過輸入和輸出與 shell 通訊。

  1. http://www.sal.ksu.edu/faculty/tim/unix_sg/bash/math.html
華夏公益教科書