鍍金池/ 教程/ Linux/ 字符串和數(shù)字
網(wǎng)絡(luò)系統(tǒng)
打印
重定向
使用命令
位置參數(shù)
權(quán)限
文本處理
疑難排解
layout: book-zh title: 自定制 shell 提示符
查找文件
layout: book-zh title: vi 簡介
shell 環(huán)境
什么是 shell
編譯程序
鍵盤高級操作技巧
流程控制:case 分支
流程控制:if 分支結(jié)構(gòu)
layout: book-zh title: 軟件包管理
進程
存儲媒介
格式化輸出
編寫第一個 Shell 腳本
啟動一個項目
流程控制:while/until 循環(huán)
文件系統(tǒng)中跳轉(zhuǎn)
字符串和數(shù)字
讀取鍵盤輸入
歸檔和備份
探究操作系統(tǒng)
流程控制:for 循環(huán)
自頂向下設(shè)計
數(shù)組
操作文件和目錄
奇珍異寶
從 shell 眼中看世界
正則表達式

字符串和數(shù)字

所有的計算機程序都是用來和數(shù)據(jù)打交道的。在過去的章節(jié)中,我們專注于處理文件級別的數(shù)據(jù)。 然而,許多程序問題需要使用更小的數(shù)據(jù)單位來解決,比方說字符串和數(shù)字。

在這一章中,我們將查看幾個用來操作字符串和數(shù)字的 shell 功能。shell 提供了各種執(zhí)行字符串操作的參數(shù)展開功能。 除了算術(shù)展開(在第七章中接觸過),還有一個常見的命令行程序叫做 bc,能執(zhí)行更高級別的數(shù)學(xué)運算。

參數(shù)展開

盡管參數(shù)展開在第七章中出現(xiàn)過,但我們并沒有詳盡地介紹它,因為大多數(shù)的參數(shù)展開會用在腳本中,而不是命令行中。 我們已經(jīng)使用了一些形式的參數(shù)展開;例如,shell 變量。shell 提供了更多方式。

基本參數(shù)

最簡單的參數(shù)展開形式反映在平常使用的變量上。

例如:

$a

當(dāng) $a 展開后,會變成變量 a 所包含的值。簡單參數(shù)也可能用花括號引起來:

${a}

雖然這對展開沒有影響,但若該變量 a 與其它的文本相鄰,可能會把 shell 搞糊涂了。在這個例子中,我們試圖 創(chuàng)建一個文件名,通過把字符串 “_file” 附加到變量 a 的值的后面。

[me@linuxbox ~]$ a="foo"
[me@linuxbox ~]$ echo "$a_file"

如果我們執(zhí)行這個序列,沒有任何輸出結(jié)果,因為 shell 會試著展開一個稱為 a_file 的變量,而不是 a。通過 添加花括號可以解決這個問題:

[me@linuxbox ~]$ echo "${a}_file"
foo_file

我們已經(jīng)知道通過把數(shù)字包裹在花括號中,可以訪問大于9的位置參數(shù)。例如,訪問第十一個位置參數(shù),我們可以這樣做:

${11}

管理空變量的展開

幾種用來處理不存在和空變量的參數(shù)展開形式。這些展開形式對于解決丟失的位置參數(shù)和給參數(shù)指定默認值的情況很方便。

${parameter:-word}

若 parameter 沒有設(shè)置(例如,不存在)或者為空,展開結(jié)果是 word 的值。若 parameter 不為空,則展開結(jié)果是 parameter 的值。

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:-"substitute value if unset"}
if unset
substitute value
[me@linuxbox ~]$ echo $foo
[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:-"substitute value if unset"}
bar
[me@linuxbox ~]$ echo $foo
bar

${parameter:=word}

若 parameter 沒有設(shè)置或為空,展開結(jié)果是 word 的值。另外,word 的值會賦值給 parameter。 若 parameter 不為空,展開結(jié)果是 parameter 的值。

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:="default value if unset"}
default value if unset
[me@linuxbox ~]$ echo $foo
default value if unset
[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:="default value if unset"}
bar
[me@linuxbox ~]$ echo $foo
bar

注意: 位置參數(shù)或其它的特殊參數(shù)不能以這種方式賦值。


${parameter:?word}

若 parameter 沒有設(shè)置或為空,這種展開導(dǎo)致腳本帶有錯誤退出,并且 word 的內(nèi)容會發(fā)送到標(biāo)準(zhǔn)錯誤。若 parameter 不為空, 展開結(jié)果是 parameter 的值。

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:?"parameter is empty"}
bash: foo: parameter is empty
[me@linuxbox ~]$ echo $?
1
[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:?"parameter is empty"}
bar
[me@linuxbox ~]$ echo $?
0

${parameter:+word}

若 parameter 沒有設(shè)置或為空,展開結(jié)果為空。若 parameter 不為空, 展開結(jié)果是 word 的值會替換掉 parameter 的值;然而,parameter 的值不會改變。

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:+"substitute value if set"}

[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:+"substitute value if set"}
substitute value if set

返回變量名的參數(shù)展開

shell 具有返回變量名的能力。這會用在一些相當(dāng)獨特的情況下。

${!prefix*}

${!prefix@}

這種展開會返回以 prefix 開頭的已有變量名。根據(jù) bash 文檔,這兩種展開形式的執(zhí)行結(jié)果相同。 這里,我們列出了所有以 BASH 開頭的環(huán)境變量名:

[me@linuxbox ~]$ echo ${!BASH*}
BASH BASH_ARGC BASH_ARGV BASH_COMMAND BASH_COMPLETION
BASH_COMPLETION_DIR BASH_LINENO BASH_SOURCE BASH_SUBSHELL
BASH_VERSINFO BASH_VERSION

字符串展開

有大量的展開形式可用于操作字符串。其中許多展開形式尤其適用于路徑名的展開。

${#parameter}

展開成由 parameter 所包含的字符串的長度。通常,parameter 是一個字符串;然而,如果 parameter 是 @ 或者是 * 的話, 則展開結(jié)果是位置參數(shù)的個數(shù)。

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo "'$foo' is ${#foo} characters long."
'This string is long.' is 20 characters long.

${parameter:offset}

${parameter:offset:length}

這些展開用來從 parameter 所包含的字符串中提取一部分字符。提取的字符始于 第 offset 個字符(從字符串開頭算起)直到字符串的末尾,除非指定提取的長度。

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo:5}
string is long.
[me@linuxbox ~]$ echo ${foo:5:6}
string

若 offset 的值為負數(shù),則認為 offset 值是從字符串的末尾開始算起,而不是從開頭。注意負數(shù)前面必須有一個空格, 為防止與 ${parameter:-word} 展開形式混淆。length,若出現(xiàn),則必須不能小于零。

如果 parameter 是 @,展開結(jié)果是 length 個位置參數(shù),從第 offset 個位置參數(shù)開始。

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo: -5}
long.
[me@linuxbox ~]$ echo ${foo: -5:2}
lo

${parameter#pattern}

${parameter##pattern}

這些展開會從 paramter 所包含的字符串中清除開頭一部分文本,這些字符要匹配定義的 patten。pattern 是 通配符模式,就如那些用在路徑名展開中的模式。這兩種形式的差異之處是該 # 形式清除最短的匹配結(jié)果, 而該 ## 模式清除最長的匹配結(jié)果。

[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo#*.}
txt.zip
[me@linuxbox ~]$ echo ${foo##*.}
zip

${parameter%pattern}

${parameter%%pattern}

這些展開和上面的 # 和 ## 展開一樣,除了它們清除的文本從 parameter 所包含字符串的末尾開始,而不是開頭。

[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo%.*}
file.txt
[me@linuxbox ~]$ echo ${foo%%.*}
file

${parameter/pattern/string}

${parameter//pattern/string}

${parameter/#pattern/string}

${parameter/%pattern/string}

這種形式的展開對 parameter 的內(nèi)容執(zhí)行查找和替換操作。如果找到了匹配通配符 pattern 的文本, 則用 string 的內(nèi)容替換它。在正常形式下,只有第一個匹配項會被替換掉。在該 // 形式下,所有的匹配項都會被替換掉。 該 /# 要求匹配項出現(xiàn)在字符串的開頭,而 /% 要求匹配項出現(xiàn)在字符串的末尾。/string 可能會省略掉,這樣會 導(dǎo)致刪除匹配的文本。

[me@linuxbox~]$ foo=JPG.JPG
[me@linuxbox ~]$ echo ${foo/JPG/jpg}
jpg.JPG
[me@linuxbox~]$ echo ${foo//JPG/jpg}
jpg.jpg
[me@linuxbox~]$ echo ${foo/#JPG/jpg}
jpg.JPG
[me@linuxbox~]$ echo ${foo/%JPG/jpg}
JPG.jpg

知道參數(shù)展開是件很好的事情。字符串操作展開可以用來替換其它常見命令比方說 sed 和 cut。 通過減少使用外部程序,展開提高了腳本的效率。舉例說明,我們將修改在之前章節(jié)中討論的 longest-word 程序, 用參數(shù)展開 ${#j} 取代命令 $(echo $j | wc -c) 及其 subshell ,像這樣:

#!/bin/bash
# longest-word3 : find longest string in a file
for i; do
    if [[ -r $i ]]; then
        max_word=
        max_len=
        for j in $(strings $i); do
            len=${#j}
            if (( len > max_len )); then
                max_len=$len
                max_word=$j
            fi
        done
        echo "$i: '$max_word' ($max_len characters)"
    fi
    shift
done

下一步,我們將使用 time 命令來比較這兩個腳本版本的效率:

[me@linuxbox ~]$ time longest-word2 dirlist-usr-bin.txt
dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38
characters)
real 0m3.618s
user 0m1.544s
sys 0m1.768s
[me@linuxbox ~]$ time longest-word3 dirlist-usr-bin.txt
dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38
characters)
real 0m0.060s
user 0m0.056s
sys 0m0.008s

原來的腳本掃描整個文本文件需耗時3.168秒,而該新版本,使用參數(shù)展開,僅僅花費了0.06秒 —— 一個非常巨大的提高。

大小寫轉(zhuǎn)換

最新的 bash 版本已經(jīng)支持字符串的大小寫轉(zhuǎn)換了。bash 有四個參數(shù)展開和 declare 命令的兩個選項來支持大小寫轉(zhuǎn)換。

那么大小寫轉(zhuǎn)換對什么有好處呢? 除了明顯的審美價值,它在編程領(lǐng)域還有一個重要的角色。 讓我們考慮一個數(shù)據(jù)庫查詢的案例。假設(shè)一個用戶已經(jīng)敲寫了一個字符串到數(shù)據(jù)輸入框中, 而我們想要在一個數(shù)據(jù)庫中查找這個字符串。該用戶輸入的字符串有可能全是大寫字母或全是小寫或是兩者的結(jié)合。 我們當(dāng)然不希望把每個可能的大小寫拼寫排列填充到我們的數(shù)據(jù)庫中。那怎么辦?

解決這個問題的常見方法是規(guī)范化用戶輸入。也就是,在我們試圖查詢數(shù)據(jù)庫之前,把用戶的輸入轉(zhuǎn)換成標(biāo)準(zhǔn)化。 我們能做到這一點,通過把用戶輸入的字符全部轉(zhuǎn)換成小寫字母或大寫字母,并且確保數(shù)據(jù)庫中的條目 按同樣的方式規(guī)范化。

這個 declare 命令可以用來把字符串規(guī)范成大寫或小寫字符。使用 declare 命令,我們能強制一個 變量總是包含所需的格式,無論如何賦值給它。

#!/bin/bash
# ul-declare: demonstrate case conversion via declare
declare -u upper
declare -l lower
if [[ $1 ]]; then
    upper="$1"
    lower="$1"
    echo $upper
    echo $lower
fi

在上面的腳本中,我們使用 declare 命令來創(chuàng)建兩個變量,upper 和 lower。我們把第一個命令行參數(shù)的值(位置參數(shù)1)賦給 每一個變量,然后把變量值在屏幕上顯示出來:

[me@linuxbox ~]$ ul-declare aBc
ABC
abc

正如我們所看到的,命令行參數(shù)(“aBc”)已經(jīng)規(guī)范化了。

有四個參數(shù)展開,可以執(zhí)行大小寫轉(zhuǎn)換操作:

表 35-1: 大小寫轉(zhuǎn)換參數(shù)展開
格式 結(jié)果
${parameter,,} 把 parameter 的值全部展開成小寫字母。
${parameter,} 僅僅把 parameter 的第一個字符展開成小寫字母。
${parameter^^} 把 parameter 的值全部轉(zhuǎn)換成大寫字母。
${parameter^} 僅僅把 parameter 的第一個字符轉(zhuǎn)換成大寫字母(首字母大寫)。

這里是一個腳本,演示了這些展開格式:

#!/bin/bash
# ul-param - demonstrate case conversion via parameter expansion
if [[ $1 ]]; then
    echo ${1,,}
    echo ${1,}
    echo ${1^^}
    echo ${1^}
fi

這里是腳本運行后的結(jié)果:

[me@linuxbox ~]$ ul-param aBc
abc
aBc
ABC
ABc

再次,我們處理了第一個命令行參數(shù),輸出了由參數(shù)展開支持的四種變體。盡管這個腳本使用了第一個位置參數(shù), 但參數(shù)可以是任意字符串,變量,或字符串表達式。

算術(shù)求值和展開

我們在第七章中已經(jīng)接觸過算術(shù)展開了。它被用來對整數(shù)執(zhí)行各種算術(shù)運算。它的基本格式是:

$((expression))

這里的 expression 是一個有效的算術(shù)表達式。

這個與復(fù)合命令 (( )) 有關(guān),此命令用做算術(shù)求值(真測試),我們在第27章中遇到過。

在之前的章節(jié)中,我們看到過一些類型的表達式和運算符。這里,我們將看到一個更完整的列表。

數(shù)基

回到第9章,我們看過八進制(以8為底)和十六進制(以16為底)的數(shù)字。在算術(shù)表達式中,shell 支持任意進制的整形常量。

表 35-2: 指定不同的數(shù)基
表示法 描述
number 默認情況下,沒有任何表示法的數(shù)字被看做是十進制數(shù)(以10為底)。
0number 在算術(shù)表達式中,以零開頭的數(shù)字被認為是八進制數(shù)。
0xnumber 十六進制表示法
base#number number 以 base 為底

一些例子:

[me@linuxbox ~]$ echo $((0xff))
255
[me@linuxbox ~]$ echo $((2#11111111))
255

在上面的示例中,我們打印出十六進制數(shù) ff(最大的兩位數(shù))的值和最大的八位二進制數(shù)(以2為底)。

一元運算符

有兩個二元運算符,+ 和 -,它們被分別用來表示一個數(shù)字是正數(shù)還是負數(shù)。例如,-5。

簡單算術(shù)

下表中列出了普通算術(shù)運算符:

表 35-3: 算術(shù)運算符
運算符 描述
+
-
*
/ 整除
** 乘方
% 取模(余數(shù))

其中大部分運算符是不言自明的,但是整除和取模運算符需要進一步解釋一下。

因為 shell 算術(shù)只操作整形,所以除法運算的結(jié)果總是整數(shù):

[me@linuxbox ~]$ echo $(( 5 / 2 ))
2

這使得確定除法運算的余數(shù)更為重要:

[me@linuxbox ~]$ echo $(( 5 % 2 ))
1

通過使用除法和取模運算符,我們能夠確定5除以2得數(shù)是2,余數(shù)是1。

在循環(huán)中計算余數(shù)是很有用處的。在循環(huán)執(zhí)行期間,它允許某一個操作在指定的間隔內(nèi)執(zhí)行。在下面的例子中, 我們顯示一行數(shù)字,并高亮顯示5的倍數(shù):

#!/bin/bash
# modulo : demonstrate the modulo operator
for ((i = 0; i <= 20; i = i + 1)); do
    remainder=$((i % 5))
    if (( remainder == 0 )); then
        printf "<%d> " $i
    else
        printf "%d " $i
    fi
done
printf "\n"

當(dāng)腳本執(zhí)行后,輸出結(jié)果看起來像這樣:

[me@linuxbox ~]$ modulo
<0> 1 2 3 4 <5> 6 7 8 9 <10> 11 12 13 14 <15> 16 17 18 19 <20>

賦值運算符

盡管它的使用不是那么明顯,算術(shù)表達式可能執(zhí)行賦值運算。雖然在不同的上下文中,我們已經(jīng)執(zhí)行了許多次賦值運算。 每次我們給變量一個值,我們就執(zhí)行了一次賦值運算。我們也能在算術(shù)表達式中執(zhí)行賦值運算:

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo $foo
[me@linuxbox ~]$ if (( foo = 5 ));then echo "It is true."; fi
It is true.
[me@linuxbox ~]$ echo $foo
5

在上面的例子中,首先我們給變量 foo 賦了一個空值,然后驗證 foo 的確為空。下一步,我們執(zhí)行一個 if 復(fù)合命令 (( foo = 5 ))。 這個過程完成兩件有意思的事情:1)它把5賦值給變量 foo,2)它計算測試條件為真,因為 foo 的值非零。


注意: 記住上面表達式中 = 符號的真正含義非常重要。單個 = 運算符執(zhí)行賦值運算。foo = 5 是說“使得 foo 等于5”, 而 == 運算符計算等價性。foo == 5 是說“是否 foo 等于5?”。這會讓人感到非常迷惑,因為 test 命令接受單個 = 運算符 來測試字符串等價性。這也是使用更現(xiàn)代的 [[ ]] 和 (( )) 復(fù)合命令來代替 test 命令的另一個原因。


除了 = 運算符,shell 也提供了其它一些表示法,來執(zhí)行一些非常有用的賦值運算:

表35-4: 賦值運算符
表示法 描述
parameter = value 簡單賦值。給 parameter 賦值。
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++ 后綴自增變量。等價于 parameter = parameter + 1 (但,要看下面的討論)。
parameter-- 后綴自減變量。等價于 parameter = parameter - 1。
++parameter 前綴自增變量。等價于 parameter = parameter + 1。
--parameter 前綴自減變量。等價于 parameter = parameter - 1。

這些賦值運算符為許多常見算術(shù)任務(wù)提供了快捷方式。特別關(guān)注一下自增(++)和自減(--)運算符,它們會把它們的參數(shù)值加1或減1。 這種風(fēng)格的表示法取自C 編程語言并且被其它幾種編程語言吸收,包括 bash。

自增和自減運算符可能會出現(xiàn)在參數(shù)的前面或者后面。然而它們都是把參數(shù)值加1或減1,這兩個位置有個微小的差異。 若運算符放置在參數(shù)的前面,參數(shù)值會在參數(shù)返回之前增加(或減少)。若放置在后面,則運算會在參數(shù)返回之后執(zhí)行。 這相當(dāng)奇怪,但這是它預(yù)期的行為。這里是個演示的例子:

[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((foo++))
1
[me@linuxbox ~]$ echo $foo
2

如果我們把1賦值給變量 foo,然后通過把自增運算符 ++ 放到參數(shù)名 foo 之后來增加它,foo 返回1。 然而,如果我們第二次查看變量 foo 的值,我們看到它的值增加了1。若我們把 ++ 運算符放到參數(shù) foo 之前, 我們得到更期望的行為:

[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((++foo))
2
[me@linuxbox ~]$ echo $foo
2

對于大多數(shù) shell 應(yīng)用來說,前綴運算符最有用。

自增 ++ 和 自減 -- 運算符經(jīng)常和循環(huán)操作結(jié)合使用。我們將改進我們的 modulo 腳本,讓代碼更緊湊些:

#!/bin/bash
# modulo2 : demonstrate the modulo operator
for ((i = 0; i <= 20; ++i )); do
    if (((i % 5) == 0 )); then
        printf "<%d> " $i
    else
        printf "%d " $i
    fi
done
printf "\n"

位運算符

位運算符是一類以不尋常的方式操作數(shù)字的運算符。這些運算符工作在位級別的數(shù)字。它們被用在某類底層的任務(wù)中, 經(jīng)常涉及到設(shè)置或讀取位標(biāo)志。

表35-5: 位運算符
運算符 描述
~ 按位取反。對一個數(shù)字所有位取反。
位左移. 把一個數(shù)字的所有位向左移動。
>> 位右移. 把一個數(shù)字的所有位向右移動。
& 位與。對兩個數(shù)字的所有位執(zhí)行一個 AND 操作。
| 位或。對兩個數(shù)字的所有位執(zhí)行一個 OR 操作。
^ 位異或。對兩個數(shù)字的所有位執(zhí)行一個異或操作。

注意除了按位取反運算符之外,其它所有位運算符都有相對應(yīng)的賦值運算符(例如,<\<=)。

這里我們將演示產(chǎn)生2的冪列表的操作,使用位左移運算符:

[me@linuxbox ~]$ for ((i=0;i<8;++i)); do echo $((1<<i)); done
1
2
4
8
16
32
64
128

邏輯運算符

正如我們在第27章中所看到的,復(fù)合命令 (( )) 支持各種各樣的比較運算符。還有一些可以用來計算邏輯運算。 這里是比較運算符的完整列表:

表35-6: 比較運算符
運算符 描述
小于或相等
>= 大于或相等
小于
> 大于
== 相等
!= 不相等
&& 邏輯與
|| 邏輯或
expr1?expr2:expr3 條件(三元)運算符。若表達式 expr1 的計算結(jié)果為非零值(算術(shù)真),則 執(zhí)行表達式 expr2,否則執(zhí)行表達式 expr3。

當(dāng)表達式用于邏輯運算時,表達式遵循算術(shù)邏輯規(guī)則;也就是,表達式的計算結(jié)果是零,則認為假,而非零表達式認為真。 該 (( )) 復(fù)合命令把結(jié)果映射成 shell 正常的退出碼:

[me@linuxbox ~]$ if ((1)); then echo "true"; else echo "false"; fi
true
[me@linuxbox ~]$ if ((0)); then echo "true"; else echo "false"; fi
false

最陌生的邏輯運算符就是這個三元運算符了。這個運算符(仿照于 C 編程語言里的三元運算符)執(zhí)行一個單獨的邏輯測試。 它用起來類似于 if/then/else 語句。它操作三個算術(shù)表達式(字符串不會起作用),并且若第一個表達式為真(或非零), 則執(zhí)行第二個表達式。否則,執(zhí)行第三個表達式。我們可以在命令行中實驗一下:

[me@linuxbox~]$ a=0
[me@linuxbox~]$ ((a<1?++a:--a))
[me@linuxbox~]$ echo $a
1
[me@linuxbox~]$ ((a<1?++a:--a))
[me@linuxbox~]$ echo $a
0

這里我們看到一個實際使用的三元運算符。這個例子實現(xiàn)了一個切換。每次運算符執(zhí)行的時候,變量 a 的值從零變?yōu)?,或反之亦然。

請注意在表達式內(nèi)執(zhí)行賦值卻并非易事。

當(dāng)企圖這樣做時,bash 會聲明一個錯誤:

[me@linuxbox ~]$ a=0
[me@linuxbox ~]$ ((a<1?a+=1:a-=1))
bash: ((: a<1?a+=1:a-=1: attempted assignment to non-variable (error token is "-=1")

通過把賦值表達式用括號括起來,可以解決這個錯誤:

[me@linuxbox ~]$ ((a<1?(a+=1):(a-=1)))

下一步,我們看一個使用算術(shù)運算符更完備的例子,該示例產(chǎn)生一個簡單的數(shù)字表格:

#!/bin/bash
# arith-loop: script to demonstrate arithmetic operators
finished=0
a=0
printf "a\ta**2\ta**3\n"
printf "=\t====\t====\n"
until ((finished)); do
    b=$((a**2))
    c=$((a**3))
    printf "%d\t%d\t%d\n" $a $b $c
    ((a<10?++a:(finished=1)))
done

在這個腳本中,我們基于變量 finished 的值實現(xiàn)了一個 until 循環(huán)。首先,把變量 finished 的值設(shè)為零(算術(shù)假), 繼續(xù)執(zhí)行循環(huán)之道它的值變?yōu)榉橇?。在循環(huán)體內(nèi),我們計算計數(shù)器 a 的平方和立方。在循環(huán)末尾,計算計數(shù)器變量 a 的值。 若它小于10(最大迭代次數(shù)),則 a 的值加1,否則給變量 finished 賦值為1,使得變量 finished 算術(shù)為真, 從而終止循環(huán)。運行該腳本得到這樣的結(jié)果:

[me@linuxbox ~]$ arith-loop
a    a**2     a**3
=    ====     ====
0    0        0
1    1        1
2    4        8
3    9        27
4    16       64
5    25       125
6    36       216
7    49       343
8    64       512
9    81       729
10   100      1000

bc - 一種高精度計算器語言

我們已經(jīng)看到 shell 是可以處理所有類型的整形算術(shù)的,但是如果我們需要執(zhí)行更高級的數(shù)學(xué)運算或僅使用浮點數(shù),該怎么辦? 答案是,我們不能這樣做。至少不能直接用 shell 完成此類運算。為此,我們需要使用外部程序。 有幾種途徑可供我們采用。嵌入的 Perl 或者 AWK 程序是一種可能的方案,但是不幸的是,超出了本書的內(nèi)容大綱。 另一種方式就是使用一種專業(yè)的計算器程序。這樣一個程序叫做 bc,在大多數(shù) Linux 系統(tǒng)中都可以找到。

該 bc 程序讀取一個用它自己的類似于 C 語言的語法編寫的腳本文件。一個 bc 腳本可能是一個分離的文件或者是讀取 標(biāo)準(zhǔn)輸入。bc 語言支持相當(dāng)少的功能,包括變量,循環(huán)和程序員定義的函數(shù)。這里我們不會討論整個 bc 語言, 僅僅體驗一下。查看 bc 的手冊頁,其文檔整理非常好。

讓我們從一個簡單的例子開始。我們將編寫一個 bc 腳本來執(zhí)行2加2運算:

/* A very simple bc script */
2 + 2

腳本的第一行是一行注釋。bc 使用和 C 編程語言一樣的注釋語法。注釋,可能會跨越多行,開始于 /* 結(jié)束于 */。

使用 bc

如果我們把上面的 bc 腳本保存為 foo.bc,然后我們就能這樣運行它:

[me@linuxbox ~]$ bc foo.bc
bc 1.06.94
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software
Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
4

如果我們仔細觀察,我們看到算術(shù)結(jié)果在最底部,版權(quán)信息之后??梢酝ㄟ^ -q(quiet)選項禁止這些版權(quán)信息。 bc 也能夠交互使用:

[me@linuxbox ~]$ bc -q
2 + 2
4
quit

當(dāng)使用 bc 交互模式時,我們簡單地輸入我們希望執(zhí)行的運算,結(jié)果就立即顯示出來。bc 的 quit 命令結(jié)束交互會話。

也可能通過標(biāo)準(zhǔn)輸入把一個腳本傳遞給 bc 程序:

[me@linuxbox ~]$ bc < foo.bc
4

這種接受標(biāo)準(zhǔn)輸入的能力,意味著我們可以使用 here 文檔,here字符串,和管道來傳遞腳本。這里是一個使用 here 字符串的例子:

[me@linuxbox ~]$ bc <<< "2+2"
4

一個腳本實例

作為一個真實世界的例子,我們將構(gòu)建一個腳本,用于計算每月的還貸金額。在下面的腳本中, 我們使用了 here 文檔把一個腳本傳遞給 bc:

#!/bin/bash
# loan-calc : script to calculate monthly loan payments
PROGNAME=$(basename $0)
usage () {
    cat <<- EOF
    Usage: $PROGNAME PRINCIPAL INTEREST MONTHS
    Where:
    PRINCIPAL is the amount of the loan.
    INTEREST is the APR as a number (7% = 0.07).
    MONTHS is the length of the loan's term.
    EOF
}
if (($# != 3)); then
    usage
    exit 1
fi
principal=$1
interest=$2
months=$3
bc <<- EOF
    scale = 10
    i = $interest / 12
    p = $principal
    n = $months
    a = p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))
    print a, "\n"
EOF

當(dāng)腳本執(zhí)行后,輸出結(jié)果像這樣:

[me@linuxbox ~]$ loan-calc 135000 0.0775 180
475
1270.7222490000

若貸款 135,000 美金,年利率為 7.75%,借貸180個月(15年),這個例子計算出每月需要還貸的金額。 注意這個答案的精確度。這是由腳本中變量 scale 的值決定的。bc 的手冊頁提供了對 bc 腳本語言的詳盡描述。 雖然 bc 的數(shù)學(xué)符號與 shell 的略有差異(bc 與 C 更相近),但是基于目前我們所學(xué)的內(nèi)容, 大多數(shù)符號是我們相當(dāng)熟悉的。

總結(jié)

在這一章中,我們學(xué)習(xí)了很多小東西,在腳本中這些小零碎可以完成“真正的工作”。隨著我們編寫腳本經(jīng)驗的增加, 能夠有效地操作字符串和數(shù)字的能力將具有極為重要的價值。我們的 loan-calc 腳本表明, 甚至可以創(chuàng)建簡單的腳本來完成一些真正有用的事情。

額外加分

雖然該 loan-calc 腳本的基本功能已經(jīng)很到位了,但腳本還遠遠不夠完善。為了額外加分,試著 給腳本 loan-calc 添加以下功能:

  • 完整的命令行參數(shù)驗證

  • 用一個命令行選項來實現(xiàn)“交互”模式,提示用戶輸入本金、利率和貸款期限

  • 輸出格式美化

拓展閱讀

上一篇:權(quán)限下一篇:存儲媒介