1. 程式人生 > >shell指令碼初探——概念篇

shell指令碼初探——概念篇

ForeWord

本文介紹了shell指令碼的一些基礎知識。

Key Point:

  • Shell概念及發展歷史
  • shell執行指令碼
  • shell變數
  • Shell特殊字元使用(` $() [] [[]]等)

         tips:全文閱讀需8min
    

Part1:Concept&History

1. Concept

1.Shell概念

我們知道,通常計算機程式要經過編譯和連結成為計算機可解讀的格式,然後才能執行。如:C/C++,java,它們叫做程式語言

但還有一種未經編譯就可執行的程式,通常稱之為指令碼程式(script)。如:shell/Python/Javascript

 所以, shell是一門弱型別解釋型語言.

這裡的弱型別是指沒有型別劃分,如:int double char 等。
解釋型是不需要編譯,只需要直譯器解釋執行。

2.shell的兩種執行模式

shell有兩種執行模式:互動式&批量式

互動式

Shell的作⽤是解釋執⾏⽤戶的命令。

比如⽤戶在命令列輸⼊⼀條命令,Shell就解釋執⾏⼀條,這種⽅式稱為互動式(Interactive),這種模式我們無時不刻在使用。

批量式

Shell還有⼀種執⾏命令的⽅式稱為批量式(Batch)。

⽤戶事先寫⼀ 個Shell指令碼(Script)其中有很多條命令,讓Shell⼀次把這些命令執⾏完,⽽不必⼀條⼀條地敲命令。

    Shell指令碼和程式設計語⾔很相似,也有變數和流程控制語句。但Shell指令碼是解釋執⾏的,不需要編譯。Shell程式從指令碼中⼀⾏⼀⾏讀取並執⾏這些命令,相當於⼀個⽤戶把指令碼中的命令⼀⾏⼀ ⾏敲到Shell提⽰符下執⾏。

2. History

由於歷史原因,linux系統上有很多種Shell:

⽂件/etc/shells給出了系統中所有已知(不⼀定已安裝)的Shell:
/bin/sh
/usr/bin/es
/usr/bin/ksh
/bin/ksh
/usr/bin/rc
/usr/bin/tcsh
/bin/tcsh
/usr/bin/esh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screen

雖然有這麼多種shell,但當前使用最廣泛的當屬安裝在/bin目錄下的bash。當然,你可以用以下命令檢視和更改系統預設的shell版本:

1657

想要深入瞭解shell歷史的小夥伴可以點選這裡

Part2:Shell執行指令碼

1. shell指令碼

1.編輯

可以把這個過程當做程式語言中的寫程式碼過程。

先來看一個shell指令碼例項,vim test.sh,此時test.sh就是一個shell的執行指令碼:

43157

1.由於shell有多種版本,且其語法會有差異。所以嚴謹的sh檔案會在首以“#!/bin/bash”等語句指明shell指令碼執行時所依賴的直譯器版本。
2.Shell指令碼中⽤#表⽰註釋,相當於C語⾔的//註釋。但如果#位於第⼀⾏開頭則例外,因為它表⽰該指令碼使⽤後⾯指定的直譯器/bin/sh解釋執⾏。

2.執行

由於shell是一門非編譯型語言,所以編輯完內容後,就可以直接由直譯器執行了。shell指令碼有這麼幾種執行方式:

NO.1: chmod u+x test.sh

先把這個指令碼⽂件加上可執⾏許可權然後Shell會fork⼀個⼦程序並調⽤exec執⾏./test.sh這個程式:

1534

通常情況下,exec系統調⽤把⼦程序的程式碼段替換成./test.sh程式的程式碼段,並從它的_start開始執⾏。 然⽽test.sh是個⽂本⽂件,根本沒有程式碼段和_start函式,怎麼辦呢?

     其實exec還有另外⼀種機制,如果要執⾏的是⼀個⽂本⽂件,並且第⼀⾏指定了直譯器(/bin/bash),則⽤直譯器程式的程式碼段替換當前程序,並且從直譯器的_start開始執⾏。⽽這個⽂本⽂件被當作命令⾏引數傳給直譯器。因此,執⾏上述指令碼相當於執⾏程式 。

NO.2:/bin/bash test.sh:直譯器(命令)+引數(指令碼檔案)

這種方式通過直接指定直譯器來執行指令碼檔案:

14

還可以這樣指明直譯器:

15

16

     其實指令碼語言的執行都是由直譯器完成的,先載入直譯器,再由直譯器執行。有了這個概念,我們也可以執行Python、PHP等執行指令碼,只要你有它的直譯器。

2. shell執行過程

我們來深入理解一下shell指令碼的執行過程:

16357

  1. 首先由命令列的互動Shell(bash)fork/exec⼀個⼦Shell(sh)⽤於執⾏指令碼,⽗程序bash等待⼦程序sh終⽌。
  2. sh讀取指令碼中的cd ..命令,調⽤相應的函式執⾏內建命令,改變當前⼯作⽬錄為上⼀級
    ⽬錄。
  3. sh讀取指令碼中的ls命令,fork/exec這個程式,列出當前⼯作⽬錄下的⽂件,sh等待ls終⽌。
  4. ls終⽌後,sh繼續執⾏,讀到指令碼⽂件末尾,sh終⽌。
  5. sh終⽌後,bash繼續執⾏,列印提⽰符等待⽤戶輸⼊。

Expand:內建命令

內建命令:一旦執行當前程序不會建立子程序,只會影響父程序(互動bash),本質:shell直譯器的一個函式

先看一個例子:

12

在上例中,如果將命令⾏下輸⼊的命令⽤ () 括號括起來,那麼命令列的互動bash就會fork出⼀個⼦Shell執⾏⼩括號中的命令。此時改變的是⼦Shell的PWD, ⽽不會影響到互動式Shell。

然⽽去掉則有不同的效果,cd ..命令是直接在互動式Shell下執⾏的,改變互動式Shell的PWD。

同樣,如果用下面的⽅式執⾏Shell指令碼:

3

這種⽅式也不會建立⼦Shell,⽽是直接在互動式Shell下逐行執⾏指令碼中的命令。

所以,source或者 . 命令是Shell的內建命令。

linux中像這樣的命令有很多,比如pwd,cd ..等。有興趣的小夥伴可以自己搜尋學習。

Part3:Shell變數

Shell變數由字母加下劃線組成,在變數名前加$符號,就可以取到它的值。

和C語⾔不同的是:

   Shell變數不需要明確定義型別,事實上Shell變數的值都是字串,⽐如我們定義VAR=45,其實VAR的值是字串45⽽⾮整數。Shell變數不需要先定義後使⽤,如果對⼀個沒有定義的變數取值,則值為空字串。 

145641

注意,給變數賦值時=兩邊不能加空格,因為在shell中,只有命令才能帶空格,有的小夥伴C/C++寫多了就容易習慣性的加空格。這就坑了。

有兩種型別的Shell變數:

環境變數

    環境變數可以從⽗程序傳給⼦程序,因此Shell程序的環境變數 可以從當前Shell程序傳給fork出來的⼦程序。⽤printenv命令可以顯⽰當前Shell程序的環境變數。

本地變數

    只存在於當前Shell程序,⽤set命令可以顯⽰當前Shell程序中定義的所有變數(包括本地變數和環境變數)和函式。

環境變數和普通(本地)變數之間的區別

    環境變數具有全域性特性,可以被任何bash的子程序繼承,普通變數只能在當前的bash有效

此外,變數還可以拼接列印:

157

Part4:Shell特殊字元

1. 檔名代換:? * []

⽂件名代換 (Globbing )就是利用一些萬用字元(Wildcard)來匹配檔名中的字元,有以下3種:

萬用字元 含義
* 匹配0個或多個任意字元
? 匹配⼀個任意字元
[] 匹配⽅括號中任意⼀個字元的⼀次出現

具體用法見下例:

先touch100個檔案用於測試:

3

萬用字元?:

?

萬用字元*:
4

萬用字元[]:
167

混合使用:

2468

最後刪掉它們:

754

2. 轉義字元 \

和C語⾔類似,\ 在Shell中被⽤作轉義字元,⽤於去除緊跟其後的單個字元的特殊意義(回車除外),換句話說。緊跟其後的字元取字⾯值。

 比如建立⼀個具有特殊⽂件名的⽂件:

167

還有一個字元雖然不具有特殊含義,但是要⽤它做⽂件名也很⿇煩,就是 - 號。

如果要建立⼀個檔名以-號開頭的⽂件,這樣是不⾏的:

51364

   因為各種linux命令都把 **-** 號開頭的命令⾏引數當作命令的選項,⽽不會當作⽂件名。

如果⾮要處理以-號開頭的⽂件名,可以有兩種辦法:

131

11

\ 還有⼀種⽤法,在\後敲回車表⽰續⾏,Shell並不會⽴刻執⾏命令,⽽是把游標移到下⼀⾏,給出一個續⾏提⽰符>,等待⽤戶繼續輸⼊,最後把所有的續⾏接到⼀起當作⼀個命令執⾏。例如:

13

3. 命令代換:“ $()

用於命令代換的$()符號本身也是一條命令,Shell先執⾏該命令,然後將輸出結果⽴刻代換到當前命令⾏中。

注意:這裡的反引號和單引號是不同的:

21

反引號在鍵盤左上角ESC鍵的正下方,單引號則在enter鍵的左邊。下面是使用例項:

3145

我們可以看到,這兩者都能達到相同的效果,此外,$()還能進行算術運算,但只能做一些+-*/的整數運算。

俗話說:“一山不容二虎”,他們必然是有區別的。區別就在這裡:

40

可以看到,$符號本來是取變數的值,加上轉義字元\後應該失去效用才對,但為什麼反引號還是輸出了變數的值呢?

這是因為反引號和$()對轉義字元的使用方式不同導致的。

    反引號齊本身就對/進行了轉義,保留了其本身意思,如果我們想在反引號中起到/的特殊意義,我們必須使用2個/來進行表示。所以我們可以簡單的想象成反引號中: // = /

50

這樣,就不會出錯啦。

所以,雖然可以用這兩種方式進行命令代換,但還是建議使用$( ),理由如下:

1、反引號很容易與單引號搞混亂,尤其對初學者來說。
2、在轉義字元\的使用上,它比較直觀。
3、它的弊端是,並不是所有的Linux系統都支援這種方式,但反引號是肯定支援的。
4、大神們都不用反引號。

Expand:eval命令

說道命令代換,就不得不提eval命令了,因為人家可是行業大神。它可以用於複雜變數的命令替換。具體看下例:

60

可以看到,與echo相比,eval對變數txt進行了二次替換,從而打印出了檔案內容。

70

上例更加說明了eval的功能,先用轉義字元\把設為無效,再與$#的值4拼接為引數4,最後進行二次替換打印出引數的值。

eval既然可以對複雜變數進行操作,那麼簡單變數也自然不在話下:
80

4. 單引號 ”,雙引號” “

和C語⾔不⼀樣,Shell指令碼中的單引號和雙引號⼀樣都是字串的界定符,⽽不是字元的界定符。

單引號‘’ 雙引號”“都可以用來表示字串,但單引號對其內部的所有內容不做解釋,直接作為字串。而雙引號對內部命令做出替換和響應。

13514

The End

到這裡,相信大家對shell指令碼已經有了初步瞭解了,當然,shell指令碼的知識還有很多。比如它的語法,也是比較容易上手的。

點選這裡,進一步學習shell的基礎語法。