shell 基本語法

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 里都是命令,所以只有當在命令參數(shù)、或表達式右值時,字符串才會被當做字符串處理,否則都會被認為命令,從而報找不到 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é)果,會再次作為輸入,被當做下一行 shell 腳本命令執(zhí)行,所以需要注意這個結(jié)果是否可以作為命令被執(zhí)行

`whoami` # root: command not found

因為 whoami 命令執(zhí)行輸出 root,root 又被作為命令執(zhí)行,就報錯了

如果有需求是要將命令執(zhí)行結(jié)果,作為日志輸出,這種場景就很適用了

語法-表達式

編程語言都可以通過各種運算符來實現(xiàn)一個個表達式,如算術(shù)表達式、賦值表達式等

但由于在 shell 內(nèi),都被當做命令來處理,所以正常的運算符無法直接使用,需要借助其他命令或語法實現(xiàn)

expr

a=2 + 2   # +: command not found
a=2+2     # 2+2被認為字符串了
a=`expr 2 + 2`  # a=4

` 反引號內(nèi)的會被當做一個命令來執(zhí)行,因為上面例子是將 expr 命令放在 = 號右側(cè),如果不加反引號,expr 會被當做字符串處理

有些算術(shù)運算符需要加轉(zhuǎn)義符,如乘號 *,大于 >,小于 < 等

算術(shù)運算符跟兩側(cè)的變量基本都需要以空格隔開,這樣才能辨別是字符串還是表達式

expr 2 + 2  # 4,加法運算
expr 2+2    # 2+2,整個被當做字符串處理
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 被當做字符串了,運算符左右需要有空格
if [ 1==2 ]; then echo true; else echo false; fi # true,因為 1==2 被當做字符串了,運算符左右需要有空格

[][[]] 內(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 這類,都是允許的

[](()) 都不適用

當需要有嵌套的判斷時,可以拆開,比如:

if [[ 1 == 1 ]] && [[ 1 > 3 || 1 > 0 ]]; then echo true; else echo false; fi # true
# 相當于 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 當做表達式計算結(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)用時,當需要傳參時,直接跟在函數(shù)名后面,以空格隔開,函數(shù)名不需要帶括號

參數(shù) $n $0 $* $#

讀取參數(shù),參數(shù)可以是執(zhí)行腳本時傳遞的參數(shù),也可以是執(zhí)行函數(shù)時傳遞的參數(shù)

  • $1 表示第一個參數(shù),以此類推

  • ${10} 當參數(shù)個數(shù)超過 9 個后,需要用大括號來獲取

  • $*$@ 輸出所有參數(shù)

  • $0 輸出腳本文件名

  • $# 輸出參數(shù)個數(shù)

所以,腳本內(nèi)部開始,可以用 echo $0 $* 來輸出外部使用該腳本時,傳遞的參數(shù)

語法-腳本文件的 source 和執(zhí)行

當前 shell 腳本內(nèi),可以導(dǎo)入其他腳本文件,也可以直接執(zhí)行其他腳本文件

source

當某個腳本被其他腳本導(dǎo)入時,其實相當于從其他文件拷貝腳本代碼過來當前腳本環(huán)境內(nèi)執(zhí)行,導(dǎo)入有兩種命令:

. filename # 注意點號 . 和文件名中間有空格

#或者

source filename

被導(dǎo)入的腳本文件不需要是可執(zhí)行類型的,畢竟執(zhí)行環(huán)境還是當前腳本啟動的 shell 進程,只是執(zhí)行的代碼無需再寫一遍,直接從其他地方拷貝過來一條條執(zhí)行而已

執(zhí)行

在當前腳本內(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)部開頭的 #! 聲明

我們通常都會將不同工作職責寫在不同腳本文件中,然后某個腳本文件內(nèi),來控制其他腳本文件的執(zhí)行流程,那么,這時候,就需要知道每個流程的腳本是否執(zhí)行正常,這時候,就可以借助腳本的 exit 命令和 $? 來實現(xiàn)

每個腳本,如果正常執(zhí)行結(jié)束,那么腳本內(nèi)部最后應(yīng)該通過 exit 0 來退出,表示當前腳本正常執(zhí)行,如果執(zhí)行過程出異常了,那么應(yīng)該執(zhí)行 exit 1 只要是非 0 即可,來表示當前腳本執(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

輸出格式化

Shell 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] 卻被解釋器當做一整個字符串了,自然就找不到這個命令,shell 解釋器能識別的 [] 語法應(yīng)該是,中括號內(nèi)部兩側(cè)需要有空格,此時就不會認為它是字符串了,如:

[ 1+1 ] # 無報錯也無輸出

當有 $ 時,就無需區(qū)分字符串的場景了,自然也就可以省略掉空格了,但保留好習(xí)慣,都留著空格也是很好的做法

命令和表達式

  • 命令是指 shell 支持的命令,比如 ls,pwd,whoami 等等
  • 表達式是指通過運算符組合成的各種表達式,如算術(shù)表達式,賦值表達式,關(guān)系表達式等等

shell 內(nèi)的每一行代碼都是在執(zhí)行命令,所以直接在 shell 內(nèi)書寫表達式是會執(zhí)行異常,因為表達式不是命令

一些命令跟傳入?yún)?shù),如 echo xxx,echo 后跟隨著會被當做字符串處理,如果想讓 xxx 這串被作為命令執(zhí)行,那需要將 xxx 放置于 `xxx` 反引號或者 $(xxx) 內(nèi)

如果想讓 xxx 被當做表達式處理,則需要借助一些命令,如 expr;

如果表達式是算術(shù)表達式,那可通過 ((xxx)) 包裹這些表達式,但需要獲取表達式結(jié)果時,通過 $((xxx)) 在前面加個 $ 實現(xiàn)


本篇就先介紹一些基礎(chǔ)語法吧,當然并不全面,但足夠看懂基本的 shell 腳本代碼了

下一篇會介紹一些常用命令,如 expect,scp,ssh,以及再拿個 jenkins 上構(gòu)建項目的實例腳本來講講

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,727評論 0 5
  • 一、Python簡介和環(huán)境搭建以及pip的安裝 4課時實驗課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 6,353評論 0 10
  • Linux Shell 基本語法 一. Linux基本命令 1.1. cp命令 該命令的功能是將給出的文件或目錄拷...
    M_Baron閱讀 2,671評論 0 1
  • 本節(jié)所講內(nèi)容:shell 基本語法變量表達式判斷語句if表達式 先看一個簡單的shell程序[root@xuego...
    盤木閱讀 806評論 0 0
  • 創(chuàng)建腳本 可以使用 vi/vim 命令來創(chuàng)建文件),新建一個文件 test.sh,擴展名為 sh(sh代表shel...
    妮妮世界閱讀 366評論 0 0

友情鏈接更多精彩內(nèi)容