標籤:
這篇是shell的命令與命令的執行,文章中的命令在這裡下載命令命令的執行,上一篇是shell中的變數,條件判斷,控制結構和函數,上上篇介紹了什麼是shell,管道和重新導向,作為程式設計語言的shell。
2.6.5 命令在shell指令碼程式內部可以執行兩類命令。一類是可以在命令提示字元中執行的"普通"命令,也成為外部命令,一類是"內建"命令,也成為內部命令。內建命令是在shell內部實現的,它們不能作為外部程式被調用。然後,大多數的內部命令同時也提供了獨立啟動並執行程式版本,這是POSIX規範的一部分。
1.break命令在控制條件未滿足之前,跳出for,while或者until迴圈。預設情況下,break只跳出一層迴圈。
2.:命令冒號:命令是一個空命令,它偶爾會被用於簡化條件邏輯,相當於true的一個別名。由於它是內建命令,所以啟動並執行比true快,但它的輸出可讀性較差。例如可以使用while :實現一個無限迴圈,代替更常見的while true。
3.continue命令這個命令使for、while或until迴圈跳到下一次迴圈繼續執行,迴圈變數取迴圈列表中的下一個值。
4..命令點
.命令用於當前shell中執行命令:
../shell_script
通常,當一個指令碼執行一條外部命令或指令碼程式時,它會建立一個新的環境(一個子shell),命令將在這個新環境中執行,在命令執行完畢後,這個環境將被丟棄,留下退出碼返回給父shell。但外部的source命令和點命令(這兩個命令是同義字)在執行指令碼程式中列出的命令時,使用的是調用該指令碼程式的同一個shell。
因為在預設情況下,shell指令碼程式會在一個新建立的環境中執行,所以指令碼程式對環境變數所作的任何修改都會丟失。而點命令允許執行的指令碼程式改變當前環境。如果要把一個指令碼當做“包裹器”來為後續執行的一些其他命令設定環境時,這個命令通常就很有用。
在shell指令碼程式中,點命令的作用有點類似與C或者C++中的#include指令,它在當前上下文中執行命令,可以使用它將變數和函數定義結合進指令碼程式。
5.echo命令雖然,X/Open建議在現在shell中使用printf命令,但是還是經常使用echo命令來輸出結尾帶有分行符號的字串。
一個常見的問題是如何
去掉分行符號,linux常用的解決方式為:
echo -n "string to output"
6.eval命令
eval命令允許對參數進行求值,它是shell的內建命令,通常不會以單獨命令的形式存在。
foo=10
x=foo
y=‘$‘$x
echo $y
foo=10
x=foo
eval y=‘$‘$x
echo $y
前一個結果輸出$foo,後一個結果輸出10。
eval命令有點像額外的$,它給出一個變數的值的值。
有點類似指標? $類似於*,取出地址所指向的內容。
7.exec命令exec命令有兩種不同的用法,它的典型用法是將當前shell替換為一個不同的程式。例如:
exec wall "Thanks for all the fish"
指令碼中的這個命令會用wall命令替換當前的shell,指令碼程式中exec命令後面的代碼都不會執行,因為執行這個指令碼的shell已經不存在了。
8.exit n命令exit命令使指令碼程式以退出碼n結束運行。如果在任何一個互動式shell的命令提示字元中使用這個命令,它會使退出系統。如果允許指令碼程式退出時不指定一個退出狀態,那麼該指令碼中最後一條被執行命令的狀態將被用作返回值。在指令碼程式中提供一個退出碼總是一個良好的習慣。
在shell指令碼編程中,退出碼0表示成功。
下面是一個簡單的例子,如果目前的目錄下存在一個名為.profile的檔案,它返回0表示成功,返回1表示失敗:
#!/bin/sh
if [ -f *.profile ]
then
exit 0
fi
exit 1
如果追求更簡潔的版本,則為:
[ -f *.profile ] && exit 0 || exit 1
9.export命令
export命令將作為它參數的變數匯出到子shell中,並使之在子shell中有效。在
預設情況下,在一個
shell中被建立的變數在這個shell調用的下級shell中是不可用的。export命令把自己的參數建立為一個環境變數,而這個環境變數可以被當前程式調用的其他指令碼和程式看見。從更技術的角度來說,
被匯出的變數構成從該shell衍生的任何子進程的環境變數。
一旦一個變數被shell匯出,它就可以被該shell調用的任何指令碼使用,也可以被後續依次調用的任何shell使用。
10.expr命令expr命令將它的參數當作一個運算式來求值。它最常見的用法就是進行簡單數學運算:
x=`expr $x + 1`(注意,這個命令貌似不能得到相應的結果,使用$()可以達到目的,後來發現反引號是Tab上面的鍵,不是單引號)
反引號`‘字元使x取值為命令expr $x + 1的執行結果。也可以使用文法$()替換反引號‘‘,如下所示:
x=$(expr $x + 1)
運算式求值 說明
expr1 | expr2 如果expr1非零,則等於expr1,否則等於expr2
expr1 & expr2 只要有一個運算式為零,則等於零,否則等於expr1
在較新的指令碼程式中,expr命令通常被替換為
更有效$((...))文法。
11.printf命令只有新版本的shell才提供printf命令,X/Open規範建議我們應該用它來代替echo命令,以產生格式化的輸出。
printf "%s\n" hello
printf "%s %d\t%s" "hi There" 15 people
12.return命令return命令的作用是使函數返回。return命令有一個數值參數,這個參數在調用該函數的指令碼程式裡被看做是該函數的返回值。如果沒有指定參數,return命令預設返回最後一條命令的退出碼。
13.set命令set命令的作用是為shell設定參數變數。許多命令的輸出結果是以空格分隔的值,如果需要使用輸出結果中的某個域,這個命令就非常有用。
假設想在一個shell指令碼中使用當前月份的名字。系統本身提供了一個date命令,它的輸出結果中包含了字串形式的月份名稱,但是需要把它與其他地區分隔開。可以將set命令和$(...)結構相結合起來執行date命令,並且返回想要的結果。date命令的輸出把月份字串作為它的第二個參數:
#!/bin/sh
echo the date is $(date)
set $(date)
echo the month is $2
exit 0
這個程式把date命令的輸出設定為參數列表,然後通過位置參數$2獲得月份。
通過這個例子可以看到date命令怎樣提取位置參數,由於date命令的輸出受本地語言的影響較大,應該使用date +%B命令來提取月份名字。
可以通過set命令和它的參數來控制shell的執行方式,其中最常用的命令格式是set -x,它讓一個指令碼程式跟蹤現實它當前執行的命令。
14.shift命令shift命令把所有參數變數左移一個位置,使$2變成$1,$3變成$2,以此類推。原來$1的值將被丟棄,而$0保持不變。如果調用shift命令時指定了一個數值參數,則表示所有的參數將左移指定的次數。$*,[email protected]和$#等其他變數也將根據參數變數的安排而作相應的變動。
15.trap命令trap命令用於指定在接受到訊號後將要採取的行動,trap命令的一種常見的使用者是在指令碼程式被中斷時完成清理工作。曆史上,shell總是用數字來代表訊號,但新的指令碼程式應該使用訊號的名字,它們定義在標頭檔signal.h中,使用訊號名時需要省略SIG首碼。可以在命令提示字元下輸入命令trap -l來查看訊號編號及其關聯的名稱。
trap命令有兩個參數,第一個參數是接收到指定訊號時將要採取的行動,第二個參數是要處理的訊號名。
trap command signal
指令碼程式通常是從上到下的順序解釋執行的,因此必須在要保護的那部分代碼之前指定trap命令。
訊號是指那些被非同步發送到一個程式的事件,在預設情況下,它們通常會終止一個程式的運行。
如果要重設某個訊號的處理方式到其預設值,只需要將command設定為-。如果要忽略某個訊號,就把command設定為空白字串‘‘。一個不帶參數的trap命令將列出當前設定的訊號以及其行動的清單。
訊號 說明
HUP(1) 掛起,通常因終端掉線或使用者退出而引發
INT(2) 中斷,通常因按下Ctrl+C按鍵組合而引發
QUIT(3) 退出,通常因按下Ctrl+\組合健而引發
ABRT(6) 終止,通常因某些嚴重的執行錯誤而引發
ALRM(14) 警示,通常用來處理逾時
TERM(15) 中之,通常在系統關機時發送
16.unset命令unset命令的作用是從環境中刪除變數或函數,這個命令不能刪除shell本身定義的唯讀變數(如IFS)
17.另外兩個有用的命令和Regex這兩個命令雖然不是shell的一部分,但是在shell程式時經常用到。同時介紹Regex,一種出現在所有linux以及與之相關聯程式中的模式比對特徵。
find命令
find命令用於搜尋檔案。
find / -name test -print
這個命令從根目錄開始尋找檔案名稱為test的檔案,並且輸出該檔案的完整路徑。
然後,這個命令的執行需要花費很長時間,並且網路上的Windows機器的硬碟也會高速轉動。這是因為linux機器掛載了一大塊Windows機器的檔案系統,看起來似乎是Windows檔案系統也被搜尋了,儘管我們知道要尋找的檔案應該在Linux機器上。
這就是第一個選項發揮作用的時候了,如果指定-mount選項,就可以告訴find命令不要搜尋掛載的其他檔案系統目錄。這樣搜尋速度會更快。
find命令的完整文法格式如下所示:
find [path] [options] [tests] [actions]
path部分:既可以使用絕對路徑,如/bin,也可以使用相對路徑,如.。如果需要,也可以指定多個路徑,如find /var /home。
find命令有許多選項可用,如:
選項 含義
-depth 在查看目錄本身之前先搜尋目錄的內容
-follow 跟隨符號連結
-maxdepths N 最多搜尋N層目錄
-mount(或-xdev) 不搜尋其他檔案系統中的目錄
測試 含義
-type c 檔案的類型為c,最常見的是d(目錄)和f(普通檔案)
操作符 含義
! -not測試取反
-a -and兩個測試都必須為真
-o -or兩個測試有一個必須為真
在目前的目錄下搜尋比檔案test28更晚的檔案:
$ find . -newer test28 -print
可以發現結果中還包含了目前的目錄,如果只想要普通檔案,則增加一個額外的測試-type f
$ find . -newer test28 -type f -print
可以組合條件搜尋,尋找目前的目錄包含_的檔案或者比test28更晚的檔案
$ find . \( -newer test28 -o -name "*_*" \) -type f -print
由於圓括弧對shell有特殊的含義,因此必須使用反斜線來引用圓括弧。
grep命令
這個名字代表的是通用Regex解析器(General Regular Expresssion Parser,簡寫為grep),使用grep命令在檔案中搜尋字串。一種常見的用法是在使用find命令時,將grep作為傳遞給-exec的一條命令。
grep命令使用一個選項,一個要匹配的模式和要搜尋的檔案,它的文法如下所示:
grep [options] pattern [files]
如果沒有提供檔案名稱,則grep命令將搜尋標準輸入。
選項 含義
-c 輸出匹配行的數目,而不是輸出匹配的行
-E 啟用擴充運算式
-h 取消每個輸出行的普通首碼,即匹配查詢模式的檔案名稱
-i 忽略大小寫
-l 只列出包含匹配行的檔案名稱,而不輸出真正的匹配行
-v 對匹配模式取反,即搜尋不匹配行而不是匹配行
例如:
$ grep in test1
顯示test1中包含in的行
$ grep -c in test1 test2 test3
顯示test1,test2,test3中包含in的行數
$ grep -c -v in test1 test2
顯示test1,test2中不包含in的行數
Regex
Regex中最常用的特殊字元如下所示:
字元 含義
^ 指向一行的開頭
$ 指向一行的結尾
. 任意單個字元
[] 方括弧內包含一個字元範圍,其中任何一個字元都可以被匹配,例如a~e,或
在字元範圍前面加上^表示使用反向字元範圍。
在方括弧中還可以使用一些有用的特殊匹配模式,如下所示:
匹配模式 含義
[:alnum:] 字母與數字字元
[:alpha:] 字母
[:ascii:] ASCII字元
[:blank:] 空格或者定位字元
[:cntrl:] ASCII控制字元
[:digit:] 數字
[:graph:] 非控制,非空格字元
[:lower:] 小寫字母
[:print:] 可列印字元
[:punct:] 標點符號字元
[:space:] 空白字元
[:upper:] 大寫字母
[:xdigit:] 十六進位數字
例如:
$ grep e$ test1
尋找test1中以e為結尾的行
$ grep ^e test1 test2
尋找顯示test1,test2中以e為開頭的行
$ grep a[[:blank:]] test1 test2
顯示test1,test2中以e為結尾的單詞
$ grep th.[[:space:]] test1 test2 test3
顯示test1,test2,test3中以th開頭的3個字母組成的單詞,用字元.來匹配一個額外的字元
$ grep -E [a-z]\{10\} test1 test2 test3
尋找顯示test1,test2,test3中只有10個字元長的全部由小寫字母組成的單詞。
2.6.6 命令的執行編寫指令碼程式時,需要
捕獲一條命令的執行結果,並把它用在shell指令碼程式中。即
執行一條命令,並把命令的輸出放到一個變數中。
所有的新指令碼程式都應該
使用$(...)形式,
$(command)的結果就是其中命令的輸出。注意,這不是該命令的退出狀態,而是它的字串形式的輸出結果。例如:
echo The current directory is $PWD
echo The current users are $(who)
如果要將命令的結果放到一個變數中,可以按照通常的方法來給它賦值,如下所示:
value=$(who)
echo $value
這種把命令的執行結果放到變數中的能力是非常強大的,它使得在指令碼程式中使用現有命令並捕獲其輸出變得很容易。如果需要把一條命令在標準輸出上的輸出轉換為一組參數,並且將它們用做為另一個程式的參數,命令xargs可以完成這個任務。
1.算數擴充之前介紹expr命令可以處理一些簡單的算數命令,但是這個命令執行起來相當慢,因為它需要調用一個新的shell來處理expr命令。
一個更新更好的辦法是
使用$((...))擴充。把準備求值的運算式括在$((...))中能夠更有效地完成簡單的算數運算。如下所示:
x=0
while [ "$x" -ne 10 ]
do
echo $x
x=$(($x+1))
done
注意,這與x=$(...)命令不同,兩對圓括弧用於算數替換,而我們之前見到的一對圓括弧用於 命令的執行和擷取輸出。
???疑惑的是把x當做字串處理判斷條件,也得到相同的輸出( "$x" != 10 )
2.參數擴充之前介紹過簡單的參數賦值和擴充了,如下所示:
foo=fred
echo $foo
???疑惑 shell中的賦值,有點指標傳遞的感覺,foo變數中儲存fred的指標,取地址運算子*類似與$,獲得變數的內容
如果想編寫一個指令碼程式,來處理test1和test2兩個檔案,可能這樣寫:
for i in 1 2
do
./testi
done
在每次迴圈中,發現./testi not found
問題在於./testi變數無法被替換,為了保護變數名中類似於$i部分的擴充,需要
把i放在花括弧中,如下所示
.test${i}
這樣,每次迴圈中,變數i的值替換為$(i),給出正確的檔案名稱,把參數的值替換進了一個字串。
可以在shell中採用多種參數替換方法,對於多參數處理問題來說,這些方法通常提供了一種精巧的解決方案。
參數擴充 說明
${param:-default} 如果param為空白,就把它設定為default的值
#{#param} 給出param的長度
${param%word} 從param的尾部開始刪除與word匹配的最小部分,然後返回剩餘部分
${param%%word} 從param的尾部開始刪除與word匹配的最長部分,然後返回剩餘部分
${param#word} 從param的頭部開始刪除與word匹配的最小部分,然後返回剩餘部分
${param##word} 從param的頭部開始刪除與word匹配的最長部分,然後返回剩餘部分
當處理字串時,這些替換通常是非常有用的,特別是對字串進行部分刪除的最後4個參數擴張方法,在處理檔案名稱和路徑時非常有用。
linux程式設計——shell程式設計(第二章)