shell 基本語法
jenkins 上構(gòu)建項目時,經(jīng)常需要借助 shell 腳本枝缔,最近也經(jīng)常跟服務(wù)器打交道布疙,順便記錄些常用命令,方便查閱
語法-變量
# 定義變量
name='dasu'
# 使用變量
echo $name # dasu
echo "I am ${name}." # I am dasu.
xxx='dasu'
用 key=value
形式定義變量愿卸,=
等號兩邊不能有空格
$xxx 或 ${xxx}
變量名前加個 $
使用變量灵临,大括號省略也可以
語法-字符串
# 字符串使用
name='dasu'
name2="dasu"
name3=dasu
echo "$name $name2 $name3" # dasu dasu dasu
# 字符串長度
echo ${#name} #4
# 注意,shell 里都是命令
'dasu' # dasu: command not found
# 獲取子字符串
echo ${name:0:2} # da
# 尋找字符
echo `expr index $name s` # 3 下標從1開始
'dasu'
"dasu"
dasu
單引號趴荸、雙引號俱诸、甚至不加引號都會被作為字符串使用
單引號里的字符串不做任何處理工作,是什么就原樣輸出
雙引號里如果有表達式赊舶、有轉(zhuǎn)義符睁搭,有變量园骆,會先進行處理,最后再輸出,所以字符串的拼接,可以放在雙引號內(nèi)
注意巡扇,shell 里都是命令,所以只有當(dāng)在命令參數(shù)、或表達式右值時绊寻,字符串才會被當(dāng)做字符串處理祠肥,否則都會被認為命令,從而報找不到 xxx 命令錯誤
${#xxx}
加個 #
號使用冤议,可以用來獲取 xxx 變量字符串的長度
${xxx:1:2}
用來截取子字符串
i=`expr index "$xxx" x`
用來查找子字符,expr 表示后面跟著的是表達式症革,因為 shell 默認每一行都是命令,所以本身不支持表達式
index 用來查找苞冯,后面跟著接收兩個參數(shù):原字符串坦仍,查找的字符
注意,只找字符老翘,不是找子字符串
`xxx`
和 $(xxx)
因為不加引號也可以被認為是字符串處理,所以在某些場景啦粹,需要讓腳本解釋器知道,這是一串命令忍饰,而不是字符串贪嫂,此時就需要通過 `
反引號,或者 $()
來實現(xiàn)
echo ls # ls艾蓝,被認為是字符串
echo `ls` # 執(zhí)行 ls 命令撩荣,并將結(jié)果輸出
echo $(ls) # 執(zhí)行 ls 命令,并將結(jié)果輸出
`
反引號內(nèi)部是一個命令饶深,$()
美元符合加小括號形式餐曹,括號內(nèi)也是表示一個命令
注意,`
或 $()
內(nèi)部的命令執(zhí)行之后的結(jié)果敌厘,會再次作為輸入台猴,被當(dāng)做下一行 shell 腳本命令執(zhí)行,所以需要注意這個結(jié)果是否可以作為命令被執(zhí)行
`whoami` # root: command not found
因為 whoami
命令執(zhí)行輸出 root俱两,root 又被作為命令執(zhí)行饱狂,就報錯了
如果有需求是要將命令執(zhí)行結(jié)果,作為日志輸出宪彩,這種場景就很適用了
語法-表達式
編程語言都可以通過各種運算符來實現(xiàn)一個個表達式休讳,如算術(shù)表達式、賦值表達式等
但由于在 shell 內(nèi)尿孔,都被當(dāng)做命令來處理俊柔,所以正常的運算符無法直接使用,需要借助其他命令或語法實現(xiàn)
expr
a=2 + 2 # +: command not found
a=2+2 # 2+2被認為字符串了
a=`expr 2 + 2` # a=4
`
反引號內(nèi)的會被當(dāng)做一個命令來執(zhí)行活合,因為上面例子是將 expr 命令放在 =
號右側(cè)雏婶,如果不加反引號,expr 會被當(dāng)做字符串處理
有些算術(shù)運算符需要加轉(zhuǎn)義符白指,如乘號 *留晚,大于 >,小于 < 等
算術(shù)運算符跟兩側(cè)的變量基本都需要以空格隔開告嘲,這樣才能辨別是字符串還是表達式
expr 2 + 2 # 4错维,加法運算
expr 2+2 # 2+2奖地,整個被當(dāng)做字符串處理
expr 2 - 2 # 0,減法運算
expr 2 \* 2 # 4赋焕,乘法運算参歹,需要加轉(zhuǎn)義
expr 2 / 2 # 1,除法運算
expr 2 % 2 # 0宏邮,取余運算
expr 2 \> 1 # 1泽示,比較運算缸血,需要加轉(zhuǎn)義
expr 2 \< 1 # 0蜜氨,比較運算,需要加轉(zhuǎn)義
expr 2 == 2 # 1
(()) 和 $(())
(())
雙括號內(nèi)捎泻,可以執(zhí)行表達式飒炎,多個表達式之間以 ,
逗號隔開,最后一個表達式會被作為 (())
運算的結(jié)果笆豁,可以通過在前面加個 $
提取結(jié)果
echo $((a=2+2,$a+2)) # 6
echo $a # 4
((b=$a*2)) # 8, * 號不用加轉(zhuǎn)義符
(())
和 expr 有各自優(yōu)缺點:
-
(())
支持語句郎汪,即形如((a=2+2))
,但 expr 只支持表達式闯狱,expr 2 + 2
-
(())
里的乘號煞赢,大于號等不需要加轉(zhuǎn)義符,expr 需要加轉(zhuǎn)義符 -
(())
只支持整數(shù)的運算哄孤,不支持字符串照筑、小數(shù)的計算,expr 支持 - 等等其他未遇到的場景
$[]
簡單的算術(shù)表達式還有一種寫法:
a=$[2+2] # a=4
a=$[2*2] # a=4,不需要加轉(zhuǎn)義符
跟 expr 相比瘦陈,$[]
好處就是一些運算符無需加轉(zhuǎn)義符
$[]
跟 $(())
很像凝危,一樣支持語句,一樣支持多個表達式晨逝,通過 ,
逗號隔開蛾默,一樣會將最后一個表達式的值返回,但 $[]
前的 $
符合不能省略
注意:關(guān)于 $[]
和 $(())
的理解可能不是很正確捉貌,基本沒用過支鸡,只是在看資料時遇到,順手測了些數(shù)據(jù)梳理出來的知識點趁窃,以后有使用到苍匆,發(fā)現(xiàn)錯誤的地方再來修改。
而且棚菊,目前碰到的 shell 腳本的需求場景浸踩,更多的是參數(shù)的獲取、變量的使用统求,因為需要動態(tài)生成命令來執(zhí)行检碗,這種場景比較多据块,關(guān)于表達式運算的場景比較少,所以先不必過多關(guān)注折剃。
語法-條件判斷 if
if 的語法:
if condition
then
command
...
elif condition; then
command
...
else
fi
如果想讓 then 和 if 同行另假,那么需要用 ;
分號隔開,同理怕犁,fi 如果想跟 else 或 then 同行边篮,也需要 ;
分號隔開,否則會有語法錯誤
if 的本質(zhì)其實是檢測命令的退出狀態(tài)奏甫,雖然我們經(jīng)掣杲危可以看到這種寫法:
if [ 2 -eq 2 ]
if [[ 2 == 1 ]]
if (( 1 == 1 ))
以上三種,不管是中括號阵子,雙中括號思杯,雙小括號,其本質(zhì)都是在運行數(shù)學(xué)計算命令挠进,既然是命令色乾,就都會有命令的退出狀態(tài)
命令退出狀態(tài)有兩種,0 是正常领突,非 0 是異常暖璧,同時,可以用 $?
來獲取上個命令的執(zhí)行退出狀態(tài)君旦,所以可以來試試看:
[ 2 -eq 2 ]
echo $? # 0澎办,正常
[ 2 == 1 ]
echo $? # 1,非正常
[[ abc == abc ]]
echo $? # 0于宙,正常
[[ ab == abc ]]
echo $? # 1浮驳,非正常
(( 1 == 1 && 1 > 0 ))
echo $? # 0,正常
(( 1 == 1 && 1 > 1 ))
echo $? # 1捞魁,非正常
明白了嗎至会?
其實, if 檢測的是命令的退出狀態(tài)谱俭,這也就意味著奉件,if 后面跟隨著的 condition 只要是命令就是符合語法的,不必像其他編程語言那樣昆著,必須是類似 if ()
這種語法結(jié)構(gòu)县貌,這也就是為什么,你可能看到別人寫的很奇怪的 if 代碼凑懂,比如:
if test 1 -eq 1; then echo true; fi # true
if whoami; then echo true; fi # root true
這樣一來茵宪,即使再看到別人寫的 if 代碼很奇葩芳肌,至少你也知道桥滨,它的執(zhí)行原理是啥了吧,至少也能看懂他那代碼的意圖了吧
好塘匣,雖然清楚了 if 檢測的本質(zhì)其實是命令的退出狀態(tài),但最好還是使用良好的編程風(fēng)格巷帝,使用閱讀性較好的寫法
關(guān)系運算符 -eq -ne -gt -lt -ge -le
- 等價于 == != > < >= <=
這些運算符只能用于比較數(shù)值類型的數(shù)據(jù)忌卤,且只能用于 []
, [[]]
這兩種楞泼,(())
不能使用這種運算符驰徊。
但使用 []
和 [[]]
這種語法形式時,有個很重要的點堕阔,就是中括號內(nèi)部兩側(cè)必須有空格棍厂,然后運算符兩側(cè)也需要有空格,否則可能就不是預(yù)期的行為了:
if [ 1 -eq 1 ]; then echo true; else echo false; fi # true
if [ 1-eq2 ]; then echo true; else echo false; fi # true印蔬,因為 1-eq2 被當(dāng)做字符串了勋桶,運算符左右需要有空格
if [ 1==2 ]; then echo true; else echo false; fi # true脱衙,因為 1==2 被當(dāng)做字符串了侥猬,運算符左右需要有空格
[]
和 [[]]
內(nèi)部既可以用類似 -eq
這種形式,也可以直接使用 ==
這種方式捐韩,后者可以用于比較字符串退唠,前者不能
布爾運算符 ! -o -a
- 分別對應(yīng):非運算,或運算荤胁,與運算
if [ 1 -eq 1 -a 1 -gt 1 ]; then echo true; else echo false; fi # false
if [ 1 -eq 1 -o 1 -gt 1 ]; then echo true; else echo false; fi # true
這些運算符只能適用于 []
瞧预,且只能跟關(guān)系運算符(-eq, -ne ...)使用
[[]]
以及 (())
都不能使用,且如果類似這樣使用 ==
和 -o
仅政,也是不起作用的:
if [ 1 > 2 -a 1 == 1 ]; then echo true; else echo false; fi # true垢油,1 > 2 明明不符合,卻返回 true 了圆丹,所以 -a 這種運算符不能喝 > 這類運算符合用滩愁,但使用 -gt 就是正常的了
if [[ 1 -eq 1 -o 1 -gt 2 ]]; then echo true; else echo false; fi
#sh: syntax error in conditional expression
#sh: syntax error near `-o'
#異常,[[]] 不支持使用布爾運算符
邏輯運算符 && ||
- 邏輯的 AND 和邏輯的 OR
if [[ 1 == 1 && 1 > 2 ]]; then echo true; else echo false; fi # false
這種運算符只能適用于 [[]]
辫封,此時不管是使用 ==
這類運算符硝枉,還是 -eq
這類,都是允許的
[]
和 (())
都不適用
當(dāng)需要有嵌套的判斷時倦微,可以拆開妻味,比如:
if [[ 1 == 1 ]] && [[ 1 > 3 || 1 > 0 ]]; then echo true; else echo false; fi # true
# 相當(dāng)于 if ((1==1) && ((1>3)||(1>0)))
字符運算符 = != -z -n $
- = != 用于判斷字符串是否相等
- -z 用于判斷字符串長度是否為 0,是的話欣福,返回 true
- -n 用于判斷字符串長度是否為 0责球,不是的話,返回 true
- $xxx 用于判斷 xxx 字符串是否為空,不為空返回 true
a='abc'
if [ $a == absc ]; then echo true; else echo false; fi # true
if [ -n $a ]; then echo true; else echo false; fi # true 雏逾,因為長度不為0
if [ -z $a ]; then echo true; else echo false; fi # false裁良,因為長度不為0
if [ $a ]; then echo true; else echo false; fi # true
這種運算符適用于 []
和 [[]]
這兩種,不適用于 (())
文件測試運算符 -d -r -w -x -s -e
-f 檢測文件是否是普通文件(既不是目錄校套,也不是設(shè)備文件)
-r 檢測文件是否可讀
-w 檢測文件是否可寫
-x 檢測文件是否可執(zhí)行
-s 檢測文件是否為空
-e 檢測文件是否存在
-d 檢測文件是否是目錄
a=test.sh
if [ -e $a ]; then echo true; else echo false; fi # 檢測 test.sh 文件是否存在
if [ -d $a ]; then echo true; else echo false; fi # 檢測 test.sh 是否存在且是否是目錄
這類運算符適用于 []
和 [[]]
這兩種价脾,不適用于 (())
涉及計算的判斷條件
大部分場景下,if 的條件判斷笛匙,使用上述的運算符結(jié)合 [[]]
使用就可以了侨把,但有某些場景,比如先進行算術(shù)運算之后妹孙,再判斷結(jié)果:
if ((1+1>2)); then echo true; else echo false; fi # false
如果想使用 [[]]
實現(xiàn)秋柄,可以是可以,但有些麻煩:
if [[ $[1+1] > 2 ]]; then echo true; else echo false; fi # false
就是需要先讓 1+1 當(dāng)做表達式計算結(jié)束蠢正,并獲取結(jié)果骇笔,然后再來做判斷
(())
有一點需要注意,它只能進行整數(shù)運算嚣崭,不能對小數(shù)或字符串進行運算
小結(jié)
腳本中使用到 if 條件判斷的場景肯定也很多笨触,絕大多數(shù)情況下,使用 [[]]
就足夠覆蓋需求場景了
不管是需要對文件的(目錄雹舀、存在芦劣、大小)判斷说榆,還是需要對字符串或命令執(zhí)行結(jié)果的判斷虚吟,使用 [[]]
都可以實現(xiàn)了
其實,[[]]
可以說是 []
的強化版签财,后者能辦到的串慰,前者都行,而對于 (())
唱蒸,更多是整數(shù)運算表達式的使用場景邦鲫,拿來結(jié)合 if 使用,純粹是因為剛好遇見而已油宜,并不是專門給 if 設(shè)計的掂碱,畢竟 if 只檢測命令執(zhí)行結(jié)果,只要是命令慎冤,都可以跟它搭
語法-函數(shù)和參數(shù)
- 函調(diào)定義
function add() {
// ...
}
# 省略 function 關(guān)鍵字
add(){
echo $*
echo ${12}
return 1
}
- 函數(shù)調(diào)用
add 1 2 #sh 1 2
函數(shù)調(diào)用時疼燥,直接函數(shù)名即可,如果需要參數(shù)蚁堤,跟其他編程語言不同醉者,定義時不能指明參數(shù)但狭,而是函數(shù)內(nèi)部直接通過 $n 來獲取參數(shù),需要第幾個撬即,n 就是第幾
函數(shù)調(diào)用時立磁,當(dāng)需要傳參時,直接跟在函數(shù)名后面剥槐,以空格隔開唱歧,函數(shù)名不需要帶括號
參數(shù) $n
$0
$*
$#
讀取參數(shù),參數(shù)可以是執(zhí)行腳本時傳遞的參數(shù)粒竖,也可以是執(zhí)行函數(shù)時傳遞的參數(shù)
$1
表示第一個參數(shù)颅崩,以此類推${10}
當(dāng)參數(shù)個數(shù)超過 9 個后,需要用大括號來獲取$*
或$@
輸出所有參數(shù)$0
輸出腳本文件名$#
輸出參數(shù)個數(shù)
所以蕊苗,腳本內(nèi)部開始沿后,可以用 echo $0 $*
來輸出外部使用該腳本時,傳遞的參數(shù)
語法-腳本文件的 source 和執(zhí)行
當(dāng)前 shell 腳本內(nèi)朽砰,可以導(dǎo)入其他腳本文件尖滚,也可以直接執(zhí)行其他腳本文件
source
當(dāng)某個腳本被其他腳本導(dǎo)入時,其實相當(dāng)于從其他文件拷貝腳本代碼過來當(dāng)前腳本環(huán)境內(nèi)執(zhí)行瞧柔,導(dǎo)入有兩種命令:
. filename # 注意點號 . 和文件名中間有空格
#或者
source filename
被導(dǎo)入的腳本文件不需要是可執(zhí)行類型的漆弄,畢竟執(zhí)行環(huán)境還是當(dāng)前腳本啟動的 shell 進程,只是執(zhí)行的代碼無需再寫一遍非剃,直接從其他地方拷貝過來一條條執(zhí)行而已
執(zhí)行
在當(dāng)前腳本內(nèi)置逻,也可以直接執(zhí)行其他腳本文件推沸,同樣有兩種類型备绽,如:
sh ./test.sh
echo $? # 腳本執(zhí)行的退出狀態(tài)
#或者
./test.sh
兩種的區(qū)別就在于:
- 前者不需要被執(zhí)行的腳本是可執(zhí)行類型的,因為已經(jīng)手動指定 sh 來作為腳本解釋器了鬓催,腳本內(nèi)部開頭的
#!
聲明也會失效掉 - 后者的話肺素,純粹就是執(zhí)行一個可執(zhí)行文件的方式,那就需要這個腳本文件是可執(zhí)行類型的宇驾,同時腳本的解釋器由腳本文件內(nèi)部開頭的
#!
聲明
我們通常都會將不同工作職責(zé)寫在不同腳本文件中倍靡,然后某個腳本文件內(nèi),來控制其他腳本文件的執(zhí)行流程课舍,那么塌西,這時候,就需要知道每個流程的腳本是否執(zhí)行正常筝尾,這時候捡需,就可以借助腳本的 exit 命令和 $?
來實現(xiàn)
每個腳本,如果正常執(zhí)行結(jié)束筹淫,那么腳本內(nèi)部最后應(yīng)該通過 exit 0
來退出站辉,表示當(dāng)前腳本正常執(zhí)行,如果執(zhí)行過程出異常了,那么應(yīng)該執(zhí)行 exit 1
只要是非 0 即可饰剥,來表示當(dāng)前腳本執(zhí)行異常
那么殊霞,調(diào)用執(zhí)行這個腳本的,就可以通過 $?
來獲取腳本執(zhí)行結(jié)果汰蓉,如:
sh ./test.sh
if [[ $? -ne 0 ]]; then
echo '異常'
exit 1
fi
這樣就可以來控制腳本執(zhí)行流程
語法-其他
注釋
#xxxx
單個 #
用來注釋后面內(nèi)容
#!/bin/sh
腳本文件的頂行绷蹲,告訴系統(tǒng),應(yīng)該去哪里用哪個解釋器執(zhí)行該腳本顾孽;
但如果該腳本不是直接執(zhí)行瘸右,而是作為參數(shù)傳遞給某個解釋器,如:
/bin/sh xxx.sh
岩齿,那太颤,文件頂頭的 #!
聲明就會被忽視,畢竟已經(jīng)明確指定解釋器了
for 循環(huán)
for loop in 1 3 4 5 6
do
done
$?
用來獲取上個命令的執(zhí)行之后的退出狀態(tài)盹沈,或者獲取上個函數(shù)執(zhí)行的返回值龄章,0 表示正常,非0 表示不正常
所以乞封,腳本如期結(jié)束時做裙,腳本內(nèi)最后應(yīng)該 exit 0 來退出命令(每個腳本的執(zhí)行其實就是執(zhí)行命令)
read xxx
從標準輸入中讀取一行,并賦值給 xxx 變量
printf
輸出格式化
輸入輸出
默認的輸入輸出都是終端肃晚,但可通過 >
<
來進行修改锚贱,比如
ls > file
將輸出寫入到文件中,覆蓋寫入
ls >> file
將輸出寫入到文件中关串,追加寫入
xxx.sh < file
本來是從鍵盤輸入到終端拧廊,轉(zhuǎn)移到從文件讀取內(nèi)容
<<EOF
xxx.sh<<EOF
....
EOF
將兩個 EOF 之間的內(nèi)容作為輸入
ls > /dev/null
如果希望執(zhí)行某個命令,但又不希望在屏幕上顯示晋修,那么可以將輸出重定向到 /dev/null
寫入 /dev/null
中的內(nèi)容會被丟棄
語法-易混淆
有些語法很容易混淆吧碾,在這里列一列:
${} 和 $[] 和 $() 和 $(())
name=dasu
echo ${name} # dasu,變量的使用
echo $[2+2] # 4墓卦,執(zhí)行算術(shù)表達式倦春,可認為作用跟 expr 類似,但兩者有各自局限落剪,expr 支持字符串的關(guān)系運算等
echo `expr 2 + 2` # 4
echo $((2+2)) # 4睁本,執(zhí)行整數(shù)的算術(shù)表達式,可認為作用跟 expr 類似忠怖,但兩者有各自局限呢堰,expr 支持字符串的關(guān)系運算等
echo `expr 2 + 2` # 4
echo $(whoami) # root,執(zhí)行命令脑又,可認為作用跟 `` 反引號類似
echo `whoami` # root
雖然 $
后面可以跟隨各種各樣符號暮胧,來實現(xiàn)不同用途锐借,但其實,都可以歸納為 $
的作用是往衷,提取后面的結(jié)果钞翔,然后將其作為輸入,再次讓 shell 解釋器處理席舍。
比如說 ${xxx}
布轿,就是將讀取變量 xxx 的值,然后輸入給解釋器:
name=dasu
${name} # dasu: command not found
echo ${name} dasu
是吧来颤,就是提取汰扭,然后再輸入給解釋器,其實也就是變量值的替換福铅,將變量替換為實際的值
那么萝毛,這么理解的話,()
小括號內(nèi)的其實就是在執(zhí)行命令滑黔,$()
就是將命令執(zhí)行結(jié)果替換命令笆包;(())
兩個小括號內(nèi)的其實就是在執(zhí)行表達式,$(())
就是將表達式執(zhí)行結(jié)果替換掉表達式略荡;$[]
同理庵佣;
那么,可能你就會有疑問了:
[1+1] # [1+1]: command not found
((1+1)) # 無報錯也無輸出
知道為什么嗎汛兜?
因為 (())
是 shell 解釋器可以識別的語法巴粪,它知道這不是字符串
但 [1+1]
卻被解釋器當(dāng)做一整個字符串了,自然就找不到這個命令粥谬,shell 解釋器能識別的 []
語法應(yīng)該是肛根,中括號內(nèi)部兩側(cè)需要有空格,此時就不會認為它是字符串了帝嗡,如:
[ 1+1 ] # 無報錯也無輸出
當(dāng)有 $
時晶通,就無需區(qū)分字符串的場景了,自然也就可以省略掉空格了哟玷,但保留好習(xí)慣,都留著空格也是很好的做法
命令和表達式
- 命令是指 shell 支持的命令一也,比如 ls巢寡,pwd,whoami 等等
- 表達式是指通過運算符組合成的各種表達式椰苟,如算術(shù)表達式抑月,賦值表達式,關(guān)系表達式等等
shell 內(nèi)的每一行代碼都是在執(zhí)行命令舆蝴,所以直接在 shell 內(nèi)書寫表達式是會執(zhí)行異常谦絮,因為表達式不是命令
一些命令跟傳入?yún)?shù)题诵,如 echo xxx,echo 后跟隨著會被當(dāng)做字符串處理层皱,如果想讓 xxx 這串被作為命令執(zhí)行性锭,那需要將 xxx 放置于 `xxx`
反引號或者 $(xxx)
內(nèi)
如果想讓 xxx 被當(dāng)做表達式處理,則需要借助一些命令叫胖,如 expr草冈;
如果表達式是算術(shù)表達式,那可通過 ((xxx))
包裹這些表達式瓮增,但需要獲取表達式結(jié)果時怎棱,通過 $((xxx))
在前面加個 $
實現(xiàn)
本篇就先介紹一些基礎(chǔ)語法吧,當(dāng)然并不全面绷跑,但足夠看懂基本的 shell 腳本代碼了
下一篇會介紹一些常用命令拳恋,如 expect,scp砸捏,ssh诅岩,以及再拿個 jenkins 上構(gòu)建項目的實例腳本來講講