天天看點

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

作者:滌生大資料

前言:

本章讨論編寫 shell腳本的基礎知識。在開始編寫自己的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腳本核心?這一篇就夠了

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

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

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

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

我們可以檢視目前主機的環境變量:

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

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

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

經驗提示:

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

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

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

核心說明:

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

chmod u+x test1 添權重限後:

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

此時腳本檔案就可以正常執行了。

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

實戰解說:

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

三、終端列印消息

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

最簡單的輸出如下:

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

echo 指令會将跟在它後面的字元串列印到終端螢幕。

注意,預設情況下,不需要使用引号将要顯示的文本字元串劃定出來。但有時在字元串中出現引号的話就比較麻煩了。

如下圖:

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

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

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

核心總結:

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

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

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

常用的組合指令參數:

  • -n 不換行輸出
還不懂shell腳本核心?這一篇就夠了

  • -e 處理特殊字元
\a 發出警告聲; \b 删除前一個字元; \c 最後不加上換行符号; \f 換行但光标仍舊停留在原來的位置; \n 換行且光标移至行首; \r 光标移至行首,但不換行; \t 插入tab; \v 與\f相同; \ 插入\字元; \nnn 插入nnn(八進制)所代表的ASCII字元;
還不懂shell腳本核心?這一篇就夠了

實戰解說:

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

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

四、變量的使用

有些時候我們會需要在shell指令使用 其他資料來處理資訊。這可以通過變量來實作。變量允許臨時性地将資訊存儲在shell腳本中, 以便和腳本中的其他指令一起使用。

4.1 環境變量

shell維護着一組環境變量,用來記錄特定的系統資訊。比如系統的名稱、登入到系統上的用 戶名、使用者的系統ID(也稱為UID)、使用者的預設主目錄以及shell查找程式的搜尋徑。可以用 set指令來顯示一份完整的目前環境變量清單。

如下圖:

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

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

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

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

實戰解說:

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

變量用{}包括起來:

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

4.2 使用者變量

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

變量示例:

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

shell腳本會自動決定變量值的資料類型。在腳本的整個生命周期裡,shell腳本中定義的變量 會一直保持着它們的值,但在shell腳本結束時會被删除掉。 與系統變量類似,使用者變量可通過$引用。

示例截圖:

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

核心解說:

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

示範截圖:

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

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

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

4.3指令替換

shell腳本中最有用的特性之一就是可以從指令輸出中提取資訊,并将其賦給變量。把輸出賦 給變量之後,就可以随意在腳本中使用了。這個特性在處理腳本資料時尤為友善。

兩種操作方式:

  • 反引号字元(`)
  • $()

核心解說:

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

如下示例:

用一對反引号把整個指令行指令圍起來:

day='date'

使用$()格式:

day=$(date)

shell會運作指令替換符号中的指令,并将其輸出賦給變量testing。注意,指派等号和指令

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

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

實戰詳解:

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

五、重定向輸入和輸出

很多時候想要儲存某個指令的輸出而不僅僅隻是讓它顯示在顯示器上。bash shell提供了幾 個操作符,可以将指令的輸出重定向到另一個位置(比如檔案)。重定向可以用于輸入,也可以 用于輸出,可以将檔案重定向到指令輸入。

5.1 輸出重定向

最基本的重定向将指令的輸出發送到一個檔案中。bash shell用大于号(>)來完成這項功能:

使用格式:

command > outputfile

之前顯示器上出現的指令輸出會被儲存到指定的輸出檔案中。

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

重定向操作符建立了一個檔案1.txt(通過預設的umask設定),并将echo指令的輸出重定向

到該檔案中。如果輸出檔案已經存在了,重定向操作符會用新的檔案資料覆寫已有檔案。

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

很多時候我們可能并不想覆寫檔案原有内容,而是想要将指令的輸出追加到已有檔案中,比如在建立一個記錄系統上某個操作的日志檔案。在這種情況下,可以用雙大于号(>>)來追加資料。

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

可以看到,who指令産生的内容并沒有覆寫1.txt中已有的内容,而是追加到檔案的末尾。

5.2 輸入重定向

輸入重定向和輸出重定向正好相反。輸入重定向将檔案的内容重定向到指令,而非将指令的

輸出重定向到檔案。

輸入重定向符号是小于号(<):

指令格式:

command < inputfile

一個簡單的記憶方法就是:在指令行上,指令總是在左側,而重定向符号“指向”資料流動

的方向。小于号說明資料正在從輸入檔案流向指令。

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

wc指令可以對對資料中的文本進行計數。預設情況下,它會輸出3個值:

  • 文本的行數
  • 文本的詞數
  • 文本的位元組數

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

還有另外一種輸入重定向的方法,稱為内聯輸入重定向(inline input redirection)。這種方法 無需使用檔案進行重定向,隻需要在指令行中指定用于輸入重定向的資料就可以了。乍看一眼, 這可能有點奇怪,但有些應用會用到這種方式。

内聯輸入重定向符号是遠小于号(<<)。除了這個符号,你必須指定一個文本标記來劃分輸 入資料的開始和結尾。任何字元串都可作為文本标記,但在資料的開始和結尾文本标記必須一緻。

command << EOF

data

EOF

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

來提示輸入資料。下面是它的使用情況。

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

六、管道

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

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

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

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

核心講解:

這裡需要注意,command1 必須有正确輸出,而 command2 必須可以處理 command2 的輸出結果;而且 command2 隻能處理 command1 的正确輸出結果,不能處理 command1 的錯誤資訊。

使用示例:

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

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

b. 使用管道将 cat 指令的輸出作為 less 指令的輸入,這樣就可以将 cat 指令的輸出每次按照一個螢幕的長度顯示,這對于檢視長度大于一個螢幕的檔案内容很有幫助。

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

c. 檢視指定程式的程序運作狀态,并将輸出重定向到檔案中。

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

​編輯d.統計系統中目前登入的使用者數。

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

七、執行數學運算

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

7.1 expr 指令

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

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

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 鍵的上方)包圍起來,請看下面的例子。

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

實戰詳解:

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

7.2 使用方括号[ ]

bash shell為了保持跟Bourne shell的相容而包含了expr指令,但它同樣也提供了一種更簡單的方法來執行數學表達式。在bash中,在将一個數學運算結果賦給某個變量時,可以用美元符和 方括号($[ operation ])将數學表達式圍起來.
還不懂shell腳本核心?這一篇就夠了

用方括号執行shell數學運算比用expr指令友善很多。這種技術也适用于shell腳本。

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

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

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

八、退出腳本

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

8.1 檢視退出狀态碼

Linux提供了一個專門的變量$?來儲存上個已執行指令的退出狀态碼。對于需要進行檢查的指令,必須在其運作完畢後立刻檢視或使用$?變量。它的值會變成由shell所執行的最後一條指令 的退出狀态碼。
還不懂shell腳本核心?這一篇就夠了

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

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

無效指令會傳回一個退出狀态碼127。Linux錯誤退出狀态碼沒有什麼标準可循,但有一些可

用的參考,如下圖:

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

退出狀态碼126表明使用者沒有執行指令的正确權限。

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

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

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

8.2 exit

exit 是一個 Shell 内置指令,用來退出目前 Shell 程序,并傳回一個退出狀态;使用$?可以接收這個退出狀态。 exit 指令可以接受一個整數值作為參數,代表退出狀态。如果不指定,預設狀态值是 0。 一般情況下,退出狀态為 0 表示成功,退出狀态為非 0 表示執行失敗(出錯)了。 exit 退出狀态隻能是一個介于 0~255 之間的整數,其中隻有 0 表示成功,其它值都表示失敗。

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

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

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

實戰詳解:

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