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版本:
想要深入瞭解shell歷史的小夥伴可以點選這裡
Part2:Shell執行指令碼
1. shell指令碼
1.編輯
可以把這個過程當做程式語言中的寫程式碼過程。
先來看一個shell指令碼例項,vim test.sh,此時test.sh就是一個shell的執行指令碼:
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這個程式:
通常情況下,exec系統調⽤把⼦程序的程式碼段替換成./test.sh程式的程式碼段,並從它的_start開始執⾏。 然⽽test.sh是個⽂本⽂件,根本沒有程式碼段和_start函式,怎麼辦呢?
其實exec還有另外⼀種機制,如果要執⾏的是⼀個⽂本⽂件,並且第⼀⾏指定了直譯器(/bin/bash),則⽤直譯器程式的程式碼段替換當前程序,並且從直譯器的_start開始執⾏。⽽這個⽂本⽂件被當作命令⾏引數傳給直譯器。因此,執⾏上述指令碼相當於執⾏程式 。
NO.2:/bin/bash test.sh:直譯器(命令)+引數(指令碼檔案)
這種方式通過直接指定直譯器來執行指令碼檔案:
還可以這樣指明直譯器:
其實指令碼語言的執行都是由直譯器完成的,先載入直譯器,再由直譯器執行。有了這個概念,我們也可以執行Python、PHP等執行指令碼,只要你有它的直譯器。
2. shell執行過程
我們來深入理解一下shell指令碼的執行過程:
- 首先由命令列的互動Shell(bash)fork/exec⼀個⼦Shell(sh)⽤於執⾏指令碼,⽗程序bash等待⼦程序sh終⽌。
- sh讀取指令碼中的cd ..命令,調⽤相應的函式執⾏內建命令,改變當前⼯作⽬錄為上⼀級
⽬錄。 - sh讀取指令碼中的ls命令,fork/exec這個程式,列出當前⼯作⽬錄下的⽂件,sh等待ls終⽌。
- ls終⽌後,sh繼續執⾏,讀到指令碼⽂件末尾,sh終⽌。
- sh終⽌後,bash繼續執⾏,列印提⽰符等待⽤戶輸⼊。
Expand:內建命令
內建命令:一旦執行當前程序不會建立子程序,只會影響父程序(互動bash),本質:shell直譯器的一個函式
先看一個例子:
在上例中,如果將命令⾏下輸⼊的命令⽤ () 括號括起來,那麼命令列的互動bash就會fork出⼀個⼦Shell執⾏⼩括號中的命令。此時改變的是⼦Shell的PWD, ⽽不會影響到互動式Shell。
然⽽去掉則有不同的效果,cd ..命令是直接在互動式Shell下執⾏的,改變互動式Shell的PWD。
同樣,如果用下面的⽅式執⾏Shell指令碼:
這種⽅式也不會建立⼦Shell,⽽是直接在互動式Shell下逐行執⾏指令碼中的命令。
所以,source或者 . 命令是Shell的內建命令。
linux中像這樣的命令有很多,比如pwd,cd ..等。有興趣的小夥伴可以自己搜尋學習。
Part3:Shell變數
Shell變數由字母加下劃線組成,在變數名前加$符號,就可以取到它的值。
和C語⾔不同的是:
Shell變數不需要明確定義型別,事實上Shell變數的值都是字串,⽐如我們定義VAR=45,其實VAR的值是字串45⽽⾮整數。Shell變數不需要先定義後使⽤,如果對⼀個沒有定義的變數取值,則值為空字串。
注意,給變數賦值時=兩邊不能加空格,因為在shell中,只有命令才能帶空格,有的小夥伴C/C++寫多了就容易習慣性的加空格。這就坑了。
有兩種型別的Shell變數:
環境變數
環境變數可以從⽗程序傳給⼦程序,因此Shell程序的環境變數 可以從當前Shell程序傳給fork出來的⼦程序。⽤printenv命令可以顯⽰當前Shell程序的環境變數。
本地變數
只存在於當前Shell程序,⽤set命令可以顯⽰當前Shell程序中定義的所有變數(包括本地變數和環境變數)和函式。
環境變數和普通(本地)變數之間的區別:
環境變數具有全域性特性,可以被任何bash的子程序繼承,普通變數只能在當前的bash有效
此外,變數還可以拼接列印:
Part4:Shell特殊字元
1. 檔名代換:? * []
⽂件名代換 (Globbing )就是利用一些萬用字元(Wildcard)來匹配檔名中的字元,有以下3種:
萬用字元 | 含義 |
---|---|
* | 匹配0個或多個任意字元 |
? | 匹配⼀個任意字元 |
[] | 匹配⽅括號中任意⼀個字元的⼀次出現 |
具體用法見下例:
先touch100個檔案用於測試:
萬用字元?:
萬用字元*:
萬用字元[]:
混合使用:
最後刪掉它們:
2. 轉義字元 \
和C語⾔類似,\ 在Shell中被⽤作轉義字元,⽤於去除緊跟其後的單個字元的特殊意義(回車除外),換句話說。緊跟其後的字元取字⾯值。
比如建立⼀個具有特殊⽂件名的⽂件:
還有一個字元雖然不具有特殊含義,但是要⽤它做⽂件名也很⿇煩,就是 - 號。
如果要建立⼀個檔名以-號開頭的⽂件,這樣是不⾏的:
因為各種linux命令都把 **-** 號開頭的命令⾏引數當作命令的選項,⽽不會當作⽂件名。
如果⾮要處理以-號開頭的⽂件名,可以有兩種辦法:
\ 還有⼀種⽤法,在\後敲回車表⽰續⾏,Shell並不會⽴刻執⾏命令,⽽是把游標移到下⼀⾏,給出一個續⾏提⽰符>,等待⽤戶繼續輸⼊,最後把所有的續⾏接到⼀起當作⼀個命令執⾏。例如:
3. 命令代換:“ $()
用於命令代換的“和$()符號本身也是一條命令,Shell先執⾏該命令,然後將輸出結果⽴刻代換到當前命令⾏中。
注意:這裡的反引號和單引號是不同的:
反引號在鍵盤左上角ESC鍵的正下方,單引號則在enter鍵的左邊。下面是使用例項:
我們可以看到,這兩者都能達到相同的效果,此外,$()還能進行算術運算,但只能做一些+-*/的整數運算。
俗話說:“一山不容二虎”,他們必然是有區別的。區別就在這裡:
可以看到,$符號本來是取變數的值,加上轉義字元\後應該失去效用才對,但為什麼反引號還是輸出了變數的值呢?
這是因為反引號和$()對轉義字元的使用方式不同導致的。
反引號齊本身就對/進行了轉義,保留了其本身意思,如果我們想在反引號中起到/的特殊意義,我們必須使用2個/來進行表示。所以我們可以簡單的想象成反引號中: // = /
這樣,就不會出錯啦。
所以,雖然可以用這兩種方式進行命令代換,但還是建議使用$( ),理由如下:
1、反引號很容易與單引號搞混亂,尤其對初學者來說。
2、在轉義字元\的使用上,它比較直觀。
3、它的弊端是,並不是所有的Linux系統都支援這種方式,但反引號是肯定支援的。
4、大神們都不用反引號。
Expand:eval命令
說道命令代換,就不得不提eval命令了,因為人家可是行業大神。它可以用於複雜變數的命令替換。具體看下例:
可以看到,與echo相比,eval對變數txt進行了二次替換,從而打印出了檔案內容。
上例更加說明了eval的功能,先用轉義字元\把設為無效,再與$#的值4拼接為引數4,最後進行二次替換打印出引數的值。
eval既然可以對複雜變數進行操作,那麼簡單變數也自然不在話下:
4. 單引號 ”,雙引號” “
和C語⾔不⼀樣,Shell指令碼中的單引號和雙引號⼀樣都是字串的界定符,⽽不是字元的界定符。
單引號‘’ 雙引號”“都可以用來表示字串,但單引號對其內部的所有內容不做解釋,直接作為字串。而雙引號對內部命令做出替換和響應。
The End
到這裡,相信大家對shell指令碼已經有了初步瞭解了,當然,shell指令碼的知識還有很多。比如它的語法,也是比較容易上手的。
點選這裡,進一步學習shell的基礎語法。