還不懂shell腳本核心?這一篇就夠了

滌生大數據 發佈 2022-11-18T14:46:08.394019+00:00

這就是一個最簡單的shell腳本,運行了兩個shell命令,who命令先運行,輸出了當前是誰登錄了系統,而後運行了whoami,輸出的是當前有效用戶名。

前言:

本章討論編寫 shell腳本的基礎知識。在開始編寫自己的shell腳本前,你必須了解的基本概念都在這裡。

一、多個shell命令的使用

shell腳本的核心在於輸入多個命令並處理每個命令的結果,而且有時候需要將一個命令的結果傳給

另一個命令。shell可以將命令串起來,依次執行完成。要依次將兩個命令一起運行,可以 把它們放在同一行中,彼此間用分號(;)隔開來。


這就是一個最簡單的shell腳本,運行了兩個shell命令,who命令先運行,輸出了當前是誰登錄了系統,而後運行了whoami,輸出的是當前有效用戶名。使用這個方式可以運行多個命令,它們都是以此串行的。

二、構建一個shell文件

構建一個shell文件,最簡單的理解就是將類似上述的命令放在一個文本文件里,文本文件的核心開頭是:

#!/bin/bash #內容解釋: 在通常的shell腳本中,井號(#)用作注釋行。shell並不會處理shell腳本中的注釋行。而, shell腳本文件的第一行是個例外,#後面的驚嘆號會告訴shell用哪個shell來運行腳本

該行內容必須放在文本的第一行,表示使用的shell類型;本文以常用的bash為例,更多了類型的shell可以參考此文。

在第一行的內容後面,就可以寫入你要執行的shell命令了,可以都寫在一行,用分號隔開,但是一般情況下,為了美觀和更高的辨識度,我們選擇一行寫一個命令,加上一個回車符,在輸入另一個命令。好比下圖:


推薦格式:


需要說明的是,你可以在文本中用"#"來注釋你的內容,這樣這些被注釋的內容,shell就不會識別和執行了,一般我們會在腳本中寫一些說明性的描述,這時需要用到"#"。如下圖:


上述可以是一個完整的shell腳本了,可以直接保存為腳本文件test1,但是此時我們如果直接執行test1文件的話,還是不能達到效果的,會提示 command not found,這裡就需要提到shell里的PATH環境變量的概念。shell 命令的查找都是通過環境變量的。


我們可以查看當前主機的環境變量:


此時我們的test1命令並沒有生效,如果我們想要使其神效,可以採用這兩個方式:

  • 將shell腳本文件所處的目錄添加到PATH環境變量中;
  • 在提示符中用絕對或相對文件路徑來引用shell腳本文件;

經驗提示:

在centos Linux發行版中,有的會將$HOME/bin目錄添加進了PATH環境變量。它在每個用戶的HOME目錄下提供了一個存放文件的地方,shell可以在那裡查找要執行的命令;


在本文中,我們將用第二種方式將腳本文件的確切位置告訴shell。記住,為了引用當前 目錄下的文件,可以在shell中使用單點操作符,如下圖:

核心說明:

執行時我們會發現還是沒有執行成功,此時可以看到終端列印了 "Permission denied",這報錯大家一定要熟悉起來,因為在以後的工作中,我們可能會遇到很多這種報錯,遇到這個問題我們的第一反應就應該是想到,有些文件或者目錄,我們當前的用戶是沒有相關的權限導致。正如test1文件,我們當前的test1用戶是沒有執行權限的,所以我們需要做的就是使用chmod 給文件添加對應的權限。


chmod u+x test1 添加權限後:


此時腳本文件就可以正常執行了。


實戰解說:

工作中我們創建的腳本文件,一般都是用.sh 結尾的,這個是給我們電腦的使用者來識別用的,這樣我們就可以一眼識別這個文件就是一個shell 腳本文件,比如上面的test1文件,我們通常是命名為test1.sh的。而且執行這個文件的時候,我們可以有一個更簡單的方式,腳本對應的sh或bash來執行,好比上面的./test1 我們可以更換為 bash test1,會有同樣的效果。



三、終端列印消息

很多時候shell都會輸出一定的內容到終端,我們如果也想在腳本中輸出一些內容到終端顯示,告訴執行腳本的人,這個腳本在執行哪些功能,這個時候我們就需要用到echo命令來輔助。

最簡單的輸出如下:


echo 命令會將跟在它後面的字符串列印到終端屏幕。

注意,默認情況下,不需要使用引號將要顯示的文本字符串劃定出來。但有時在字符串中出現引號的話就比較麻煩了。

如下圖:


此時我們如果想要引號也輸出在終端的話,需要這樣做:echo "Let's see if this'll work"


核心總結:

echo命令可用單引號或雙引號來劃定文本字符串。如果在字符串中用到了它們,你需要在 文本中使用其中一種引號,而用另外一種來將字符串劃定起來。

此時我們就可以在腳本文件中任意位置使用echo來輸出我們打算輸出的內容了。如下圖:


常用的組合命令參數:

  • -n 不換行輸出


  • -e 處理特殊字符

\a 發出警告聲; \b 刪除前一個字符; \c 最後不加上換行符號; \f 換行但光標仍舊停留在原來的位置; \n 換行且光標移至行首; \r 光標移至行首,但不換行; \t 插入tab; \v 與\f相同; \ 插入\字符; \nnn 插入nnn(八進位)所代表的ASCII字符;


實戰解說:


在實際的使用中,我們通常也會使用echo 配合>>將內容追加到文本文件中,如下圖:


四、變量的使用

有些時候我們會需要在shell命令使用 其他數據來處理信息。這可以通過變量來實現。變量允許臨時性地將信息存儲在shell腳本中, 以便和腳本中的其他命令一起使用。

4.1 環境變量

shell維護著一組環境變量,用來記錄特定的系統信息。比如系統的名稱、登錄到系統上的用 戶名、用戶的系統ID(也稱為UID)、用戶的默認主目錄以及shell查找程序的搜索徑。可以用 set命令來顯示一份完整的當前環境變量列表。

如下圖:

在腳本中,變量的使用格式是:$變量名稱

下面是變量在腳本中的使用,可以看到


實戰解說:

如上文中的$HOME,我們一般還可以寫成${HOME} 這兩者的效果是等同的,而且需要注意的是,我們在$符號之前不能只是\,這樣變量就會失效了,另一個需要注意的是,當你的變量需要和一個字符串連用的時候,此時一定要用{}的形式,否則變量會失效,如下圖演示:


變量用{}包括起來:

4.2 用戶變量

除了環境變量,shell腳本還允許在腳本中定義和使用自己的變量。定義變量允許臨時存儲數 據並在整個腳本中使用,從而使shell腳本看起來更像一個真正的電腦程式。 用戶變量可以是任何由字母、數字或下劃線組成的文本字符串,長度不超過20個。用戶變量 區分大小寫,所以變量Var1和變量var1是不同的。這個小規矩經常讓腳本編程初學者感到頭疼。 使用等號將值賦給用戶變量。在變量、等號和值之間不能出現空格(另一個困擾初學者的用 法)。這裡有一些給用戶變量賦值的例子。

變量示例:

var1=10 var2=-57 var3=testing var4="still more testing"

shell腳本會自動決定變量值的數據類型。在腳本的整個生命周期里,shell腳本中定義的變量 會一直保持著它們的值,但在shell腳本結束時會被刪除掉。 與系統變量類似,用戶變量可通過$引用。

示例截圖:

核心解說:

變量每次被引用時,都會輸出當前賦給它的值。需要記住的是,引用一個變量值時需要使 用$符,而引用變量來對其進行賦值時則不要使用美元符。看下面的例子。

演示截圖:


如果沒有使用$符,shell會將變量名解釋成普通的文本字符串,通常這並不是你想要的結果。

4.3命令替換

shell腳本中最有用的特性之一就是可以從命令輸出中提取信息,並將其賦給變量。把輸出賦 給變量之後,就可以隨意在腳本中使用了。這個特性在處理腳本數據時尤為方便。

兩種操作方式:

  • 反引號字符(`)
  • $()

核心解說:

需要注意反引號字符,這可不是用於字符串的那個普通的單引號字符。由於在shell腳本之外很 少用到,你可能甚至都不知道在鍵盤什麼地方能找到這個字符。但你必須慢慢熟悉它,因為這是 許多shell腳本中的重要組件。提示:在美式鍵盤上,它通常和波浪線(~)位於同一鍵位。 命令替換允許你將shell命令的輸出賦給變量。儘管這看起來並不那麼重要,但它卻是腳本編 程中的一個主要組成部分。

如下示例:

用一對反引號把整個命令行命令圍起來:

day='date'

使用$()格式:

day=$(date)

shell會運行命令替換符號中的命令,並將其輸出賦給變量testing。注意,賦值等號和命令

替換字符之間沒有空格。這裡有個使用普通的shell命令輸出創建變量的例子。


實戰詳解:

命令替換會創建一個子shell來運行對應的命令。子shell(subshell)是由運行該腳本的shell 所創建出來的一個獨立的子shell(child shell)。正因如此,由該子shell所執行命令是無法使用腳本中所創建的變量的。 在命令行提示符下使用路徑./運行命令的話,也會創建出子shell;要是運行命令的時候 不加入路徑,就不會創建子shell。如果你使用的是內建的shell命令,並不會涉及子shell。 在命令行提示符下運行腳本時一定要留心!

五、重定向輸入和輸出


很多時候想要保存某個命令的輸出而不僅僅只是讓它顯示在顯示器上。bash shell提供了幾 個操作符,可以將命令的輸出重定向到另一個位置(比如文件)。重定向可以用於輸入,也可以 用於輸出,可以將文件重定向到命令輸入。

5.1 輸出重定向

最基本的重定向將命令的輸出發送到一個文件中。bash shell用大於號(>)來完成這項功能:

使用格式:

command > outputfile

之前顯示器上出現的命令輸出會被保存到指定的輸出文件中。


重定向操作符創建了一個文件1.txt(通過默認的umask設置),並將echo命令的輸出重定向

到該文件中。如果輸出文件已經存在了,重定向操作符會用新的文件數據覆蓋已有文件。

很多時候我們可能並不想覆蓋文件原有內容,而是想要將命令的輸出追加到已有文件中,比如在創建一個記錄系統上某個操作的日誌文件。在這種情況下,可以用雙大於號(>>)來追加數據。

可以看到,who命令產生的內容並沒有覆蓋1.txt中已有的內容,而是追加到文件的末尾。

5.2 輸入重定向

輸入重定向和輸出重定向正好相反。輸入重定向將文件的內容重定向到命令,而非將命令的

輸出重定向到文件。

輸入重定向符號是小於號(<):

命令格式:

command < inputfile

一個簡單的記憶方法就是:在命令行上,命令總是在左側,而重定向符號「指向」數據流動

的方向。小於號說明數據正在從輸入文件流向命令。

wc命令可以對對數據中的文本進行計數。默認情況下,它會輸出3個值:

  • 文本的行數
  • 文本的詞數
  • 文本的字節數

通過將文本文件重定向到wc命令,你立刻就可以得到文件中的行、詞和字節的計數。這個例 子說明1.txt文件有2行、11個單詞以及83位元組。

還有另外一種輸入重定向的方法,稱為內聯輸入重定向(inline input redirection)。這種方法 無需使用文件進行重定向,只需要在命令行中指定用於輸入重定向的數據就可以了。乍看一眼, 這可能有點奇怪,但有些應用會用到這種方式。

內聯輸入重定向符號是遠小於號(<<)。除了這個符號,你必須指定一個文本標記來劃分輸 入數據的開始和結尾。任何字符串都可作為文本標記,但在數據的開始和結尾文本標記必須一致。

command << EOF

data

EOF

在命令行上使用內聯輸入重定向時,shell會用PS2環境變量中定義的次提示符(參見第6章)

來提示輸入數據。下面是它的使用情況。

六、管道

通過前面的學習,我們已經知道了怎樣從文件重定向輸入,以及重定向輸出到文件。Shell 還有一種功能,就是可以將兩個或者多個命令(程序或者進程)連接到一起,把一個命令的輸出作為下一個命令的輸入,以這種方式連接的兩個或者多個命令就形成了管道 『|』(pipe)

Linux 管道使用豎線|連接多個命令,這被稱為管道符。Linux 管道的具體語法格式如下:

command1 | command2 command1 | command2 [ | commandN... ]

當在兩個命令之間設置管道時,管道符|左邊命令的輸出就變成了右邊命令的輸入。只要第一個命令向標準輸出寫入,而第二個命令是從標準輸入讀取,那麼這兩個命令就可以形成一個管道。大部分的 Linux 命令都可以用來形成管道。

核心講解:

這裡需要注意,command1 必須有正確輸出,而 command2 必須可以處理 command2 的輸出結果;而且 command2 只能處理 command1 的正確輸出結果,不能處理 command1 的錯誤信息。

使用示例:

a. 工作中常用的就是配合grep 使用,下圖表示的意思是,將cat讀取出來的文本內容發送到 grep 命令;



b. 使用管道將 cat 命令的輸出作為 less 命令的輸入,這樣就可以將 cat 命令的輸出每次按照一個屏幕的長度顯示,這對於查看長度大於一個屏幕的文件內容很有幫助。


c. 查看指定程序的進程運行狀態,並將輸出重定向到文件中。


​編輯d.統計系統中當前登錄的用戶數。

七、執行數學運算

對任何程式語言都很重要的特性是操作數字的能力。遺憾的是,對shell腳本來說,這 個處理過程會比較麻煩。在shell腳本中有兩種途徑來進行數學運算。

7.1 expr 命令

expr 是 evaluate expressions 的縮寫,譯為「表達式求值」。Shell expr 是一個功能強大,並且比較複雜的命令,它除了可以實現整數計算,還可以結合一些選項對字符串進行處理,例如計算字符串長度、字符串比較、字符串匹配、字符串提取等。

expr 對表達式的格式有幾點特殊的要求:

  • 出現在表達式中的運算符、數字、變量和小括號的左右兩邊至少要有一個空格,否則會報錯。
  • 有些特殊符號必須用反斜槓\進行轉義(屏蔽其特殊含義),比如乘號*和小括號(),如果不用\轉義,那麼 Shell 會把它們誤解為正則表達式中的符號(*對應通配符,()對應分組)。
  • 使用變量時要加$前綴。
[root@bd15-21-131-161 ~]# expr 2 +3  #錯誤:加號和 3 之前沒有空格
expr: syntax error
[root@bd15-21-131-161 ~]# expr 2 + 3    #這樣才是正確的
5
[root@bd15-21-131-161 ~]# expr 4 * 5     #錯誤:乘號沒有轉義
expr: syntax error
[root@bd15-21-131-161 ~]# expr 4 \* 5  #使用 \ 轉義後才是正確的
20
[root@bd15-21-131-161 ~]# expr ( 2 + 3 ) \* 4  #小括號也需要轉義
-bash: syntax error near unexpected token `2'
[root@bd15-21-131-161 ~]# expr \( 2 + 3 \) \* 4   #使用 \ 轉義後才是正確的
20
[root@bd15-21-131-161 ~]# n=3
[root@bd15-21-131-161 ~]# expr n + 2 
expr: non-numeric argument
[root@bd15-21-131-161 ~]# expr $n + 2    #使用變量時要加 $
5
[root@bd15-21-131-161 ~]#  m=7
[root@bd15-21-131-161 ~]# expr $m \* \( $n + 5 \)
56
 


以上是直接使用 expr 命令,計算結果會直接輸出,如果你希望將計算結果賦值給變量,那麼需要將整個表達式用反引號``(位於 Tab 鍵的上方)包圍起來,請看下面的例子。

實戰詳解:

使用 expr 進行數學計算是多麼的麻煩呀,需要注意各種細節,工作中不推薦使用。

7.2 使用方括號[ ]

bash shell為了保持跟Bourne shell的兼容而包含了expr命令,但它同樣也提供了一種更簡單的方法來執行數學表達式。在bash中,在將一個數學運算結果賦給某個變量時,可以用美元符和 方括號($[ operation ])將數學表達式圍起來.

用方括號執行shell數學運算比用expr命令方便很多。這種技術也適用於shell腳本。


需要額外注意的是bash shell數學運算符只支持整數運算。若要進行任何實際的數學計算,這是一個巨大的限制。如下圖:

八、退出腳本

迄今為止所有的示例腳本中,我們都是突然停下來的。運行完最後一條命令時,腳本就結束 了。其實還有另外一種更優雅的方法可以為腳本劃上一個句號。 shell中運行的每個命令都使用退出狀態碼(exit status)告訴shell它已經運行完畢。退出狀態 碼是一個0~255的整數值,在命令結束運行時由命令傳給shell。可以捕獲這個值並在腳本中使用。

8.1 查看退出狀態碼

Linux提供了一個專門的變量$?來保存上個已執行命令的退出狀態碼。對於需要進行檢查的命令,必須在其運行完畢後立刻查看或使用$?變量。它的值會變成由shell所執行的最後一條命令 的退出狀態碼。


如果命令成功結束,那麼它退出的狀態碼就是 0,如果是失敗的,那狀態碼就是一個非零的正數值。

無效命令會返回一個退出狀態碼127。Linux錯誤退出狀態碼沒有什麼標準可循,但有一些可

用的參考,如下圖:


退出狀態碼126表明用戶沒有執行命令的正確權限。

另一個會碰到的常見錯誤是給某個命令提供了無效參數。

8.2 exit

exit 是一個 Shell 內置命令,用來退出當前 Shell 進程,並返回一個退出狀態;使用$?可以接收這個退出狀態。 exit 命令可以接受一個整數值作為參數,代表退出狀態。如果不指定,默認狀態值是 0。 一般情況下,退出狀態為 0 表示成功,退出狀態為非 0 表示執行失敗(出錯)了。 exit 退出狀態只能是一個介於 0~255 之間的整數,其中只有 0 表示成功,其它值都表示失敗。

Shell 進程執行出錯時,可以根據退出狀態來判斷具體出現了什麼錯誤,比如打開一個文件時,我們可以指定 1 表示文件不存在,2 表示文件沒有讀取權限,3 表示文件類型不對。


可以看到,"after exit"並沒有輸出,這說明遇到 exit 命令後,test1執行就結束了。

實戰詳解:

注意,exit 表示退出當前 Shell 進程,我們必須在新進程中運行 test1,否則當前 Shell 會話(終端窗口)會被關閉,我們就無法看到輸出結果了。

關鍵字: