標籤:
我們知道電腦的硬體資源比如磁碟,IO,記憶體都是由軟體來統一管理的,這類特殊的軟體就是常說的作業系統,windows在底層的資源控制基礎上構建了自己的介面,非常適合使用,只需要到處點點就能完成我們需要的功能。這是一種控制資源的方式,同時也可以使用command的方式來操作底層的資源。liunx中最重要的部分是它的核心,核心管理著系統的資源,同時也提供給我們操作核心的介面,就是我們經常用到的shell(殼),主流的shell有以下幾種:
sh:
burne shell (sh)
burne again shell (bash)
csh:
c shell (csh)
tc shell (tcsh)
korn shell (ksh)
接觸比較多的是bash和ksh,幾種不同的shell功能大體相似,當然也各有特點。
一般給非管理使用者顯示的是$,管理員顯示的是root。linux下的分行符號(按下enter鍵)用CR表示,當遇到CR時,shell解譯器會去解釋CR前面的command line,解釋命令列是先按照IFS去分割命令列,然後對其中的元字元進行處理,比如:echo $A 其中A的值為hello,按照IFS(預設為tab,newline,space)拆解為echo和$A然後對元字元$進行解釋,取A變數的值,最後輸出hello到螢幕。
一、echo
echo命令是我們經常需要用到的輸出命令,可以查看參數的值,其中提供了幾個參數,能夠實現不同的效果,比如echo -n就是取消末尾的分行符號,可以在環境上試試輸出echo -n "a" 和echo "a"有什麼不同。如果我們想在預設再增加一個換行可能會想到echo "a\n",這是行不通的,echo預設執行的是-E參數,表示不去解釋反斜線的字元轉換,因此需要開啟\功能,需要使用-e參數,如下:echo -e "a\n"。
二、雙引號""和單引號‘‘
在shell中雙引號和單引號的作用是不同的,shell中的字元可以分為兩種,一種是一般字元,一種是元字元,比如$是一種元字元,當執行到元字元的時候shell會去做相應的動作,比如echo $A就是列印變數A的值,但是如果我們想列印“$A”那麼應該怎麼實現呢,答案就是使用單引號,echo ‘$A‘,單引號中的所有元字元都會被當做普通的字元來處理,那麼雙引號又有什麼不同呢,雙引號會關閉大部分的元字元處理,但是有少部分還是會當做元字元來解釋,比如$符號,另外還有一種關閉元字元的做法是在元字元前加/反斜線。
三、var=value和export
嚴格來說在當前shell中定義的的變數均為本地變數,只有export後才會成為環境變數,供後面的命令使用,export命令的詳細解釋以後會提到。
四、exec和source
執行shell的時候經常用到source *.sh,實際上我們執行*.sh指令碼的時候,會產生一個subshell進程來執行,並且該子進程的環境變數和父進程的環境變數不是共用的,因此假如我們定義cd /home/a/b/d命令在test.sh指令碼中,並且在/home/a下面執行test.sh,實際上目錄並不會切換到/home/a/b/d下,就是這個原因。那麼如何才能執行test.sh使得目錄切換,shell提供了兩種方式來阻止產生subshell進程執行指令碼,source和exec,這兩個命令會在當前的shell進程中完成指令碼的調用。那麼這兩種方式的調用又有什麼區別呢?看下面的一個例子:
1.sh
#!/bin/bashA=Becho "PID of 1.sh before exec/source/fork:$$"export Aecho "1.sh :\$A is $A"case $1 in exec) echo "using exec..." exec ./2.sh ;; source) echo "using source" . ./2.sh ;; *) echo "using fork" ./2.sh ;;esacecho "PID of 1.sh after exec/source/fork:$$"echo "1.sh:\$A is $A"
2.sh
#!/bin/bashecho "PID for 2.sh is: $$"echo "2.sh get \$A=$A form 1.sh"A=Cexport Aecho "2.sh:\$A is $A"
執行./1.sh fork後的結果:
PID of 1.sh before exec/source/fork:18885
1.sh :$A is B
using fork
PID for 2.sh is: 18886
2.sh get $A=B form 1.sh
2.sh:$A is C
PID of 1.sh after exec/source/fork:18885
1.sh:$A is B
執行./1.sh exec後的結果:
PID of 1.sh before exec/source/fork:20952
1.sh :$A is B
using exec...
PID for 2.sh is: 20952
2.sh get $A=B form 1.sh
2.sh:$A is C
執行./1.sh source後的結果:a
PID of 1.sh before exec/source/fork:26519
1.sh :$A is B
using source
PID for 2.sh is: 26519
2.sh get $A=B form 1.sh
2.sh:$A is C
PID of 1.sh after exec/source/fork:26519
1.sh:$A is C
下面逐一分析列印的結果:
預設的fork執行,會產生一個子進程來執行shell指令碼,其中父進程中的export效果會傳遞到子進程,但是子進程中的export效果並不會傳遞給父進程,因此export命令的效果是單向的,從父進程傳遞子進程。
exec和source執行並不會產生子進程,而是在當前進程執行指令碼,不同點是exec會終止當前的指令碼執行,當子指令碼執行完畢後,整個執行過程就算完畢了。
五、()和{}的區別
這裡要引入一個命令組的概念,就像是其他語言中的函數,()和{}的不同是()是在子shell中執行,{}是在本shell中執行,說到這裡不得不提出一個概念,函數,在shell中定義函數的方式有兩種,一種是function_name{},還有一種是function_name (){}。
六、$(())、${}和$()的區別
在bash中,$()和``都是用作命令替換,命令替換和之前的變數替換的概念有些類似,比如dir=$(pwd),首先pwd的執行結果會賦值給變數dir,${}的作用是變數替換,比如A=B;echo ${A},實際上在這裡也可以直接echo $A,但是假如寫成echo $AA,則shell是不識別的,因此${}起到一個邊界的作用,但是如果你只認為${}只是起到邊界的作用,那就太小瞧它了。
假設有變數file=/dir1/dir2/dir3/my.file.txt
我們可以用${}進行不同的變數替換得到不同的值。
${file#*/}:去掉第一個/及其左邊的所有字元,結果為dir1/dir2/dir3/my.file.txt
${file##*/}去掉最後一個/及其左邊的所有字元,結果為my.file.txt
${file%/*}去掉最後一個/及其右邊的所有字元,結果為/dir1/dir2/dir3
${file%%/*}:拿掉第一個 / 及其右邊的字串:(空值)
#是去掉左邊,%是去掉右邊,一個是最短匹配,兩個是最長相符
${file:0:5} 結果為從第0個開始向右取5個字元/dir1
因此${file:offset:length}從offset開始向右取length個字元
${file/dir/path}將第一個dir替換為path,結果為/path1/dir2/dir3/my.file.txt
${file//dir/path}將所有的dir都替換為path。
上面的是字元的替換,還可以根據當前的值是否為空白來做替換,如下:
${file-myfile.txt}如果file未定義則輸出myfile.txt
${file:-myfile.txt}如果file未定義或者為定義為空白則輸出myfile.txt
${file+myfile.txt}如果file有定義則不論是否為空白,都輸出myfile.txt
${file:+myfile.txt}如果file不為空白,則輸出myfile.txt
${file=myfile.txt}如果file未定義則輸出myfille.txt同時賦值file為myfile.txt
${file:=myfile.txt}如果file未定義或者為空白,則輸出myfile.txt
${#file}輸出file變數的長度
順便再來介紹一下bash中的數組array
數組的定義類似:A=(a b c d e)中間用空格分割
如果要得到這個數組的內容,可以使用${A[@]}或者${A[*]}
最後再來看下$(())的用途,$(())方便我們做整數運算,比如a=1;b=2;echo $((a+b))輸出為3。$(())為我們做比較和運算提供了類C語言的風格。
七、[email protected]和$*的差別
比如我們有個sh指令碼名稱為test.sh,它可以接受參數,如下 test.sh a b c,獲得外部參數的方式為$1 $2 $3分別擷取a b c,如果要擷取指令碼身的名稱則使用$0。注意指令碼中定義的函數也可以接收參數同樣可以使用$1...這種方式擷取傳入的參數。不同的時$0不是函數的名稱而是最外面指令碼的名稱。
$10表示的並不是擷取第十個參數,而是擷取第一個參數後面再加上字串0,因此如果要擷取第十個參數可以使用${10},或者使用shift命令,shift 1為取消第一個參數($0)排除,shift 5為取消前5個參數。
再介紹一個參數$#表示擷取傳入的參數總數,比如test.sh one two three 則$#為3。
如果要擷取所有的參數則可以使用[email protected],比如test.sh one two three則擷取到三個word,而$*是將參數當做一個整體。
八、&&和||的差別
在介紹$$和||之前先來瞭解一個新的概念,return value,在執行command或者function的時候都會傳值會父進程,我們可以使用$?來擷取這個最新的返回值,return value只有兩種狀態,0的話為真,非0的話為假,比如:
假設myfile.txt存在,而a.txt不存在,ls a.txt後再echo $?為非0,如果ls myfile.txt則為0。可以使用test expersion或者[ expersion ]來測試返回值。不過我更習慣使用[]這種文法。
接下來就可以看看$$和||的差別了,command1 && command2表示如果command1命令返回真則執行command2。command1 || command2表示,如果command1返回假則執行command2。
九、<和>
大概存在三種FD(檔案描述符),分別為0標準輸入,1標準輸出,2錯誤輸出,>表示標準輸出重新導向與1>相同。
2>&1表示把標準錯誤輸出重新導向到標準輸出。
&>file表示把標準輸出和標準錯誤輸出重新導向到檔案file中。
2>&-表示將標準錯誤輸出關閉
>/dev/null 2>&1表示將標準錯誤輸出綁定到標準輸入並且標準輸入重新導向到/dev/null,也即什麼都不會輸出
十、case和if
有時候需要根據當前的值來執行不同的邏輯,if在這裡就派上了用場,標準的if用法為:
if comd1;then
comd2
elif comd3;then
comd4
else
comd5
fi
如果then後面不想跑任何命令可以使用:這個null command來替代。
如果條件比較多並且為字串則可以使用另外一種文法,case
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
exit 1
esac
十一、for、while與until
loop是非常基本的一種邏輯控制,所有的語言都提供了自己的迴圈控制語句
for loop從一個清單列表中讀取值,依次做處理:
for var in one two three four five
do
...
done
下面給出一個累計變數的語句:
for ((i=1;i<=10;i++))
do
...
done
如果改為while實現則如下:
num=1
while [ $num -le 10 ];do
...
done
-le表示小於等於
while是在條件為true時進入迴圈,until則相反,是在條件為false時進入迴圈。
同樣迴圈中也有break和continue關鍵字,和java等其他語言的用法是一樣的,就不做解釋了。
上面就是shell十三問的大體內容,有些內容並沒有涉及到,後續會對一些比較常用的命令比如grep,sed,awk等進行簡單的講解。
shell十三問總結