上一篇:Shell 之變量
一骗污、前言
Shell 腳本語(yǔ)言是一門(mén)弱類(lèi)型語(yǔ)言何暮。實(shí)際上买猖,它并沒(méi)有數(shù)據(jù)類(lèi)型的概念询筏,無(wú)論你輸入的是字符串還是數(shù)字膨蛮,都是按照字符串類(lèi)型來(lái)存儲(chǔ)的叠纹。
至于是什么類(lèi)型,Shell 會(huì)根據(jù)上下文去確定具體類(lèi)型敞葛。
舉個(gè)例子:
$ sum=1+2
$ echo $sum
1+2
?? 以上示例誉察,Shell 認(rèn)為 1+2
是字符串,而不是算術(shù)運(yùn)算之后將結(jié)果再賦值給變量 sum
惹谐。
如果你要進(jìn)行算術(shù)運(yùn)算持偏,可以用 let
命令或 expr
命令。
$ let sum=1+2
$ echo $sum
3
?? 根據(jù) let
命令氨肌,Shell 確定了你想要的是算術(shù)運(yùn)算鸿秆,因此就能得到 3
。
如果非要?jiǎng)澐值脑捲跚簦梢杂校骸缸址骨溥础ⅰ覆紶栔怠埂ⅰ刚麛?shù)」和「數(shù)組」桩了。
二附帽、字符串
在 Shell 中,最常見(jiàn)的就是字符串類(lèi)型了井誉。注意幾點(diǎn):
- 當(dāng)字符串不包含「空白符」蕉扮,引號(hào)是可選的。若原意就是表示一個(gè)字符串颗圣,而非整數(shù)或數(shù)組時(shí)喳钟,建議使用引號(hào)屁使。
- 由單引號(hào)包裹的字符,都會(huì)原樣輸出奔则。且單引號(hào)包裹的內(nèi)容不允許再出現(xiàn)單引號(hào)蛮寂,轉(zhuǎn)義也不行。
- 由雙引號(hào)包裹的字符易茬,一些特殊字符(主要有
$
酬蹋、`
、\
)會(huì)進(jìn)行擴(kuò)展或轉(zhuǎn)義抽莱。- 若要在雙引號(hào)內(nèi)輸出
$
范抓、`
、\
食铐、"
字符匕垫,使用反斜杠\
進(jìn)行轉(zhuǎn)義即可。
關(guān)于引號(hào)的用法虐呻,推薦看下 ?? Google Shell Style Guide - quoting象泵。
舉個(gè)例子:
# ?
str=Frankie
str='Frankie' # 推薦
str="Frankie"
str="Frankie's" # 推薦
str="Frankie's MacBook Pro" # 推薦
str='Frankie"s MacBook Pro' # 推薦
?? 以上示例語(yǔ)法上是允許的,?? 以下則是錯(cuò)誤示例斟叼。
# ?
str='Frankie's MacBook Pro'
2.1 獲取字符串長(zhǎng)度
語(yǔ)法為 ${#變量名}
偶惠,且 {}
是必須的。
$ str='Frankie'
$ echo ${#str}
7
2.2 截取子串
語(yǔ)法為 ${變量名:起始位置:截取長(zhǎng)度}
犁柜,注意起始位置從 0
開(kāi)始計(jì)算洲鸠。
- 若省略截取長(zhǎng)度,表示截取從起始位置開(kāi)始到結(jié)尾的子串馋缅。
- 起始位置可以是負(fù)數(shù)扒腕,但負(fù)數(shù)前面必須要要有一個(gè)空格,以免與設(shè)置變量默認(rèn)值
${foo:-hello}
的語(yǔ)法混淆萤悴。- 截取長(zhǎng)度可以是負(fù)值瘾腰,表示要排除從字符末尾開(kāi)始的 N 個(gè)字符。
以上操作覆履,不會(huì)改變?cè)址E瑁?lèi)似 JavaScript 的 Array.prototype.substr()
方法。
比如 ${str:6:5}
硝全,在變量 str
中截取第 6
位(包含)開(kāi)始栖雾,長(zhǎng)度為 5
的子串。
$ str='Hello Shell!'
$ echo ${str:6:5}
Shell
?? 以上 ${str:6:5}
可以替換為 ${str: -6:-1}
伟众,表示截取變量 str
中倒數(shù)第 6
位(包含)開(kāi)始析藕,到倒數(shù)第 1
個(gè)之前的子串。
2.3 字符串搜索與替換
Shell 提供了多種搜索凳厢、替換的方法账胧。
具體看這一篇:Bash 字符串操作竞慢。請(qǐng)注意,替換方法只有貪婪匹配模式治泥。
2.4 大小寫(xiě)轉(zhuǎn)換
利用 tr
(transform)命令筹煮,可實(shí)現(xiàn)大小寫(xiě)轉(zhuǎn)換。
$ str='Frankie'
$ echo $str | tr 'a-z' 'A-Z'
FRANKIE
$ echo $str | tr 'A-Z' 'a-z'
frankie
三居夹、布爾值
定義布爾值跟字符串一樣 ??
truth=true
falsy=false
注意條件判斷即可败潦,舉個(gè)例子:
bool=false
if $bool; then
echo 'Done'
fi
?? 以上示例,只有變量 bool
的值為 false
吮播,才會(huì)進(jìn)入 then
語(yǔ)句輸出 Done
变屁。就算是 bool
未定義眼俊、或變量被刪除了意狠、或者 bool
的值為空字符剂公,都不會(huì)進(jìn)入 then
語(yǔ)句窘奏。
因此昭卓,布爾值正確的判斷方式寸宵,應(yīng)使用 test
命令粮呢,或使用 test
的簡(jiǎn)寫(xiě)語(yǔ)法 [ ]
或 [[ ]]
炸宵。比如:
bool=false
if [ $bool = true ]; then
echo 'Done'
fi
if [ $bool = false ]; then
echo 'Error'
fi
?? 以上判斷方式函卒,只有當(dāng)變量 bool
的值為 true
或 false
時(shí)戏溺,才會(huì)命中條件性昭。
四拦止、整數(shù)
4.1 算術(shù)運(yùn)算
在 Shell 有兩種語(yǔ)法可以進(jìn)行算術(shù)運(yùn)算。
(( ... ))
糜颠。$[ ... ]
- 此為舊語(yǔ)法汹族。
其中 (( ... ))
內(nèi)部的空白符會(huì)被忽略,因此 ((1+1))
和 (( 1 + 1 ))
是一樣的其兴。
(( ... ))
語(yǔ)法不返回值顶瞒,只要運(yùn)算結(jié)果不為0
,則表示命令執(zhí)行成功元旬,否則表示命令執(zhí)行失敗榴徐。
若要獲取運(yùn)算結(jié)果,需在前面加上 $
匀归,即 $(( ... ))
坑资,使其變成算術(shù)表達(dá)式,返回運(yùn)算結(jié)果穆端。
$ echo $((1 + 1))
2
(( ... ))
支持這些運(yùn)算操作:加減乘除袱贮、取余(%
)、指數(shù)(**
)徙赢、自增(++
)字柠、自減(--
)探越。
注意點(diǎn):
(( ... ))
內(nèi)部可使用圓括號(hào)()
來(lái)改變運(yùn)算順序,亦可嵌套窑业。(( ... ))
內(nèi)部的變量無(wú)需添加$
钦幔,因此里面的字符串會(huì)被認(rèn)為是變量。(( ... ))
內(nèi)部使用了不存在的變量常柄,不會(huì)報(bào)錯(cuò)鲤氢。在 Shell 中訪問(wèn)不存在的變量會(huì)返回空值,此時(shí)(( ... ))
會(huì)將空值當(dāng)作0
處理西潘。- 除法運(yùn)算的結(jié)果總是「整數(shù)」卷玉。比如
$((5 / 2))
結(jié)果為2
,而不是2.5
喷市。(( ... ))
和$[ ... ]
語(yǔ)法相种,都只能做「整數(shù)」的運(yùn)算,否則會(huì)報(bào)錯(cuò)品姓。(( ... ))
可以執(zhí)行賦值運(yùn)算寝并,比如$((a = 1))
會(huì)將變量a
賦值為1
。
4.2 expr 命令
expr
是一個(gè)表達(dá)式計(jì)算工具腹备。支持:
- 加法運(yùn)算:
+
- 減法運(yùn)算:
-
- 乘法運(yùn)算:
\*
- 除法運(yùn)算:
/
- 取模運(yùn)算:
%
注意衬潦,這里乘法運(yùn)算 \*
要加 \
轉(zhuǎn)義,否則 Shell 解析特殊符號(hào)植酥。還有镀岛,非整數(shù)參與運(yùn)算會(huì)報(bào)錯(cuò)哦!
$ sum=$(expr 1 + 2)
$ echo $sum
3
4.3 let 命令
let
命令用于將算術(shù)運(yùn)算的結(jié)果友驮,賦予一個(gè)變量漂羊。
$ let sum=1+2
$ echo $sum
3
?? 以上示例,使得變量 sum
等于 1+2
的運(yùn)算結(jié)果喊儡。注意拨与,sum=1+2
里面不能有空格。
4.4 小數(shù)運(yùn)算
以上 (( ... ))
和 expr
命令均不支持小數(shù)運(yùn)算艾猜,如果想進(jìn)行小數(shù)運(yùn)算买喧,可以借助 bc
計(jì)算器或者 awk
命令。
$ echo 'scale=4; 10/3' | bc
3.3333
?? 其中 scale=4
表示保留四位小數(shù)匆赃。
4.5 邏輯運(yùn)算
(( ... ))
也提供了邏輯運(yùn)算:
-
<
小于 -
>
大于 -
<=
小于或相等 -
>=
大于或相等 -
==
相等 -
!=
不相等 -
&&
邏輯與 -
||
邏輯或 -
!
邏輯否 -
expr1 ? expr2 : expr3
三元條件運(yùn)算符淤毛。若表達(dá)式expr1
的計(jì)算結(jié)果為非零值(算術(shù)真),則執(zhí)行表達(dá)式
expr2
算柳,否則執(zhí)行表達(dá)式expr3
低淡。
當(dāng)邏輯表達(dá)式為真,返回
1
,否則返回0
蔗蹋。
五何荚、數(shù)組
在 Shell 中,可以用數(shù)組來(lái)存放多個(gè)值猪杭,數(shù)組元素之間通過(guò)「空格」隔開(kāi)餐塘。只支持一維數(shù)組,不支持多維數(shù)組皂吮。
在讀取數(shù)組成員戒傻、遍歷數(shù)組等方面,bash蜂筹、zsh 之間會(huì)有一定的區(qū)別需纳。
5.1 數(shù)組起始索引
現(xiàn)代高級(jí)編程語(yǔ)言中,它們的數(shù)組起始索引多數(shù)都是 0
艺挪。
但在 Shell 編程語(yǔ)言中不翩,不同的 Shell 解析器其數(shù)組起始索引(下標(biāo))可能是不同的。比如 bash 的起始索引 0
闺属,zsh 的起始索引是 1
慌盯。
?? 摘自 StackExchange:
Virtually all shell arrays (
Bourne
,csh
,tcsh
,fish
,rc
,es
,yash
) start at1
.ksh
is the only exception that I know (bash
just copiedksh
).
這樣看,起始索引為 1
的 Shell 解析器占多數(shù)掂器。對(duì)于習(xí)慣了從 0
開(kāi)始的我來(lái)說(shuō),這一點(diǎn)是有的難以接受的俱箱。關(guān)于數(shù)組起始索引国瓮,有興趣的可看:CITATION NEEDED。
arr[0]=a
arr[1]=b
?? 以上示例狞谱,使用 bash 去解析是沒(méi)問(wèn)題的乃摹。但用 zsh 解析時(shí),就會(huì)報(bào)錯(cuò):assignment to invalid subscript range
跟衅。因?yàn)?zsh 的起始索引是 1
開(kāi)始的孵睬,所以索引 0
是一個(gè)不合法的下標(biāo)。
5.2 創(chuàng)建數(shù)組
可使用以下幾種方式來(lái)創(chuàng)建數(shù)組:
# 創(chuàng)建空數(shù)組
arr=()
# 創(chuàng)建數(shù)組伶跷,按順序賦值
arr=(val1 val2 ... valN)
# 創(chuàng)建數(shù)組掰读,逐項(xiàng)添加
arr[0]=val1
arr[1]=val2
arr[2]=val3
# 創(chuàng)建數(shù)組,不按順序賦值
arr=([2]=val3 [0]=val1 [1]=val2)
# 創(chuàng)建稀疏數(shù)組
arr=(val1 [2]=val3 val4)
注意幾點(diǎn):
- 沒(méi)有賦值的數(shù)組元素其默認(rèn)值是空字符串叭莫。
- 以上
[2]=val3
形式不允許有空格蹈集。 - 元素之間使用空格隔開(kāi)。
前面提到雇初,不同類(lèi)型的 Shell 的起始索引可能是不一樣的拢肆,因此以上采用 [0]
、[1]
、[2]
等方式設(shè)置指定項(xiàng)的值郭怪,其表示的第幾項(xiàng)元素可能是不相同的支示。
還可以這樣 ??
# 可使用通配符,將當(dāng)前目錄的所有 MP3 文件鄙才,放入一個(gè)數(shù)組
$ mp3s=(*.mp3)
# 用 declare 聲明一個(gè)關(guān)聯(lián)數(shù)組悼院,其索引除了支持整數(shù),也支持字符串咒循。
$ declare -a ARRAYNAME
5.3 讀取數(shù)組長(zhǎng)度
前面介紹過(guò)据途,讀取字符串長(zhǎng)度的語(yǔ)法為 ${#變量名}
,數(shù)組也是類(lèi)似的叙甸。
但要借助數(shù)組的特殊索引 @
和 *
颖医,將數(shù)組擴(kuò)展成列表,然后再次使用 #
獲取數(shù)組的長(zhǎng)度裆蒸。語(yǔ)法有以下兩種:
${#array[*]}
${#array[@]}
arr1=(a b c)
arr2=('aa 00' 'bb 11' 'cc 22')
echo ${#arr1[*]}
echo ${#arr1[@]}
echo ${#arr2[*]}
echo ${#arr2[@]}
?? 以上結(jié)果均輸出 3
熔萧。
如果是讀取數(shù)組某項(xiàng)的長(zhǎng)度,則使用 ${#數(shù)組變量名[下標(biāo)]}
的形式僚祷。比如:
arr[10]=foo
echo ${#arr[10]}
??以上輸出 3
佛致,它讀取的是索引為 10
的元素的值的長(zhǎng)度。
5.4 讀取數(shù)組單個(gè)成員
其語(yǔ)法為 ${數(shù)組變量[下標(biāo)]}
辙谜,比如:
$ arr=(a b c)
$ echo ${arr[1]}
?? 基于起始索引的問(wèn)題俺榆,${arr[1]}
輸出值可能是 a
,也可能是 b
装哆。
注意罐脊,里面的 {}
不能省略,否則 $arr[1]
在 bash 里輸出位 a[1]
蜕琴,相當(dāng)于 $arr
的值與字符串 [1]
連接萍桌,因此結(jié)果是 a[1]
。
注意以下語(yǔ)法:
$ arr=(a b c)
$ echo $arr
?? 在 zsh 中凌简,可以輸出數(shù)組所有項(xiàng)上炎,即 a b c
,在 bash 則是輸出數(shù)組第一項(xiàng)雏搂,即 a
藕施。
若想在各種 Shell 環(huán)境下統(tǒng)一,最合理的做法是什么呢畔派?使用類(lèi)似截取字符串子串的語(yǔ)法:
${array[@]:offset:length}
其中 array[@]
表示所有元素铅碍,offset
表示偏移量(總是從 0
開(kāi)始),length
表示截取長(zhǎng)度线椰。這種語(yǔ)法在不同 Shell 環(huán)境下總能獲得一致行為胞谈。因此 ${arr[@]:0:1}
總能正確地取到數(shù)組的第一項(xiàng)。
因此,需要兼容 bash 和 zsh 時(shí)烦绳,應(yīng)使用
${array[@]:offset:length}
語(yǔ)法而不是${array[subscript]}
語(yǔ)法卿捎。
5.5 讀取數(shù)組所有成員
利用數(shù)組的特殊索引 @
和 *
,它們返回?cái)?shù)組的所有成員径密。
$ arr=(a b c)
$ echo ${arr[@]}
$ echo ${arr[*]}
?? 以上 ${arr[@]}
和 ${arr[*]}
都輸出數(shù)組所有成員 a b c
午阵。因此,利用這兩個(gè)特殊索引享扔,可配合 for
循環(huán)來(lái)遍歷數(shù)組底桂。
for item in "${arr[@]}"; do
echo $item
done
5.6 ${arr[@]}
和 ${arr[*]}
細(xì)節(jié)區(qū)別
其差異,主要體現(xiàn)在 for
循環(huán)上惧眠。
示例一:
arr=('aa 00' 'bb 11' 'cc 22')
echo 'use @, with double quote:'
for item in "${arr[@]}"; do
echo "--> item: $item"
done
echo 'use @, without double quote:'
for item in ${arr[@]}; do
echo "--> item: $item"
done
?? 都是使用了 @
籽懦,區(qū)別在于 ${arr[@]}
外層是否使用了「雙引號(hào)」。bash 輸出如下:??
use @, with double quote:
--> item: aa 00
--> item: bb 11
--> item: cc 22
use @, without double quote:
--> item: aa
--> item: 00
--> item: bb
--> item: 11
--> item: cc
--> item: 22
這里用高級(jí)語(yǔ)言來(lái)類(lèi)比:首先氛魁,原數(shù)組是 ['aa 00', 'bb 11', 'cc 22']
暮顺。如果帶雙引號(hào) "${arr[@]}"
,遍歷的是原數(shù)組秀存。如果不帶雙引號(hào) ${arr[@]}
捶码,相當(dāng)于內(nèi)部隱式地做了一次「扁平化」操作,使其變成 ['aa', '00', 'bb', '11', 'cc', '22']
形式或链,最終遍歷的是扁平化后的產(chǎn)物惫恼。
因此,在遍歷數(shù)組時(shí)株扛,若使用
@
索引尤筐,應(yīng)該要使用雙引號(hào),以保持原有數(shù)組的結(jié)構(gòu)洞就。
示例二:
arr=('aa 00' 'bb 11' 'cc 22')
echo 'use *, with double quote:'
for item in "${arr[*]}"; do
echo "--> item: $item"
done
echo 'use *, without double quote:'
for item in ${arr[*]}; do
echo "--> item: $item"
done
?? 都是使用了 *
,區(qū)別在于 ${arr[*]}
外層是否使用了「雙引號(hào)」掀淘。bash 輸出如下:??
use *, with double quote:
--> item: aa 00 bb 11 cc 22
use *, without double quote:
--> item: aa
--> item: 00
--> item: bb
--> item: 11
--> item: cc
--> item: 22
從結(jié)果上看旬蟋, "${arr[*]}"
把數(shù)組所有項(xiàng)當(dāng)成了一個(gè)整體,遍歷時(shí)只有一項(xiàng)革娄。而不帶雙引號(hào)時(shí)倾贰, ${arr[*]}
與 ${arr[@]}
行為一致,都把原數(shù)組扁平化了拦惋。
對(duì)于類(lèi)似 arr=(a b c)
的數(shù)組(即數(shù)組每一項(xiàng)不包含空白符)匆浙, 循環(huán)中 "${arr[@]}"
、 ${arr[@]}
厕妖、${arr[*]}
行為都是一致的首尼,而 "${arr[*]}"
同樣是把原數(shù)組所有項(xiàng)當(dāng)做一個(gè)整體了。
提一下,上述示例輸出結(jié)果均在 bash 下執(zhí)行软能。但在 zsh 環(huán)境下迎捺,這三種 "${arr[@]}"
、 ${arr[@]}
查排、${arr[*]}
形式凳枝,都能「正確」遍歷原數(shù)組,不會(huì)扁平化跋核。
基于以上細(xì)微差異岖瑰,遍歷數(shù)組的最佳實(shí)踐是:應(yīng)使用
@
,且要帶上「雙引號(hào)」砂代。
5.7 截取數(shù)組
其實(shí)前面已經(jīng)提到過(guò)了蹋订,其語(yǔ)法就是:${array[@]:offset:length}
。比如:
$ fruits=(apple banana lemon pear plum orange watermelon)
$ echo "${fruits[@]:3:2}"
pear plum
其中 offset
為偏移量(總是從 0
開(kāi)始)泊藕,length
表示截取長(zhǎng)度辅辩。它不會(huì)改變?cè)瓟?shù)組,類(lèi)似于 JavaScript 的 Array.prototype.slice()
方法娃圆。${array[@]:offset:1}
也是獲取數(shù)組中某項(xiàng)最兼容的寫(xiě)法玫锋。
若 length
省略,截取從 offset
開(kāi)始到結(jié)尾的數(shù)組項(xiàng)讼呢。其中 offset
和 length
也支持負(fù)值撩鹿,類(lèi)似字符串截取,這里不再展開(kāi)悦屏。
5.8 追加數(shù)組成員
數(shù)組末尾追加成員节沦,可以使用 +=
賦值操作符,會(huì)自動(dòng)把值最佳到數(shù)字末尾础爬。
$ arr1=(a b c)
$ arr1+=(d e)
$ echo "${arr1[@]}"
a b c d e
注意 +=
前后不能有空格甫贯,若追加多項(xiàng)元素,則使用空格隔開(kāi)看蚜。
如果知道了數(shù)組下標(biāo)叫搁,也可以使用 arr[index]=xxx
形式添加。但注意若 index
位置已有元素供炎,則會(huì)產(chǎn)生覆蓋效果渴逻。
5.9 刪除數(shù)組成員
清空數(shù)組,應(yīng)使用 unset
語(yǔ)法音诫。比如:
$ arr=(a b c)
$ unset arr
$ echo "${arr[@]}"
對(duì)于 arr=''
這種形式惨奕,在 zsh 上可以起到清空數(shù)組的作用,而 bash 上是對(duì)數(shù)組第一項(xiàng)賦值為空字符串而已竭钝。
如是刪除某項(xiàng)梨撞,可以這樣:
$ arr=(a b c)
$ unset arr[1]
echo "${arr[@]}"
未完待續(xù)...