算術

算術 在 Bash

{other: "%{count} 個練習"}

關於 算術

Bash 可以進行算術運算,但_只能處理整數_。 如果你需要做浮點數運算,就必須呼叫外部程式(例如 awk 或 bc)。

算術運算子都列在手冊裡(依優先順序排列):Shell Arithmetic。

此外,Bash 只會在特定地方進行算術運算(也就是所謂的算術「情境」)。

算術展開

算術展開看起來像是 指令替換,只是改用雙重括號。

$(( expression ))

這個語法會展開成運算式求值後的結果。 它通常用來把結果存進變數:

x=$(( 3 + 4 ))
Note

括號內要留多少空白,由你自己決定。 這關乎風格與可讀性。

x=$(( 3 + 4 ))
y=$((5+6))

你親切的作者喜歡加上空白,讓算術運算式在括號內更顯眼。

算術條件式

算術條件式是在雙重括號內執行,而且_不需要_開頭的 $:

(( expression ))

這不會展開成運算結果。 不過它會產生一個_結束狀態_。

  • 如果運算式的結果是非零,結束狀態就是 0(成功)。
  • 如果運算式的結果是零,結束狀態就是 1(失敗)。
Note

在 (( 裡面,Bash 對 true/false 的認定和 C 語言一樣:零是 false,非零是 true。 這和 Bash 判斷結束狀態的方式正好相反!

這個語法通常用在兩個地方:

  1. 當作 if 敘述或 while 迴圈的指令部分。

    if (( x == y - 1 )); then ...
    while (( y > 0 )); do ...
    
  2. 當作獨立指令,用來修改變數的值。

    (( x = x + 1 ))
    echo "new value for x is $x"
    
    Caution

    把這種算術用法和 set -e 搭配使用時要小心。 如果運算式求值結果為零,結束狀態就會是非零,Bash 便會終止。

    x=0
    (( x++ ))
    echo "you will see this"
    
    set -e
    x=0
    (( x++ ))
    echo "shell exits on the previous line; you won't see this"
    

    想更深入了解 ++ 運算子,請看這篇關於 Postincrement vs Preincrement 的文章

其他具有算術情境的地方

還有其他幾個地方也屬於算術情境:

  • 算術 for 迴圈。

    for (( i = 0; i < 10; i++ )); do
        echo "$i"
    done
    
  • 數字索引陣列的索引部分。

    echo "The next element is ${array[x + 1]}"
    # ................................^^^^^
    

    想進一步了解陣列,請閱讀 Arrays 概念文件。

  • ${var:offset:length} 展開中的位移與長度部分。

  • let 內建指令。

    • 請注意,(( expression )) 比 let "expresson" 更受推薦。 和算術條件式建構不同,let 運算式會受到檔名展開與單字分割的影響,而且需要加上引號。
  • 變數指定,當變數具有「整數屬性」時。

    # variable x does not have the integer attribute
    x="3 + 4"
    declare -p x      # => declare -- x="3 + 4"
    
    # variable y has the integer attribute
    declare -i y
    y="4 + 5"
    declare -p y      # => declare -i y="9"
    
    Note

    作者認為 declare -i 的用途有限。 它主要的優點是允許「裸」算術(不需要算術運算式語法)。 這可能會讓閱讀你程式碼的人感到困惑,因為算術運算式會在意想不到的地方被求值。

在算術運算式中使用變數

算術情境有一個特性可以提升可讀性。

height=10
width=20
area=$(( height * width ))

你有注意到算術運算式裡的變數少了 $ 錢字號嗎? Bash 允許你這麼做。 這讓運算式容易閱讀得多。

手冊上寫著:

在運算式內,shell 變數也可以直接用名稱參照,不必使用參數展開語法。

這甚至對陣列元素也適用:

point1=(10 15)  # these are (x, y) coordinates
point2=(20 7)
distance_squared=$(( (point1[0] - point2[0])**2 + (point1[1] - point2[1])**2 ))

但其他參數運算式仍必須使用各自的特殊語法。 例如,把字串長度加倍:

s="hello world"
len=$(( ${#s} * 2 ))

位置參數也同樣必須如此。 如果前兩個位置參數代表寬與高:

area=$(( $1 * $2 ))
透過 GitHub 編輯 連結會在新視窗或分頁中開啟