首頁 > 軟體

Shell指令碼的超詳細講解(推薦!)

2022-07-20 18:01:53

一、Shell指令碼基礎概念

1.1 什麼是shell?

shell英文翻譯過來是外殼的意思,作為計算機語言來理解可以認為它是作業系統的外殼。我們可以通過shell命令來操作和控制作業系統,比如Linux中的shell命令就包括lscdpwd等等。

shell是站在核心的基礎上編寫的一個應用程式,它連線了使用者和Linux核心,從而讓使用者能夠更加便捷、高效、安全的使用linux核心,這其實就是shell的本質。

使用專業術語的說法來解釋,Shell其實是一個命令直譯器,它通過接受使用者輸入的Shell命令來啟動、暫停、停止程式的執行或對計算機進行控制。

1.2 什麼是shell指令碼

shell指令碼就是由Shell命令組成的執行檔案,將一些命令整合到一個檔案中,進行處理業務邏輯,指令碼不用編譯即可執行。它通過直譯器解釋執行,所以速度相對來說比較慢。

1.3 shell指令碼的意義

我們在1.2中也解釋道shell指令碼其實就是shell命令組成的檔案,shell指令碼可以記錄命令執行的過程和執行邏輯,以便以後重複執行,還可以批次、定時處理主機,方便管理員進行設定或者管理。

二、建立一個簡單的Shell指令碼

2.1 建立一個shell指令碼檔案

在建立shell指令碼時,我們預設新建一個以.sh/.script結尾的檔案,主要是為了讓程式設計師更加快捷的辨認出該檔案是一個shell指令碼檔案。

我們建立一個test.sh的shell指令碼檔案,其中具體內容為下:

#!/bin/bash
echo hello
  • " # ”開頭的就是註釋,單行註釋
  • <<EOF … EOF 或 :<<! … ! :多行註釋
  • #!/bin/bash : 主要用於指定直譯器
  • Linux中提供的shell直譯器有:
  • /bin/sh
  • /bin/bash
  • /usr/bin/sh
  • /usr/bin/bash

2.2 執行一個Shell指令碼

我們根據指令碼檔案是否具有可執行許可權,將執行一個shell指令碼的方法分為兩大類。

2.2.1 指令碼檔案無執行許可權

這種情況下我們有三種方式來執行指令碼:

手動在環境中開啟指定直譯器:sh test.sh

直接在當前環境中執行的shell中執行指令碼:. test.sh

直接在當前環境中執行的shell中執行指令碼:source test.sh

2.2.2 指令碼檔案有執行許可權

在這一部分由於我們假設指令碼檔案有可執行器許可權,所以我們使用chmod +x test.sh為我們的test.sh檔案增加了可執行許可權。

我們知道當一個檔案具有可執行許可權時我們可以使用該檔案的路徑名直接執行該檔案,有兩種方式可以執行指令碼:

1.絕對路徑名執行指令碼檔案

絕對路徑就是從根目錄下開始記錄檔案路徑名,是檔案在計算機上真正存在的路徑。(如果不知道你的檔案路徑名,可以在當前位置的shell中使用pwd查詢當前所在位置)

2../相對路徑名的格式執行指令碼檔案

相對路徑是指以當前的檔案作為起點,相較於當前目錄的位置而被指向並且加以參照的檔案資源。

比如我們知道test.sh檔案的絕對路徑為/home/westos/Desktop/textcpp/test.sh,那麼當我們在testcpp資料夾中時,test.sh檔案的相對路徑為test.sh

又因為.代表當前所在位置,故而為其實./test.sh其實就是該檔案的絕對路徑,只是表示的方式不同。

三、基本語法

3.1 變數

變數名其實就是一片記憶體區域的地址或者可以說是定址符號,有了變數我們就可以使用一串固定的字元來表示不固定的目標。

3.1.1 變數型別

在shell中會同時存在三種型別變數。

  • 區域性變數:區域性變數在指令碼或命令中定義,僅在當前shell範例中有效,其他shell啟動的程式不能存取區域性變數。
  • 環境變數:所有的程式,包括shell啟動的程式,都能存取環境變數,有些程式需要環境變數來保證其正常執行。必要的時候shell指令碼也可以定義環境變數。
  • shell變數:shell變數是由shell程式設定的特殊變數。shell變數中有一部分是環境變數,有一部分是區域性變數,這些變數保證了shell的正常執行

3.1.2 變數操作

  1. 建立普通變數:name=“test”,組要注意的是等號兩邊不能有空格。
  2. 建立區域性變數:local name=“test”,使用local修飾的變數在函數體外無法存取,只能在函數體中使用。
  3. 建立唯讀變數:name=“only_read” -> readonly name,這種變數不可以被修改。
  4. 使用變數:echo $name或者echo ${name}
  5. 刪除變數:unset name,刪除之後的變數無法被存取,需要注意無法刪除唯讀變數。

3.1.3 字串變數

3.1.3.1 字串變數的建立

  1. 使用單引號建立:var='test'。
    這種方式建立的變數只能原樣輸出,變數無效,我們可以借用c中的“字串常數”的定義理解這種特性。除此以外,單引號中不能出現單獨的單引號,跳脫也是不可以的。
  2. 使用雙引號建立:var="my name is ${name}",這種方式建立的字串變數有效,也可以出現跳脫符。

3.1.3.2 拼接字串

  1. 字面量拼接
    str01="1""2"或者str01="1"'2',這樣就將1和2兩個字元拼接在了一起。需要注意的是兩個串之間不可以有空格。
  2. 變數拼接
    str03=${part01}${part02}或str04=${part01}"end"或str05="${part01} ${part02}"這三種方式都可以拼接字串變數。
  3. 命令拼接
    str02= date“end”,這裡的date是一個shell命令,需要使用參照,具體如下:
str02=`date`"end"

3.1.3.3 獲取字串長度

1.使用wc -L命令

wc -L可以獲取到當前行的長度,因此對於單獨行的字串可以用這個簡單的方法獲取,另外wc -l則是獲取當前字串內容的行數。

echo "abc" |wc -L

2.使用expr length可以獲取string的長度

expr length ${<!--{C}%3C!%2D%2D%20%2D%2D%3E-->str}

3.awk獲取域的個數

但是如果大於10個字元的長度時是否存在問題需要後面確認

echo "abc" |awk -F "" '{print NF}'

4.通過awk+length的方式獲取字串長度

echo 「Alex」|awk '{print length($0)}'

5.通過echo ${#name}的方式

name=Alex
echo ${#name}

3.1.3.4 提取子字串

1.如下方式:

程式碼意義
${varible##*string}從左向右擷取最後一個string後的字串
${varible#*string}從左向右擷取第一個string後的字串
${varible%%string*}從右向左擷取最後一個string後的字串
${varible%string*}從右向左擷取第一個string後的字串

例,如下程式碼:

$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}

執行結果為rthought.jpg

2.使用${varible:n1:n2}

擷取變數varible從n1到n2之間的字串,可以根據特定字元偏移和長度,來選擇特定子字串,如下程式碼:

$ EXCLAIM=cowabunga
$ echo ${EXCLAIM:0:3}

執行結果最終顯示cow

3.1.4 陣列

如果說變數是儲存單個變數的記憶體空間,那麼陣列就是多個變數的集合,它儲存多個元素在一片連續的記憶體空間中。在bash中,只支援一維陣列,不支援多維陣列。

3.1.3.1 陣列定義與參照

定義一個陣列方式如下:

陣列名=(元素1 元素2 元素3 ... 元素n)

指定陣列對應下標的元素進行賦值:

陣列名[下標]=值

同時指定多個陣列元素進行賦值:

陣列名=([下標1]=值1 [下標2]=值2 ... [下標n]=值n)

參照陣列對應下標的元素:

 ${陣列名[下標]}

3.1.3.2 遍歷陣列元素

使用for(或while迴圈)迴圈遍歷陣列元素:

#!/bin/bash
a=(1 2 3 4 5 6)
for((i=0; i<10; i++))
do
    echo "a[$i]=${a[$i]}"
done

除此以外我們還可以使用${a[*]}或者${a[@]}來遍歷陣列元素,具體程式碼如下:

#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo ${a[@]}

3.1.3.3 獲取陣列長度

我們可以使用#來獲取陣列長度,需要注意的是在shell指令碼中我們越界存取陣列時是不會報錯的。

#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo "a len: ${#a[*]}"

我們先使用其獲取陣列中的元素後使用#獲取元素個數即可。

3.1.3.4 合併陣列

我們可以如下進行拼接:

#!/bin/bash
a=(1 2 3 4 5 6)
b=("hello" "zhaixue.cc")
c=(${a[*]} ${b[*]})

這樣我們就將兩個陣列拼接起來了。

3.1.3.5 刪除陣列元素

如果我們想要刪除某個陣列元素,具體程式碼如下:

#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo "a len: ${#a[*]}"
unset a[5]
echo ${a[*]}
echo "a len: ${#a[*]}"

執行結果如下:

我們如果要刪除整個陣列,可以執行unset a,舉例程式碼如下:

#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo "a len: ${#a[*]}"
unset a
echo ${a[*]}
echo "a len: ${#a[*]}"

3.1.5 變數傳參

相關的變數含義為:

變數含義
$0代表執行的檔名
$1代表傳入的第1個引數
$n代表傳入的第n個引數
$#引數個數
$*以一個單字串顯示所有向指令碼傳遞的引數。
$@與$*相同,但是使用時加引號,並在引號中返回每個引數
$$指令碼執行的當前程序號
$!後臺執行的最後一個程序的ID
$?顯示最後命令的退出狀態。0表示沒有錯誤,其他任何值表明有錯誤。

3.2 運運算元

原生的bash並不支援簡單的數學運算,通常要通過其它命令來實現。

3.2.1 算數運運算元

以下表格中的ab都是變數。

運算shell中格式
加法expr $a + $b
減法expr $a - $b
乘法expr $a * $b
除法expr $b / $a
取餘expr $b % $a
賦值a=$b
相等[ $a == $b ]
不相等[ $a != $b ]

需注意:

條件表法式需要放在方括號之間,並且要有空格。使用expr進行計算時需要使用反引號,為了讓讀者更容易理解,給出下列範例程式碼。
#!/bin/bash
a=10
b=20

val=`expr $a + $b`
echo "a + b : $val"

3.2.2 關係運算子

關係運算子只支援數位,不支援字串,除非字串的值是數位。

運算shell中的實現主要符號
檢測兩個數是否相等[ $a -eq $b ]-eq
檢測兩個數是否不相等[ $a -ne $b ]-ne
檢測左邊的數是否大於右邊的[ $a -gt $b ]-gt
檢測左邊的數是否小於右邊的[ $a -lt $b ]-lt
檢測左邊的數是否大於等於右邊的[ $a -ge $b ]-ge
檢測左邊的數是否小於等於右邊的[ $a -le $b ]-le

舉例程式碼如下:

#!/bin/bash
a=1
b=2

if [ $a != $b ]
then
   echo "$a != $b : a 不等於 b"
else
   echo "$a == $b: a 等於 b"

執行結果如下:

3.2.3 布林運運算元

具體如下:

運算shell中的實現主要符號
非運算[ ! false ]!
或運算[ $a -lt 20 -o $b -gt 100 ]-o
與運算[ $a -lt 20 -a $b -gt 100 ]-a

3.2.4 邏輯運運算元

具體如下:

運算shell中的實現主要符號
邏輯的 AND[[ $a -lt 100 && $b -gt 100 ]]&&
邏輯的 OR[[ $a -lt 100 || $b -gt 100 ]]||

布林運運算元和邏輯運運算元的區別:

語法上,邏輯運算需要雙括弧,布林運算只需要單大括弧功能上,邏輯運算具有特殊的短路功能,即是在AND運算中第一個表示式為false時則不執行第二個表示式,在OR運算中第一個表示式為true時不執行第二個表示式。

3.2.5 字串運運算元

下表列出了常用的字串運運算元:

運算shell中的實現主要符號
檢測兩個字串是否相等[ $a = $b ]=
檢測兩個字串是否不相等[ $a != $b ]!=
檢測字串長度是否為0[ -z $a ]-z
檢測字串長度是否不為 0[ -n “$a” ]-n
檢測字串是否為空[ $a ]$

3.2.6 檔案測試運運算元

主要用於檢測unix檔案的各種屬性:

運算shell中的實現主要符號
檢測檔案是否是塊裝置檔案[ -b $file ]-b file
檢測檔案是否是字元裝置檔案[ -c $file ]-c file
檢測檔案是否是目錄[ -d $file ]-d file
檢測檔案是否是普通檔案(既不是目錄,也不是裝置檔案)[ -f $file ] 返回 true-f file
檢測檔案是否設定了 SGID 位[ -g $file ]-g file
檢測檔案是否設定了粘著位(Sticky Bit)[ -k $file ]-k file
檢測檔案是否是有名管道[ -p $file ]-p file
檢測檔案是否設定了 SUID 位[ -u $file ]-u file
檢測檔案是否可讀[ -r $file ]-r file
檢測檔案是否可寫[ -w $file ]-w file
檢測檔案是否可執行[ -x $file ]-x file
檢測檔案是否為空(檔案大小是否大於0)[ -s $file ]-s file
檢測檔案(包括目錄)是否存在[ -e $file ]-e file

舉例如下:

#!/bin/bash
file="/home/westos/Desktop/textcpp/test.sh"
if [ -r $file ]
then
   echo "檔案可讀"
else
   echo "檔案不可讀"
fi

執行結果為:

3.2.7 運算指令

1.(( ))

我們可以直接使用雙圓括弧計算其中的內容,如((var=a+b)),該指令經常在if/while等條件判斷中需要計算時使用。

2.let

在計算表示式的時候我們可以直接使用let,如let var=a+b。

3.expr

在前面的內容中我們也提到了它,是非常常用的計算指令,使用時需要在外部增反引號

 var=`expr a+b`

4.bc計算器

bc計算器支援shell中的小數進行運算,並且可以互動式或者非互動式的使用。基本使用方式為var=$(echo "(1.1+2.1)"|bc)

5.$[]

我們可以直接使用這種方式計算中括弧中的內容,如echo $[1+2]

3.3 控制語句

和其他語句不同,shell的流傳呢個控制不可為空。接下來我們為大家介紹sehll中常用的語法。

3.3.1 if語句結構

3.3.1.1 if-fi

就類似於c中的if條件判斷,如下:

if condition
then
    command1 
    command2
    ...
    commandN 
fi

3.3.1.2 if-else-fi

程式碼如下:

if condition
then
    command1 
else
    command2
fi

若condition成立則執行command1,否則執行command2。

3.3.1.3 if else-if else

程式碼如下:

if condition1
then
    command1
elif condition2 
then 
    command2
else
    command3
fi

若condition1成立,執行command1,若condition1不成立,condition2成立執行command2,若兩個condition都不成立就執行command3。

3.3.2 迴圈結構

3.3.2.1 for迴圈

格式為:

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

以上也可以寫做一行,若變數var在列表中,則for迴圈執行一次所有命令。以以下程式碼作為測試:

#!/bin/bash
for loop in 1 2 3 4 5
do
    echo "The value is: $loop"
done

執行結果為:

3.3.2.2 while迴圈

格式如下:

while condition
do
    command
done

我們執行如下程式碼:

#!/bin/bash
int=1
while(( $int<=5 ))
do
    echo $int
    let "int++"
done

執行的最終結果為:

3.3.2.3 無限迴圈

我們可以以上兩種語句給出無限迴圈的實現,首先看一下for迴圈如何實現:

for (( ; ; ))

除此以外我們也可以使用while迴圈實現如下:

while :
do
    command
done

或者直接將while中的判斷語句置為真:

while true
do
    command
done

3.3.2.4 until迴圈

until 迴圈執行一系列命令直至條件為 true 時停止。語法格式如下:

until condition
do
    command
done

3.3.2.5 跳出迴圈

在迴圈過程中,有時候需要在未達到迴圈結束條件時強制跳出迴圈,Shell使用兩個命令來實現該功能:breakcontinue

1.break跳出迴圈

當我們需要跳出當前迴圈,或者終止死迴圈時,我們就可以使用break來跳出迴圈。接下來我們執行如下程式碼:

#!/bin/bash
var=1
while(( $var < 5 ))
do
        if(( $var>3 ))
        then
             echo "跳出迴圈"
             break
        fi
        echo "$var"
        var=`expr $var + 1`
done

執行結果為:

在該回圈中var>3時break,而是直接跳出迴圈。

2.continue跳出迴圈

continue命令與break命令類似,只有一點差別,它不會跳出所有迴圈,僅僅跳出當前迴圈。 接下來我們執行如下程式碼:

#!/bin/bash
var=1
while(( $var < 5 ))
do
        if(( $var>3 ))
        then
             echo "跳出迴圈"
             continue
        fi
        echo "$var"
        var=`expr $var + 1`
done

執行結果為:

使用continue跳出的迴圈只是當前迴圈,無法跳出整個迴圈,由於在該程式碼中我們每次執行到continue就會跳出當前迴圈,無法執行 var=expr $var + 1,所以迴圈條件一直成立,就成了死迴圈。

3.3.3 case-esac多選擇語句

case ... esac 為多選擇語句,與其他語言中的switch ... case 語句類似,是一種多分支選擇結構,每個 case 分支用右圓括號開始,用兩個分號 ;;表示 break,即執行結束,跳出整個 case … esac 語句,esac(就是 case 反過來)作為結束標記。

case 要求取值後面必須為單詞 in,每一模式必須以右括號結束。取值可以為變數或常數,匹配發現取值符合某一模式後,其間所有命令開始執行直至 ;;。

若檢測匹配時無一匹配,使用*捕獲該值,再執行後續命令。

語法格式如下:

case 值 in
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
   *)
    command1
esac

3.3.3 select-in語句

select in是shell中獨有的一種迴圈,非常適合終端的互動場景,它可以顯示出帶編號的選單,使用者出入不同編號就可以選擇不同的選單,並執行不同的功能。

語法格式如下:

select var  in seq
do
    action
done

我們執行如下程式碼:

#!/bin/bash

echo "What is your favourite OS?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
  break;
done
echo "You have selected $var"

執行結果為:

四、函數

函數其實就是將一段程式碼組合封裝在一起實現某個特定的功能或返回某個特定的值。我們在定義函數時需要先起一個函數名,在使用的時候直接呼叫函數名即可。

4.1 定義函數

shell中定義函數格式如下:

[ function ] funname [()]
{
    action;
    [return int;]
}

注意:

1.以上的[ function ]也可以省略
2.當函數沒有return時,預設返回最後一個命令的執行結果作為返回值。

4.2 函數引數

在shell中,呼叫函數時可以向其傳遞引數。在函數內部直接通過$n獲取引數的值。我們給出範例如下:

#!/bin/bash
funWithParam(){
    echo "第一個引數為 $1 !"
    echo "第十個引數為 ${10} !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

需要注意$10不能返回第十個引數,當n>10的時候,需要使用$(n)來獲取引數。

4.3 函數作用域

Shell指令碼中執行函數時並不會開啟子程序,預設在函數外部或函數內部定義和使用變數的效果相同。函數外部的變數在函數內部可以直接呼叫,反之函數內部的變數也可以在函數外部直接呼叫。但是這樣會導致變數混淆、資料可能被錯誤地修改等等問題,那麼如何解決這些問題呢?

系統為我們提供了一個local語句,該語句可以使在函數內部定義的變數僅在函數內部有效。定義時直接在變數前加local即可。

五、重定向

一個命令通常從一個叫標準輸入的地方讀取輸入,預設情況下,這恰好是你的終端。同樣,一個命令通常將其輸出寫入到標準輸出,預設情況下,這也是你的終端。

一般情況下,每個 Unix/Linux 命令執行時都會開啟三個檔案:

  1. 標準輸入檔案(stdin):stdin的檔案描述符為0,Unix程式預設從stdin讀取資料。
  2. 標準輸出檔案(stdout):stdout 的檔案描述符為1,Unix程式預設向stdout輸出資料。
  3. 標準錯誤檔案(stderr):stderr的檔案描述符為2,Unix程式會向stderr流中寫入錯誤資訊。

但有些時候我們可能需要將資料從其它檔案讀入或讀出,這就需要我們重定向。

5.1 輸入重定向

我們可以讓命令從檔案中獲取,這樣本來的命令需要從標準輸入stdin中獲取,轉換為從我們的指定檔案中獲取。這樣本來需要從鍵盤輸入的命令就會轉移到檔案讀取內容。語法如下:

command1 < file

5.2 輸出重定向

同輸入重定向很相似,輸出重定向也是將本來需要輸出標準輸出檔案stdout中轉化為我們的指定檔案中。語法如下:

command1 > file

5.3 標準錯誤檔案重定向

我們可以直接藉助標準錯誤檔案的檔案描述符來重定向stderr,語法如下:

$ command 2>file

擴充一點,如果我們想將stdout標準輸出檔案和stderr標準錯誤檔案合併重定向到一個指定檔案中,語法如下:

$ command > file 2>&1

5.4 Here Document

Here Document 是 Shell 中的一種特殊的重定向方式,用來將輸入重定向到一個互動式 Shell 指令碼或程式。它的作用是將兩個 delimiter 之間的內容(document) 作為輸入傳遞給 command。基本語法如下:

command << delimiter    documentdelimiter

注意:

結尾的delimiter 一定要頂格寫,前面不能有任何字元,後面也不能有任何字元,包括空格和 tab 縮排。開始的delimiter前後的空格會被忽略掉。

5.5 /dev/null 檔案

如果希望執行某個命令,但又不希望在螢幕上顯示輸出結果,那麼可以將輸出重定向到 /dev/null中,/dev/null 是一個特殊的檔案,寫入到它的內容都會被丟棄;如果嘗試從該檔案讀取內容,那麼什麼也讀不到。但是 /dev/null 檔案非常有用,將命令的輸出重定向到它,會起到"禁止輸出"的效果。語法如下:

$ command > /dev/null

總結

到此這篇關於Shell指令碼的超詳細講解的文章就介紹到這了,更多相關Shell指令碼講解內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com