在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

字符串和數(shù)字

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

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

參數(shù)展開

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

基本參數(shù)

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

例如:

$a

當(dāng) $a 展開后,會(huì)變成變量 a 所包含的值。簡(jiǎn)單參數(shù)也可能用花括號(hào)引起來(lái):

${a}

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

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

如果我們執(zhí)行這個(gè)序列,沒(méi)有任何輸出結(jié)果,因?yàn)?shell 會(huì)試著展開一個(gè)稱為 a_file 的變量,而不是 a。通過(guò) 添加花括號(hào)可以解決這個(gè)問(wèn)題:

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

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

${11}

管理空變量的展開

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

${parameter:-word}

若 parameter 沒(méi)有設(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 沒(méi)有設(shè)置或?yàn)榭?,展開結(jié)果是 word 的值。另外,word 的值會(huì)賦值給 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 沒(méi)有設(shè)置或?yàn)榭?,這種展開導(dǎo)致腳本帶有錯(cuò)誤退出,并且 word 的內(nèi)容會(huì)發(fā)送到標(biāo)準(zhǔn)錯(cuò)誤。若 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 沒(méi)有設(shè)置或?yàn)榭?,展開結(jié)果為空。若 parameter 不為空, 展開結(jié)果是 word 的值會(huì)替換掉 parameter 的值;然而,parameter 的值不會(huì)改變。

[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 具有返回變量名的能力。這會(huì)用在一些相當(dāng)獨(dú)特的情況下。

${!prefix*}

${!prefix@}

這種展開會(huì)返回以 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 所包含的字符串的長(zhǎng)度。通常,parameter 是一個(gè)字符串;然而,如果 parameter 是 @ 或者是 * 的話, 則展開結(jié)果是位置參數(shù)的個(gè)數(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}

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

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

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

如果 parameter 是 @,展開結(jié)果是 length 個(gè)位置參數(shù),從第 offset 個(gè)位置參數(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}

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

這種形式的展開對(duì) parameter 的內(nèi)容執(zhí)行查找和替換操作。如果找到了匹配通配符 pattern 的文本, 則用 string 的內(nèi)容替換它。在正常形式下,只有第一個(gè)匹配項(xiàng)會(huì)被替換掉。在該 // 形式下,所有的匹配項(xiàng)都會(huì)被替換掉。 該 /# 要求匹配項(xiàng)出現(xiàn)在字符串的開頭,而 /% 要求匹配項(xiàng)出現(xiàn)在字符串的末尾。/string 可能會(huì)省略掉,這樣會(huì) 導(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ù)展開是件很好的事情。字符串操作展開可以用來(lái)替換其它常見命令比方說(shuō) sed 和 cut。 通過(guò)減少使用外部程序,展開提高了腳本的效率。舉例說(shuō)明,我們將修改在之前章節(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 命令來(lái)比較這兩個(gè)腳本版本的效率:

[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

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

大小寫轉(zhuǎn)換

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

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

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

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

#!/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 命令來(lái)創(chuàng)建兩個(gè)變量,upper 和 lower。我們把第一個(gè)命令行參數(shù)的值(位置參數(shù)1)賦給 每一個(gè)變量,然后把變量值在屏幕上顯示出來(lái):

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

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

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

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

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

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

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

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

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

算術(shù)求值和展開

我們?cè)诘谄哒轮幸呀?jīng)接觸過(guò)算術(shù)展開了。它被用來(lái)對(duì)整數(shù)執(zhí)行各種算術(shù)運(yùn)算。它的基本格式是:

$((expression))

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

這個(gè)與復(fù)合命令 (( )) 有關(guān),此命令用做算術(shù)求值(真測(cè)試),我們?cè)诘?7章中遇到過(guò)。

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

數(shù)基

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

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

一些例子:

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

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

一元運(yùn)算符

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

簡(jiǎn)單算術(shù)

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

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

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

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

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

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

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

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

在循環(huán)中計(jì)算余數(shù)是很有用處的。在循環(huán)執(zhí)行期間,它允許某一個(gè)操作在指定的間隔內(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é)果看起來(lái)像這樣:

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

賦值運(yùn)算符

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

[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 賦了一個(gè)空值,然后驗(yàn)證 foo 的確為空。下一步,我們執(zhí)行一個(gè) if 復(fù)合命令 (( foo = 5 ))。 這個(gè)過(guò)程完成兩件有意思的事情:1)它把5賦值給變量 foo,2)它計(jì)算測(cè)試條件為真,因?yàn)?foo 的值非零。


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


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

表35-4: 賦值運(yùn)算符
表示法 描述
parameter = value 簡(jiǎn)單賦值。給 parameter 賦值。
parameter += value 加。等價(jià)于 parameter = parameter + value。
parameter -= value 減。等價(jià)于 parameter = parameter – value。
parameter *= value 乘。等價(jià)于 parameter = parameter * value。
parameter /= value 整除。等價(jià)于 parameter = parameter / value。
parameter %= value 取模。等價(jià)于 parameter = parameter % value。
parameter++ 后綴自增變量。等價(jià)于 parameter = parameter + 1 (但,要看下面的討論)。
parameter-- 后綴自減變量。等價(jià)于 parameter = parameter - 1。
++parameter 前綴自增變量。等價(jià)于 parameter = parameter + 1。
--parameter 前綴自減變量。等價(jià)于 parameter = parameter - 1。

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

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

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

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

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

對(duì)于大多數(shù) shell 應(yīng)用來(lái)說(shuō),前綴運(yùn)算符最有用。

自增 ++ 和 自減 -- 運(yùn)算符經(jīng)常和循環(huán)操作結(jié)合使用。我們將改進(jìn)我們的 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"

位運(yùn)算符

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

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

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

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

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

邏輯運(yùn)算符

正如我們?cè)诘?7章中所看到的,復(fù)合命令 (( )) 支持各種各樣的比較運(yùn)算符。還有一些可以用來(lái)計(jì)算邏輯運(yùn)算。 這里是比較運(yùn)算符的完整列表:

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

當(dāng)表達(dá)式用于邏輯運(yùn)算時(shí),表達(dá)式遵循算術(shù)邏輯規(guī)則;也就是,表達(dá)式的計(jì)算結(jié)果是零,則認(rèn)為假,而非零表達(dá)式認(rèn)為真。 該 (( )) 復(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

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

[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

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

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

當(dāng)企圖這樣做時(shí),bash 會(huì)聲明一個(gè)錯(cuò)誤:

[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")

通過(guò)把賦值表達(dá)式用括號(hào)括起來(lái),可以解決這個(gè)錯(cuò)誤:

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

下一步,我們看一個(gè)使用算術(shù)運(yùn)算符更完備的例子,該示例產(chǎn)生一個(gè)簡(jiǎ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

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

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

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

讓我們從一個(gè)簡(jiǎn)單的例子開始。我們將編寫一個(gè) bc 腳本來(lái)執(zhí)行2加2運(yùn)算:

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

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

使用 bc

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

[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

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

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

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

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

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

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

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

一個(gè)腳本實(shí)例

作為一個(gè)真實(shí)世界的例子,我們將構(gòu)建一個(gè)腳本,用于計(jì)算每月的還貸金額。在下面的腳本中, 我們使用了 here 文檔把一個(gè)腳本傳遞給 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個(gè)月(15年),這個(gè)例子計(jì)算出每月需要還貸的金額。 注意這個(gè)答案的精確度。這是由腳本中變量 scale 的值決定的。bc 的手冊(cè)頁(yè)提供了對(duì) bc 腳本語(yǔ)言的詳盡描述。 雖然 bc 的數(shù)學(xué)符號(hào)與 shell 的略有差異(bc 與 C 更相近),但是基于目前我們所學(xué)的內(nèi)容, 大多數(shù)符號(hào)是我們相當(dāng)熟悉的。

總結(jié)

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

額外加分

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

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

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

  • 輸出格式美化

拓展閱讀

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