Bash語法

Shell簡介

shell是一種腳本語言,通過對應的腳本解釋器解釋執行,一般作爲內置於操作系統的應用程序向用戶提供訪問操作系統內核的服務。不過shell也分化出了很多種類,常見的有shell(/bin/sh)、bash(/bin/bash)、csh(/usr/bin/csh)、ksh(/usr/bin/ksh)、powershell(windows的shell)等,大部分shell語法互相兼容,其中bash是linux默認的shell,後面以bash作爲典型shell進行介紹

bash語法

語句
  • shell所有語句不需要使用分號 ; 進行終結
  • 執行shell腳本文件等價於在bash窗口即時逐行執行語句,所以某一行的語法錯誤或執行錯誤不會影響後續語句的執行
變量
  • 聲明(定義)&賦值變量
    和大部分腳本語言類似,shell無需聲明變量,或者說直接給某個變量名賦值則相當於聲明瞭該變量,如:
variable="abc" #✅
variable = "abc" #❌

🌟和大部分語言不同的是,shell賦值語句的等號 = 兩邊不能有 空格, 後面會看到空格的存在與否會影響shell語法解析,就像python使用縮進來標識語句塊一樣令人討厭。。

  • 使用變量
    當使用一個變量時(即變量作爲右值),需要在變量名前面加上 $,在變量名兩邊加上大括號 {}, 大括號一般情況下可缺省,主要爲了幫助解釋器識別變量的邊界,在部分必要情況下不可缺省
echo $variable #可缺省
echo ${variable}
echo "I am good at ${skill}Script" #如果使用的是skill變量則大括號不可缺省,缺省時解釋器會使用skillScript變量

所以推薦使用變量時均不缺省大括號

  • 只讀變量
    使用 readonly 命令可以將變量定義爲只讀變量,被定義爲只讀變量後變量無法再被賦值
url="www.google.com"
readonly url
url="www.baidu.com" #賦值失敗
  • 刪除變量
    使用 unset 命令可以刪除變量,使該變量重新回到賦值之前的狀態(即未定義狀態),輸出爲定義變量時shell解釋器的行爲是輸出一個空行,同時只讀變量無法被刪除
variable=1
unset variable
echo $variable #輸出空
  • 變量類型
    在運行shell時,會同時存在如下三種變量
    • 局部變量
      局部變量在腳本或命令中定義,僅在當前shell實例中生效,其它shell實例無法訪問
    • 環境變量
      定義在os中,所有的程序(包括shell)都可以訪問環境變量,有的程序需要環境變量作爲參數來運行,shell腳本中也可以定義環境變量
    • shell變量
      由shell程序(解釋器)設置的特殊變量,一部分是環境變量,一部分是局部變量
數據類型

shell支持的數據結構不多,所以shell腳本與其他腳本語言的優勢在於環境兼容性強,劣勢在於不適合複雜度較高,要進行數據結構比較複雜的操作時最好使用其它複雜腳本語言(如python),然後使用shell進行調用即可

  • 字符串
    字符串是shell腳本中最常用的數據類型,字符串可以使用 單引號雙引號不使用引號,需要注意單引號中的任何字符都會原樣輸出,而雙引號支持轉義字符和變量,另外連續的字符串會直接拼接而無需操作符
name="bob"
echo "hello,$name!" #hello,bob!
echo "hello,"$name"!" #hello,bob!
echo 'hello,$name!' #hello,$name!
echo 'hello,'$name'!' #hello,bob!
  • 數字
    直接將數字給變量賦值即可,但是shell默認只支持整數運算而不支持浮點數運算,需要算術運算時使用 expr。因爲本質上解釋器 大部分時候 將數字當作字符串處理的,畢竟字符串可以缺省引號,expr只是將字符串轉化爲數字進行算術運算並將結果重新以字符串返回
a=10
b=-5
echo `expr $a + $b` #5
  • 數組
    shell支持一維數組(不支持多維數組),通過下標索引,且下標由0開始,可以使用不連續的下標,下標的範圍也沒有限制,和lua比較像
    • 定義(初始化)數組
      shell中使用小括號來表示數組,數組元素之間用 空格 分開,兩邊用 小括號() 包圍
    array=(value0 value1 value2)
    
    注意:和其它動態語言類似,數組不初始化也可以直接進行賦值使用
    • 使用數組
      使用 數組名[下標] 即可訪問數組某個元素(作爲變量)
    echo ${array} #不指定下標時直接讀取第0個元素的值
    echo ${array[0]} #讀取數組的第0個元素的值(這種情況大括號無法省略,否則或讀取$array變量)
    echo ${array[@]} #使用@符號獲取數組中所有元素
    array[0]=5 #修改第0個元素的值
    
    • 數組長度
      shell使用 # 獲取數組或字符串變量的長度,但是不能獲取常量的長度
    str="string"
    array=(1 "abcdefg" -4)
    echo ${#str} #6
    echo ${#"string"} #報錯 “string”是字面值&常量
    echo ${#array[@]} #3 返回數組長度3
    echo ${#array[1]} #7
    echo ${#array[2]} #2(-4仍然被當作"-4"字符串處理)
    
運算符

shell語法中,多元運算符的兩邊必須有 空格

  • 算術運算符
    原生bash不支持簡單的數學運算,但是可以通過其它命令如expr(一種表達式計算工具)完成表達式的求值操作,例如value=`expr 2 + 2`語句即可將2+2的結果計算出來並賦值給val變量
    • + 加法運算
    • - 劍法運算
    • * 乘法運算
      注意乘號(*)前邊必須加反斜槓(\)轉義才能實現乘法運算
    • / 除法運算
    • % 取餘運算
  • 關係運算符
    關係運算符僅適用於數字,其中=和!=不僅適用於數字也適用於字符串的比較
    • -eq(=) equal 判斷是否相等
    • -ne(!=) not equal 判斷是否不等
    • -gt(>) greater than 判斷是否大於
    • -ge(>=) greater equal 判斷是否大於等於
    • -lt(<) less than 判斷是否小於
    • -le(<=) less equal 判斷是否小於等於
  • 邏輯運算符
    邏輯運算符僅適用於運算符兩邊均是布爾值的運算
    • ! 非運算
    • -o(||) 或運算 or
    • -a(&&) 與運算 and
  • 字符串運算符
    字符串運算符任何時候均適用(因爲bash支持將數字當作字符串處理)
    • = 判斷是否相等
    • != 判斷是否不等
    • -z zero 判斷字符串長度是否爲0
    • -n not zero 判斷字符串長度是否不爲0
    • $ 判斷字符串是否不爲空,即該變量是否存在,存在時返回true
  • 文件檢測運算符
    文件檢測運算符適用於檢測Unix文件的各種屬性,作爲路徑字符串的單元操作符
    • -b block 判斷路徑是否是塊設備文件
    • -c char 判斷路徑是否是字符設備文件
    • -d directory 判斷路徑是否是目錄路徑(即文件夾)
    • -e exist 判斷路徑是否存在(即該路徑對應一個文件或者目錄)
    • -f file 判斷路徑是否是文件路徑(既不是目錄也不是設備文件)
    • -r read 判斷文件是否可讀
    • -w write 判斷文件是否可寫
    • -x exec 判斷文件是否可執行
    • -s 判斷文件是否不爲空(文件大小是否大於0)
流程控制語句
  • 條件判斷
    完整的條件判斷語句快如下所示
if condition1
then
  command1
  command2
  commandN
elif condition2;then #用;可以代替換行
  command3
else
  command4
fi
  • 注意如果某個elif分支或else分支沒有任何指令執行,則不能寫這個分支,否則解釋器會報錯
  • condition語句一般使用 中括號[] 包含返回布爾值的運算符的運算語句,如 [ a == b ],且變量名與中括號之間必須有 空格
  • 循環
    shell支持多種循環語句
    • for循環
      可以使用for循環語句遍歷變量列表、數組或二者的組合
    for var in item1 item2 ${array[@]} item3 itemN
    do
      command1
      command2
    done
    
    • while/until循環
      while/until循環用於不斷執行一系列命令,也可以用於輸入文件中讀取數據,區別在於until循環的條件語句condition是終止條件且循環體至少執行一次
    while/until condition
    do
      command1
      command2
    done
    
    • break和continue
      和其它語言一樣,break命令跳出循環體執行後面的語句,continue跳出本次循環而不影響後面循環的執行
  • 選擇語句
    shell支持case xxx in形式的選擇語句,類似於其它語言的switch case語句。shell的選擇語句使用 ;; 表示break,每個case選項使用 xxx) 表示,*)表示default,使用esac結束該選擇語句
case $value in
  "value1")
    echo "case1"
    ;;
  "value2")
    echo "case2"
    ;;
  *)
    echo "case default"
    ;;
esac
  • 關於(、((、[、[[的區別
  • 凡是計算結果是布爾值的運算符,必須只存在於條件判斷語句之中而不能給變量賦值, 因爲shell把一切變量賦值當作字符串賦值處理
函數

shell的函數用起來有點像宏, 而不是編程語言普遍意義上傳給函數,因爲函數沒有對應的局部變量棧,內部定義的變量可作爲shell的變量在函數外獲取。傳入函數的參數在函數內部通過$n來獲取,所以函數體內部無法獲取shell外部傳來的參數(如果想將其傳入函數內可以作爲函數的參數傳入),但是函數體內可以獲取shell中定義的局部變量和環境變量。函數體內可以用return語句返回一個數值(0-255),然後在函數調用完成後通過$?獲取

a=1
function funcName() {
  echo $1
  echo $a
  b=2
  return 3
}
funcName "param1" param2 # param1 1
echo $? #3
echo $b #2
  • 函數聲明
    函數聲明使用function func() {...},其中function和()二者可以缺省一個,()和{}之間的空格也可以缺省
  • 函數調用
    和調用指令一樣通過funcName param1 param2...paramN進行調用,函數名後面是空格分開的參數列表
註釋
  • 單行註釋
    # 符號後面的即爲單行註釋,會被解釋器忽略
a=1 #註釋內容
  • 多行註釋
    多行註釋使用如下格式
:<<EOF
comment...
comment...
EOF

其中EOF可以替換成任意註釋中不存在的字符串,類似http協議中的boundary

常用命令&約定
  • shell強大之處在於可以調用所有環境變量Path中的程序,每個程序在shell都作爲一個命令
  • #!是shell文件開始部分一個約定的標記(和python聲明文件編碼類似),它告訴系統這個腳本需要什麼解釋器來執行,即使用哪一種Shell, 如:#!/bin/bash即聲明該shell腳本使用bash解釋執行
  • 參數處理
    在執行shell腳本時,通常需要向腳本傳遞參數,在腳本內通過 $n 獲取參數,n代表參數的位置從1開始,因爲無法以純數字作爲變量名所以並不會衝突,但是超過10後必須使用 ${n} 獲取避免歧義。下面還有一些特殊字符處理參數
    • $# 參數總個數
    • $* 以單字符串返回腳本傳遞的所有參數(一般用於log)
    • $@ 返回每個參數構成的數組(一般用於遍歷)
    • $- 顯示shell當前使用的選項,和set命令對應
    • $? 返回上一條命令(包括函數調用)的退出狀態 0表示正常返回
  • 文件包含
    shell可以包含外部腳本,通過 .source 進行包含,是類似include的概念,當解釋器識別到文件包含指令時,會將對應文件中的文本替換到包含指令中再進行執行。
. fileName1 #包含fileName1文件
source fileName2 #包含fileName2文件
  • 文件包含和直接執行其它shell的區別
    我們知道也可以用./script.shbash script.shsh script.sh等方式直接執行其它shell,但是二者有本質區別:文件包含形式執行的shell文件不需要有可執行權限,只需要有可讀權限,因爲shell將包含文件的文本內容視爲shell本身的一部分。文件包含的shell腳本和原shell共享所有變量,是在同一個進程中執行的;而第二種方式直接執行的shell是作爲當前父進程下的子進程執行的,只能通過傳入參數的方式將變量傳入子進程,且子進程中各項變量和操作在結束後不會傳回父進程(只能設置一個返回值)。所以某種意義上來說執行其它shell更像是一次函數調用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章