1、執行數學運算
另一個對任何編程語言都很重要的特性是操作數字的能力。遺憾的是,對shell腳本來說,這個處理過程會比較麻煩。在shell腳本中有兩種途徑來進行數學運算。
1.1 expr 命令
最開始,Bourne shell提供了一個特別的命令用來處理數學表達式。expr命令允許在命令行上處理數學表達式,但是特別笨拙。
1 $ expr 1 + 5
2 6
注意:1 + 5,+ 號左右兩邊均有空格,如兩邊均無空格,shell會認為這是一個字符串!!!
expr命令能夠識別少數的數學和字符串操作符,見下表。
操 作 符 | 描 述 |
ARG1 | ARG2 | 如果ARG1既不是null也不是零值,返回ARG1;否則返回ARG2 |
ARG1 & ARG2 | 如果沒有參數是null或零值,返回ARG1;否則返回0 |
ARG1 < ARG2 | 如果ARG1小於ARG2,返回1;否則返回0 |
ARG1 <= ARG2 | 如果ARG1小於或等於ARG2,返回1;否則返回0 |
ARG1 = ARG2 | 如果ARG1等於ARG2,返回1;否則返回0 |
ARG1 != ARG2 | 如果ARG1不等於ARG2,返回1;否則返回0 |
ARG1 >= ARG2 | 如果ARG1大於或等於ARG2,返回1;否則返回0 |
ARG1 > ARG2 | 如果ARG1大於ARG2,返回1;否則返回0 |
ARG1 + ARG2 | 返回ARG1和ARG2的算術運算和 |
ARG1 - ARG2 | 返回ARG1和ARG2的算術運算差 |
ARG1 * ARG2 | 返回ARG1和ARG2的算術乘積 |
ARG1 / ARG2 | 返回ARG1被ARG2除的算術商 |
ARG1 % ARG2 | 返回ARG1被ARG2除的算術余數 |
STRING : REGEXP | 如果REGEXP匹配到了STRING中的某個模式,返回該模式匹配 |
match STRING REGEXP | 如果REGEXP匹配到了STRING中的某個模式,返回該模式匹配 |
substr STRING POS LENGTH | 返回起始位置為POS(從1開始計數)、長度為LENGTH個字符的子字符串 |
index STRING CHARS | 返回在STRING中找到CHARS字符串的位置;否則,返回0 |
length STRING | 返回字符串STRING的數值長度 |
+ TOKEN | 將TOKEN解釋成字符串,即使是個關鍵字 |
(EXPRESSION) | 返回EXPRESSION的值 |
盡管標准操作符在expr命令中工作得很好,但在腳本或命令行上使用它們時仍有問題出現。許多expr命令操作符在shell中另有含義(比如星號)。當它們出現在在expr命令中時,會得到一些詭異的結果。
1 $ expr 5 * 2
2 expr: syntax error 3 $
要解決這個問題,對於那些容易被shell錯誤解釋的字符,在它們傳入expr命令之前,需要使用shell的轉義字符(反斜線)將其標出來。
1 $ expr 5 \* 2
2 10
3 $
現在,麻煩才剛剛開始!在shell腳本中使用expr命令也同樣復雜:
1 $ cat expr.sh
2 #!/bin/bash 3 #An example of using expr command 4 var1=20
5 var2=10
6 var3=$(expr ${var1} / ${var2}) 7 echo the result is ${var3} 8 $
要將一個數學算式的結果賦給一個變量,需要使用命令替換來獲取expr命令的輸出:
1 $ chmod a+x expr.sh
2 $ /expr.sh
3 the result is 2
4 $
幸好bash shell有一個針對處理數學運算符的改進,將會在后面介紹中看到。
1.2 使用方括號
bash shell為了保持跟Bourne shell的兼容而包含了expr命令,但它同樣也提供了一種更簡單的方法來執行數學表達式。在bash中,在將一個數學運算結果賦給某個變量時,可以用美元符和方括號($[ operation ])將數學表達式圍起來。
1 $ var1=$[1 + 5] 2 $ echo $var1 3 6
4 $ var2=$[10 / 2] 5 $ echo $var2 6 5
7 $
用方括號執行shell數學運算比用expr命令方便很多。這種技術也適用於shell腳本。
1 $ chmod a+x expr1.sh
2 $ cat expr1.sh
3 #!/bin/bash 4
5 var1=50
6 var2=40
7 var3=35
8
9 var4=$[$var1 * ($var2 - $var3)] 10 echo the result is $var4 11 $ ./expr1.sh
12 the result is 250
13 $
同樣,注意在使用方括號來計算公式時,不用擔心shell會誤解乘號或其他符號。shell知道它不是通配符,因為它在方括號內。
在bash shell腳本中進行算術運算會有一個主要的限制。請看下例:
1 $ cat expr2.sh
2 #!/bin/bash 3
4 var1=20
5 var2=3
6
7 var3=$[$var1 / $var2] 8 echo the final result is $var3 9 $ chmod a+x expr2.sh
10 $ ./expr2.sh
11 the final result is 6
12 $
bash shell數學運算符只支持整數運算。若要進行任何實際的數學計算,這是一個巨大的限制。
說明 z shell(zsh)提供了完整的浮點數算術操作。如果需要在shell腳本中進行浮點數運算,可以考慮看看z shell(將在后面博客中討論)。
2、浮點解決方案
有幾種解決方案能夠克服bash中數學運算的整數限制。最常見的方案是用內建的bash計算器,叫作bc。
2.1 bc的基本用法
bash計算器實際上是一種編程語言,它允許在命令行中輸入浮點表達式,然后解釋並計算該表達式,最后返回結果。bash計算器能夠識別:
- 數字(整數和浮點數)
- 變量(簡單變量和數組)
- 注釋(以#或C語言中的/* */開始的行)
- 表達式
- 編程語句(例如if-then語句)
- 函數
- 可以在shell提示符下通過bc命令訪問bash計算器:
1 $ bc 2 bc 1.07.1
3 Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc. 4 This is free software with ABSOLUTELY NO WARRANTY. 5 For details type `warranty'.
6 3.3 * 5
7 16.5
8 3.14 * (3 + 4) 9 21.98
10 quit 11 $
這個例子一開始輸入了表達式3.3 * 5。bash計算器返回了計算結果。隨后每個輸入到計算器的表達式都會被求值並顯示出結果。要退出bash計算器,你必須輸入quit。
浮點運算是由內建變量scale控制的。必須將這個值設置為你希望在計算結果中保留的小數位數,否則無法得到期望的結果。
1 $ bc -q 2 4 / 5
3 0
4 scale=3
5 4 / 5
6 .800
7 quit 8 $
-q, --quiet Do not print the normal GNU bc welcome.
scale變量的默認值是0。在scale值被設置前,bash計算器的計算結果不包含小數位。在將其值設置成4后,bash計算器顯示的結果包含四位小數
除了普通數字,bash計算器還能支持變量。
1 $ bc -q 2 var1=10
3 var1 * 4
4 40
5 var2 = var1 / 5
6 print var2 7 2
8 quit 9 $
變量一旦被定義,你就可以在整個bash計算器會話中使用該變量了。print語句允許你打印變量和數字。
2.2 在腳本中使用bc
現在你可能想問bash計算器是如何在shell腳本中幫助處理浮點運算的。還記得命令替換嗎?是的,可以用命令替換運行bc命令,並將輸出賦給一個變量。基本格式如下:
variable=$(echo "options; expression" | bc)
第一部分options允許你設置變量。如果你需要不止一個變量,可以用分號將其分開。expression參數定義了通過bc執行的數學表達式。
1 $ cat bc1.sh
2 #!/bin/bash 3 var1=$(echo "scale=4; 10 / 4" | bc) 4 echo the answer is $var1 5 $
這個例子將scale變量設置成了四位小數,並在expression部分指定了特定的運算。運行這個腳本會產生如下輸出。
1 $ ./bc1.sh
2 the answer is 2.5000
3 $
太好了!現在你不會再只能用數字作為表達式值了。也可以用shell腳本中定義好的變量。
1 $ cat bc2.sh
2 #!/bin/bash 3
4 var1=100
5 var2=45
6
7 var3=$(echo "scale=4; ${var1} / ${var2}" | bc) 8 echo the answer is ${var3} 9 $
腳本定義了兩個變量,它們都可以用在expression部分,然后發送給bc命令。別忘了用美元符表示的是變量的值而不是變量自身。這個腳本的輸出如下。
1 $ chmod a+x bc2.sh
2 $ ./bc2.sh
3 the answer is 2.2222
4 $
當然,一旦變量被賦值,那個變量也可以用於其他運算。
1 $ cat bc3.sh
2 #!/bin/bash 3
4 var1=10
5 var2=3.1415926
6 var3=$(echo "scale=4; ${var1} * ${var2}" | bc) 7 var4=$(echo "scale=4; ${var3} * ${var2}" | bc) 8
9 echo the result is ${var4} 10 $ chmod a+x bc3.sh
11 $ ./bc3.sh
12 the result is 98.6960406
13 $
這個方法適用於較短的運算,但有時你會涉及更多的數字。如果需要進行大量運算,在一個命令行中列出多個表達式就會有點麻煩。
有一個方法可以解決這個問題。bc命令能識別輸入重定向,允許你將一個文件重定向到bc命令來處理。但這同樣會叫人頭疼,因為你還得將表達式存放到文件中。
最好的辦法是使用內聯輸入重定向,它允許你直接在命令行中重定向數據。在shell腳本中,你可以將輸出賦給一個變量。
1 variable=$(bc << EOF 2 options 3 statements 4 expressions 5 EOF 6 )
EOF文本字符串標識了內聯重定向數據的起止。記住,仍然需要命令替換符號將bc命令的輸出賦給變量。
現在可以將所有bash計算器涉及的部分都放到同一個腳本文件的不同行。下面是在腳本中使用這種技術的例子。
1 $ cat bc4.sh
2 #!/bin/bash 3
4 var1=11.11
5 var2=22.22
6 var3=33.33
7 var4=44.44
8
9 var5=$(bc << EOF 10 scale=5
11 a1=(${var1} * ${var2}) 12 b1=(${var3} * ${var4}) 13 a1+b1 14 EOF 15 ) 16
17 echo the answer is ${var5} 18 $ chmod a+x bc4.sh
19 $ ./bc4.sh
20 the answer is 1728.0494
21 $
將選項和表達式放在腳本的不同行中可以讓處理過程變得更清晰,提高易讀性。EOF字符串標識了重定向給bc命令的數據的起止。當然,必須用命令替換符號標識出用來給變量賦值的命令。
你還會注意到,在這個例子中,你可以在bash計算器中賦值給變量。這一點很重要:在bash計算器中創建的變量只在bash計算器中有效,不能在shell腳本中使用。
3、退出腳本
迄今為止所有的示例腳本中,我們都是突然停下來的。運行完最后一條命令時,腳本就結束了。其實還有另外一種更優雅的方法可以為腳本划上一個句號。
shell中運行的每個命令都使用退出狀態碼(exit status)告訴shell它已經運行完畢。退出狀態碼是一個0~255的整數值,在命令結束運行時由命令傳給shell。可以捕獲這個值並在腳本中使用。
3.1、查看退出狀態碼
Linux提供了一個專門的變量$?來保存上個已執行命令的退出狀態碼。對於需要進行檢查的命令,必須在其運行完畢后立刻查看或使用$?變量。它的值會變成由shell所執行的最后一條命令的退出狀態碼。
1 $ date
2 Sat Jan 15 10:01:30 EDT 2020
3 $ echo $?
4 0
5 $
按照慣例,一個成功結束的命令的退出狀態碼是0。如果一個命令結束時有錯誤,退出狀態碼就是一個正數值。
1 $ asdfg 2 -bash: asdfg: command not found 3 $ echo $?
4 127
5 $
1 $ asdfg 2 -bash: asdfg: command not found 3 $ echo $?
4 127
5 $
無效命令會返回一個退出狀態碼127。Linux錯誤退出狀態碼沒有什么標准可循,但有一些可用的參考,如下表所示。
退出狀態碼126表明用戶沒有執行命令的正確權限。
1 $ ./myprog.c 2 -bash: ./myprog.c: Permission denied 3 $ echo $?
4 126
5 $
另一個會碰到的常見錯誤是給某個命令提供了無效參數。
1 $ date %t 2 date: invalid date '%t'
3 $ echo $?
4 1
5 $
這會產生一般性的退出狀態碼1,表明在命令中發生了未知錯誤。
3.2、exit 命令
默認情況下,shell腳本會以腳本中的最后一個命令的退出狀態碼退出。
1 $ ./test6 2 The result is 2
3 $ echo $?
4 0
5 $
你可以改變這種默認行為,返回自己的退出狀態碼。exit命令允許你在腳本結束時指定一個退出狀態碼。
1 $ cat test13 2 #!/bin/bash 3 # testing the exit status 4 var1=10
5 var2=30
6 var3=$[$var1 + $var2] 7 echo The answer is $var3 8 exit 5
9 $
當查看腳本的退出碼時,你會得到作為參數傳給exit命令的值。
1 $ chmod u+x test13 2 $ ./test13 3 The answer is 40
4 $ echo $?
5 5
6 $
也可以在exit命令的參數中使用變量。
1 $ cat test14 2 #!/bin/bash 3 # testing the exit status 4 var1=10
5 var2=30
6 var3=$[$var1 + $var2] 7 exit $var3 8 $
當你運行這個命令時,它會產生如下退出狀態。
1 $ chmod u+x test14 2 $ ./test14 3 $ echo $?
4 40
5 $
你要注意這個功能,因為退出狀態碼最大只能是255。看下面例子中會怎樣。
1 $ cat test14b 2 #!/bin/bash 3 # testing the exit status 4 var1=10
5 var2=30
6 var3=$[$var1 * $var2] 7 echo The value is $var3 8 exit $var3 9 $
現在運行它的話,會得到如下輸出。
1 $ ./test14b 2 The value is 300
3 $ echo $?
4 44
5 $
退出狀態碼被縮減到了0~255的區間。shell通過模運算得到這個結果。一個值的模就是被除=后的余數。最終的結果是指定的數值除以256后得到的余數。在這個例子中,指定的值是300(返=回值),余數是44,因此這個余數就成了最后的狀態退出碼。
在后面中,你會了解到如何用if-then語句來檢查某個命令返回的錯誤狀態,以便知道命=令是否成功。