本文整理並轉自CU上的帖子[學習共享] shell 十三問?,此貼是2003年發表的,但倒是至關不錯的linux基礎知識聚集貼,原帖主使用的臺灣風格,本文加以簡體化和整理。html
談到I/O redirection
,不妨先讓咱們認識一下File Descriptor
(FD)。程序的運算,在大部份狀況下都是進行數據(data)的處理,這些數據從哪讀進?又,送出到哪裏呢?這就是File descriptor(FD)的功用了。linux
在shell程序中,最常使用的FD大概有三個,分別爲:shell
0: Standard Input(STDIN)bash
1: Standard Output(STDOUT)app
2: Standard Error Output(STDERR)工具
在標準狀況下,這些FD分別跟以下設備(device)關聯:oop
stdin(0):keyboard性能
stdout(1):monitor學習
stderr(2):monitor測試
咱們能夠用以下下命令測試一下:
$ mail -s test root this is a test mail. please skip. ^d(同時按crtl跟d鍵)
很明顯,mail程序所讀進的數據,就是從stdin
也就是keyboard讀進的。不過,不見得每一個程序的stdin都跟mail同樣從keyboard讀進,由於程序做者能夠從檔案參數讀進stdin,如:
$ cat /etc/passwd
但,要是cat以後沒有檔案參數則又如何呢?哦,請您本身玩玩看囉….^_^
$ cat
(請留意數據輸出到哪裏去了,最後別忘了按^d離開…)至於stdout與stderr,嗯…等我有空再續吧…^_^ 仍是,有哪位前輩要來玩接龍呢?
沿文再續,書接上一回…^_^ 相信,通過上一個練習後,你對stdin
與stdout
應該不難理解吧?而後,讓咱們繼續看stderr
好了。
事實上,stderr沒甚麼難理解的:說穿了就是「錯誤信息」要往哪邊送而已…比方說,若讀進的檔案參數是不存在的,那咱們在monitor上就看到了:
$ ls no.such.file ls: no.such.file: No such file or directory
若,一個命令同時產生stdout與stderr呢?那還不簡單,都送到monitor來就行了:
$ touch my.file $ ls my.file no.such.file ls: no.such.file: No such file or directory my.file
okay,至此,關於FD及其名稱、還有相關聯的設備,相信你已經沒問題了吧?那好,接下來讓咱們看看如何改變這些FD的預設數據信道,咱們可用<
來改變讀進的數據信道(stdin),使之從指定的檔案讀進。咱們可用>
來改變送出的數據信道(stdout,stderr),使之輸出到指定的檔案。比方說:
$ cat < my.file
就是從my.file讀進數據
$ mail -s test root < /etc/passwd
則是從/etc/passwd
讀進…
這樣一來,stdin將再也不是從keyboard讀進,而是從檔案讀進了…嚴格來講,<
符號以前須要指定一個FD的(之間不能有空白),但由於0
是<
的預設值,所以<
與0<
是同樣的!okay,這個好理解吧?
那,要是用兩個<<
又是啥呢?這是所謂的HERE Document
,它可讓咱們輸入一段文本,直到讀到<<
後指定的字串。比方說:
$ cat <<FINISH first line here second line there third line nowhere FINISH
這樣的話,cat會讀進3行句子,而無需從keyboard讀進數據且要等^d結束輸入。至於>
又如何呢?且聽下回分解...
okay,又到講古時間~~~當你搞懂了0<
原來就是改變stdin
的數據輸入信道以後,相信要理解以下兩個redirection就不難了:1>
2>
。前者是改變stdout
的數據輸出信道,後者是改變stderr
的數據輸出信道。二者都是將本來要送出到monitor的數據轉向輸出到指定檔案去。
因爲1
是>
的預設值,所以,1>
與>
是相同的,都是改變stdout
。用上次的ls例子來講明一下好了:
$ ls my.file no.such.file 1>file.out ls: no.such.file: No such file or directory
這樣monitor就只剩下stderr而已。由於stdout給寫進file.out
去了。
$ ls my.file no.such.file 2>file.err my.file
這樣monitor就只剩下stdout,由於stderr寫進了file.err
。
$ ls my.file no.such.file 1>file.out 2>file.err
這樣monitor就啥也沒有,由於stdout與stderr都給轉到檔案去了…
呵~~~看來要理解>
一點也不難啦﹗是不?沒騙你吧?^_^ 不過,有些地方仍是要注意一下的。
首先,是同時寫入的問題。比方以下這個例子:
$ ls my.file no.such.file 1>file.both 2>file.both
假如stdout(1)與stderr(2)都同時在寫入file.both的話,則是採起「覆蓋」方式:後來寫入的覆蓋
前面的。讓咱們假設一個stdout與stderr同時寫入file.out的情形好了:
首先stdout寫入10個字元
而後stderr寫入6個字元
那麼,這時候本來stdout輸出的10個字元就被stderr覆蓋掉了。那,如何解決呢?所謂山不轉路轉、路不轉人轉嘛,咱們能夠換一個思惟:將stderr導進stdout或將stdout導進sterr,而不是你們在搶同一份檔案,不就好了﹗bingo﹗就是這樣啦:
2>&1
就是將stderr
並進stdout
做輸出
1>&2
或>&2
就是將stdout
並進stderr
做輸出
因而,前面的錯誤操做能夠改成:
$ ls my.file no.such.file 1>file.both 2>&1 或 $ ls my.file no.such.file 2>file.both >&2
這樣,不就皆大歡喜了嗎?呵~~~ ^_^ 不過,光解決了同時寫入的問題還不夠,咱們還有其餘技巧須要瞭解的。故事還沒結束,別走開﹗廣告後,咱們再回來…﹗
okay,此次不講I/O Redirction,講佛吧…(有沒搞錯?﹗網中人是否頭殼燒壞了?…)嘻~~~ ^_^
學佛的最高境界,就是「四大皆空」。至因而空哪四大塊?我也不知,由於我還沒到那境界…但這個「空」字,卻很是值得咱們返復把玩的:---色便是空、空便是色﹗好了,施主要是可以領會「空」的禪意,那離修成正果不遠矣~~~
在Linux檔案系統裏,有個設備檔位於/dev/null
。許多人都問過我那是甚麼玩意兒?我跟你說好了:那就是「空」啦﹗沒錯﹗空空如也的空就是null了….請問施主是否突然有所頓誤了呢?然則恭喜了~~~ ^_^ 這個null在I/O Redirection中可有用得很呢:
若將FD1
跟FD2
轉到/dev/null
去,就可將stdout與stderr弄不見掉。
若將FD0
接到/dev/null
來,那就是讀進nothing。
比方說,當咱們在執行一個程序時,畫面會同時送出stdout跟stderr,
假如你不想看到stderr(也不想存到檔案去),那能夠:
$ ls my.file no.such.file 2>/dev/null my.file
若要相反:只想看到stderr呢?還不簡單﹗將stdout弄到null就行:
$ ls my.file no.such.file >/dev/null ls: no.such.file: No such file or directory
那接下來,假如單純只跑程序,不想看到任何輸出結果呢?哦,這裏留了一手上次節目沒講的法子,專門贈予有緣人﹗…^_^ 除了用>/dev/null 2>&1
以外,你還能夠如此:
$ ls my.file no.such.file &>/dev/null
(提示:將&>
換成>&
也行啦~~!)
okay?講完佛,接下來,再讓咱們看看以下狀況:
$ echo "1" > file.out $ cat file.out 1 $ echo "2" > file.out $ cat file.out 2
看來,咱們在重導stdout或stderr進一份檔案時,彷佛永遠只得到最後一次導入的結果。那,以前的內容呢?呵~~~要解決這個問提很簡單啦,將>
換成>>
就好:
$ echo "3" >> file.out $ cat file.out 2 3
如此一來,被重導的目標檔案以內容並不會失去,而新的內容則一直增長在最後面去。easy?呵…^_^
但,只要你再一次用回單一的>
來重導的話,那麼,舊的內容仍是會被「洗」掉的﹗這時,你要如何避免呢?----備份﹗yes,我聽到了﹗不過….還有更好的嗎?既然與施主這麼有緣份,老納就送你一個錦囊妙法吧:
$ set -o noclobber $ echo "4" > file.out -bash: file: cannot overwrite existing file
那,要如何取消這個「限制」呢?哦,將set -o
換成set +o
就行:
$ set +o noclobber $ echo "5" > file.out $ cat file.out 5
再問:那…有辦法不取消而又「臨時」蓋寫目標檔案嗎?哦,佛曰:不可告也﹗啊~開玩笑的、開玩笑的啦~ ^_^唉,早就料到人心是不足的了﹗
$ set -o noclobber $ echo "6" >| file.out $ cat file.out 6
留意到沒有:在>後面再加個「|」就好(注意:>與|之間不能有空白哦)… 呼…(深呼吸吐納一下吧)~~~ ^_^再來還有一個難題要你去參透的呢:
$ echo "some text here" > file $ cat < file some text here $ cat < file > file.bak $ cat < file.bak some text here $ cat < file > file $ cat < file
嗯?!注意到沒有?!!----怎麼最後那個cat命令看到的file竟是空的?﹗why?why?why?同窗們:下節課不要遲到囉~~~!
噹噹噹~上課囉~ ^_^
前面提到:$ cat < file > file
以後本來有內容的檔案結果卻被洗掉了﹗要理解這一現像其實不難,這只是priority的問題而已:
在IO Redirection中,stdout與stderr的管道會先準備好,纔會從stdin讀進數據。也就是說,在上例中,> file
會先將file清空,而後纔讀進< file
,但這時候檔案已經被清空了,所以就變成讀不進任何數據了…哦~原來如此~~ ^_^
那…以下兩例又如何呢?
$ cat <> file $ cat < file >> file
嗯…同窗們,這兩個答案就當練習題囉,下節課以前請交做業﹗好了,I/O Redirection也快講完了,sorry,由於我也只知道這麼多而已啦~嘻 ^_^ 不過,還有同樣東東是必定要講的,各位觀衆(請自行配樂~!#@!$%):----就是pipe line也!
談到pipe line
,我相信很多人都不會陌生:咱們在不少command line上常看到的「|」符號就是pipe line了。不過,究竟pipe line是甚麼東東呢?別急別急…先查一下英漢字典,看看pipe是甚麼意思?沒錯﹗它就是「水管」的意思…那麼,你能想像一下水管是怎麼一根接着一根的嗎?又,每根水管之間的input跟output又如何呢?嗯??靈光一閃:原來pipe line的I/O跟水管的I/O是如出一轍的:上一個命令的stdout接到下一個命令的stdin去了!
的確如此…無論在command line上你使用了多少個pipe line,先後兩個command的I/O都是彼此鏈接的﹗(恭喜:你終於開竅了﹗^_^)
不過…然而…可是……stderr呢?好問題﹗不過也容易理解:若水管漏水怎麼辦?也就是說:在pipe line之間,前一個命令的stderr是不會接進下一命令的stdin的,其輸出,若不用2>
導到file去的話,它仍是送到攝像頭上面來﹗這點請你在pipe line運用上務必要注意的。那,或許你又會問:有辦法將stderr也喂進下一個命令的stdin去嗎?(貪得無厭的傢伙﹗)方法固然是有,並且你早已學過了﹗^_^ 我提示一下就好:請問你如何將stderr合併進stdout一同輸出呢?若你答不出來,下課以後再來問我吧…(若是你臉皮真夠厚的話…)
或許,你仍意尤未盡﹗或許,你曾經碰到過下面的問題:
在cm1 | cm2 | cm3…
這段pipe line中,若要將cm2
的結果存到某一檔案呢?
若你寫成cm1 | cm2 > file | cm3
的話,那你確定會發現cm3的stdin是空的﹗(固然啦,你都將水管接到別的水池了﹗)聰明的你或許會如此解決:cm1 | cm2 > file; cm3 < file
是的,你的確能夠這樣作,但最大的壞處是:這樣一來,file I/O會變雙倍﹗在command執行的整個過程當中,file I/O是最多見的最大性能殺手。凡有經驗的shell操做者,都會盡可能避免或下降file I/O的頻率。那,上面問題還有更好方法嗎?有的,那就是tee
命令了。
所謂tee
命令是在不影響本來I/O的狀況下,將stdout複製一份到檔案去。所以,上面的命令行能夠如此打:
cm1 | cm2 | tee file | cm3
在預設上,tee會改寫目標檔案,若你要改成增長內容的話,那可用-a
參數達成。
基本上,pipe line的應用在shell操做上是很是普遍的,尤爲是在text filtering方面,
凡舉cat
,more
,head
,tail
,wc
,expand
,tr
,grep
,sed
,awk
,…等等文字處理工具,搭配起pipe line來使用,你會驚覺command line原來是活得如此精彩的﹗常讓人有「衆裏尋他千百度,驀然回首,那人卻在燈火闌珊處﹗」之感…^_^
好了,關於I/O Redirection的介紹就到此告一段落。若往後有空的話,再爲你們介紹其它在shell上好玩的東西﹗bye…^_^
放了一個愉快的春節假期,人也變得懶懶散散的…只是,答應了你們的做業,仍是要堅持完成就是了~~~
還記得咱們在第10章所介紹的return value嗎?是的,接下來介紹的內容與之有關,若你的記憶也被假期的歡樂時光所抵消掉的話,那,建議您仍是先回去溫習溫習再回來…
若你記得return value,我想你也應該記得了&&
與||
是甚麼意思吧?用這兩個符號再配搭command group的話,咱們可以讓shell script變得更加聰明哦。比方說:
comd1 && { comd2 comd3 : } || { comd4 comd5 }
意思是說:假如comd1的return value爲true的話,然則執行comd2與comd3,不然執行comd4與comd5。
事實上,咱們在寫shell script的時候,常常須要用到這樣那樣的條件以做出不一樣的處理動做。
用&&
與||
的確能夠達成條件執行的效果,然而,從「人類語言」上來理解,卻不是那麼直觀。
更多時候,咱們仍是喜歡用if….then…else…這樣的keyword來表達條件執行。在bash shell中,咱們能夠如此修改上一段代碼:
if comd1 then comd2 comd3 else comd4 comd5 fi
這也是咱們在shell script中最經常使用到的if
判斷式:只要if後面的command line返回true的return value(咱們最經常使用test命令來送出return value),然則就執行then後面的命令,不然執行else後的命令;fi則是用來結束判斷式的keyword。
在if判斷式中,else部份能夠不用,但then是必需的。(若then後不想跑任何command,可用:
這個null command代替)。固然,then或else後面,也能夠再使用更進一層的條件判斷式,這在shell script設計上很常見。
如有多項條件須要「依序」進行判斷的話,那咱們則可以使用elif
這樣的keyword:
if comd1; then comd2 elif comd3; then comd4 else comd5 fi
意思是說:若comd1爲true,然則執行comd2;不然再測試comd3,然則執行comd4;假若comd1與comd3均不成立,那就執行comd5。
if判斷式的例子很常見,你可從不少shell script中看獲得,我這裏就再也不舉例子了…接下來要爲你們介紹的是case判斷式。
雖然if判斷式已可應付大部份的條件執行了,然而,在某些場合中,卻不夠靈活,尤爲是在string式樣的判斷上,比方以下:
QQ() { echo -n "Do you want to continue?(Yes/No):" read YN if [ "$YN" = Y -o "$YN" = y -o "$YN" = "Yes" -o "$YN" = "yes" -o "$YN" = "YES" ] then QQ else exit 0 fi } QQ
從例中,咱們看得出來,最麻煩的部份是在於判斷YN的值可能有好幾種式樣。聰明的你或許會如此修改:
if echo "$YN" | grep -q '^[Yy]\([Ee][Ss]\)*$'
也就是用Regular Expression
來簡化代碼。(咱們有機會再來介紹RE)只是…是否有其它更方便的方法呢?有的,就是用case判斷式便可:
QQ() { echo -n "Do you want to continue?(Yes/No):" read YN case "$YN" in [Yy]|[Yy][Ee][Ss]) QQ ;; *) exit 0 ;; esac } QQ
咱們經常使用case的判斷式來判斷某一變量在不一樣的值(一般是string)時做出不一樣的處理,比方說,判斷script參數以執行不一樣的命令。若你有興趣、且用Linux系統的話,不妨挖一挖/etc/init.d/*裏那堆script中的case用法。以下就是一例:
case "$1" in start) start ;; stop) stop ;; status) rhstatus ;; restart|reload) restart ;; condrestart) [ -f /var/lock/subsys/syslog ] && restart || : ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart}" exit 1 esac
(若你對positional parameter的印像已經模糊了,請重看第9章吧。)okay,十三問還剩一問而已,過幾天再來搞定之….^_^
終於,來到shell十三問的最後一問了…長長吐一口氣~~~~
最後要介紹的是shell script設計中常見的「循環」(loop)。所謂的loop就是script中的一段在必定條件下反覆執行的代碼。bash shell中經常使用的loop有以下三種:for
while
until
for loop是從一個清單列表中讀進變量值,並「依次」的循環執行do到done之間的命令行。例:
for var in one two three four five do echo ----------- echo '$var is '$var echo done
上例的執行結果將會是:
for會定義一個叫var的變量,其值依次是one two three four five。
由於有5個變量值,所以do與done之間的命令行會被循環執行5次。
每次循環均用echo產生三行句子。而第二行中不在hard quote以內的$var
會依次被替換爲one two three four five。
當最後一個變量值處理完畢,循環結束。
咱們不難看出,在for loop中,變量值的多寡,決定循環的次數。然而,變量在循環中是否使用則不必定,得視設計需求而定。假若for loop沒有使用in這個keyword來指定變量值清單的話,其值將從$@
(或$*
)中繼承:
for var; do … done
(若你忘記了positional parameter,請溫習第9章…)
for loop用於處理「清單」(list)項目很是方便,其清單除了可明確指定或從positional parameter取得以外,也可從變量替換或命令替換取得…(再一次提醒:別忘了命令行的「重組」特性!)
然而,對於一些「累計變化」的項目(如整數加減),for亦能處理:
for ((i=1;i<=10;i++)) do echo "num is $i" done
除了for loop,上面的例子咱們也可改用while loop來作到:
num=1 while [ "$num" -le 10 ]; do echo "num is $num" num=$(($num + 1)) done
while loop的原理與for loop稍有不一樣:它不是逐次處理清單中的變量值,而是取決於while後面的命令行之return value:
若爲ture,則執行do
與done
之間的命令,而後從新判斷while
後的return value。
若爲false,則再也不執行do與done之間的命令而結束循環。
分析上例:
在while以前,定義變量num=1。
而後測試(test)$num是否小於或等於10。
結果爲true,因而執行echo並將num的值加一。
再做第二輪測試,此時num的值爲1+1=2,依然小於或等於10,所以爲true,繼續循環。
直到num爲10+1=11時,測試纔會失敗…因而結束循環。
咱們不難發現:若while的測試結果永遠爲true的話,那循環將一直永久執行下去:
while : ;do echo looping… done
上例的:
是bash的null command,不作任何動做,除了送回true的return value。所以這個循環不會結束,稱做死循環。死循環的產生有多是故意設計的(如跑daemon),也多是設計錯誤。若要結束死循環,可透過signal來終止(如按下ctrl-c)。(關於process與signal,等往後有機會再補充,十三問暫時略過。)
一旦你可以理解while loop的話,那,就能理解until loop:
與while相反,until是在return value爲false時進入循環,不然結束。
所以,前面的例子咱們也能夠輕鬆的用until來寫:
num=1 until [ ! "$num" -le 10 ]; do echo "num is $num" num=$(($num + 1)) done
或是:
num=1 until [ "$num" -gt 10 ]; do echo "num is $num" num=$(($num + 1)) done
okay,關於bash的三個經常使用的loop暫時介紹到這裏。在結束本章以前,再跟你們補充兩個與loop有關的命令:break
continue
這兩個命令經常使用在複合式循環裏,也就是在do…done之間又有更進一層的loop,固然,用在單一循環中也何嘗不可啦…^_^
break
是用來打斷循環,也就是「強迫結束」循環。若break後面指定一個數值n的話,則「從裏向外」打斷第n個循環,預設值爲break 1
,也就是打斷當前的循環。
在使用break時須要注意的是,它與return及exit是不一樣的:
break是結束loop
return是結束function
exit是結束script/shell
而continue
則與break
相反:強迫進入下一次循環動做。若你理解不來的話,那你可簡單的當作:在continue
到done
之間的句子略過而返回循環頂端…與break相同的是:continue
後面也可指定一個數值n,以決定繼續哪一層(從裏向外計算)的循環,預設值爲continue 1
,也就是繼續當前的循環。
在shell script設計中,若能善用loop,將能大幅度提升script在複雜條件下的處理能力。請多加練習吧….
好了,該是到告終束的時候了。
婆婆媽媽的跟你們羅唆了一堆關於shell的基礎概念,目的不是要告訴你們「答案」,而是要帶給你們「啓發」…在往後關於shell的討論中,我或許會常常用「鏈接」方式指引回來十三問中的內容,以便咱們在進行技術探討時彼此能有一些討論基礎,而不至於各說各話、徒費時力。但,更但願十三問能帶給你更多的思考與樂趣,至爲重要的是透過實做來加深理解。
是的,我很重視「實做」與「獨立思考」這兩項學習要素,若你可以掌握其中真義,那請容我說聲:
---恭喜﹗十三問你沒白看了﹗^_^
p.s.
至於補充問題部份,我暫時不寫了。而是但願:
你們擴充題目。
一塊兒來寫心得。
Good luck and happy studying!