Linux shell編程中IO和條件及迴圈處理的細節問題討論_linux shell

來源:互聯網
上載者:User

> 與 < 差在哪?
談到I/O redirection,不妨先讓我們認識一下File Descriptor(FD)。程式的運算,在大部份情況下都是進行資料(data)的處理,這些資料從哪讀進?又,送出到哪裡呢?這就是File descriptor(FD)的功用了。

在shell程式中,最常使用的FD大概有三個,分別為:

  • 0: Standard Input(STDIN)
  • 1: Standard Output(STDOUT)
  • 2: Standard Error Output(STDERR)

在標準情況下,這些FD分別跟如下裝置(device)關聯:

  • stdin(0):keyboard
  • stdout(1):monitor
  • stderr(2):monitor

我們可以用如下下命令測試一下:

$ mail -s test rootthis is a test mail.please skip.

^d(同時按crtl跟d鍵)
很明顯,mail程式所讀進的資料,就是從stdin也就是keyboard讀進的。不過,不見得每個程式的stdin都跟mail一樣從keyboard讀進,因為程式作者可以從檔案參數讀進stdin,如:

$ cat /etc/passwd

但,要是cat之後沒有檔案參數則又如何呢?哦,請您自己玩玩看囉….^_^

$ cat

(請留意資料輸出到哪裡去了,最後別忘了按^d離開…)至於stdout與stderr,嗯…然後,讓我們繼續看stderr好了。

事實上,stderr沒甚麼難理解的:說穿了就是“錯誤資訊”要往哪邊送而已…比方說,若讀進的檔案參數是不存在的,那我們在monitor上就看到了:

$ ls no.such.filels: no.such.file: No such file or directory

若,一個命令同時產生stdout與stderr呢?那還不簡單,都送到monitor來就好了:

$ touch my.file$ ls my.file no.such.filels: no.such.file: No such file or directorymy.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 <<FINISHfirst line heresecond line therethird line nowhereFINISH

這樣的話,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.outls: no.such.file: No such file or directory

這樣monitor就只剩下stderr而已。因為stdout給寫進file.out去了。

$ ls my.file no.such.file 2>file.errmy.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

在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/nullmy.file

若要相反:只想看到stderr呢?還不簡單﹗將stdout弄到null就行:

$ ls my.file no.such.file >/dev/nullls: 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.out1$ echo "2" > file.out$ cat file.out2

看來,我們在重導stdout或stderr進一份檔案時,似乎永遠只獲得最後一次匯入的結果。那,之前的內容呢?呵~~~要解決這個問提很簡單啦,將>換成>>就好:

$ echo "3" >> file.out$ cat file.out23

如此一來,被重導的目標檔案之內容並不會失去,而新的內容則一直增加在最後面去。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.out5

再問:那…有辦法不取消而又“臨時”蓋寫目標檔案嗎?哦,佛曰:不可告也﹗啊~開玩笑的、開玩笑的啦~ ^_^唉,早就料到人心是不足的了﹗

$ set -o noclobber$ echo "6" >| file.out$ cat file.out6

留意到沒有:在>後面再加個“|”就好(注意:>與|之間不能有空白哦)… 呼…(深呼吸吐納一下吧)~~~ ^_^再來還有一個難題要你去參透的呢:

$ echo "some text here" > file$ cat < filesome text here$ cat < file > file.bak$ cat < file.baksome 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原來是活得如此精彩的﹗常讓人有“眾裡尋他千百度,驀然回首,那人卻在燈火闌珊處﹗”之感…^_^

你要 if 還是 case 呢?
放了一個愉快的春節假期,人也變得懶懶散散的…只是,答應了大家的作業,還是要堅持完成就是了~~~

還記得我們在第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 comd1then  comd2  comd3else  comd4  comd5fi

這也是我們在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  comd2elif comd3; then  comd4else  comd5fi

意思是說:若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,十三問還剩一問而已,過幾天再來搞定之….^_^

for what?while與until差在哪?

最後要介紹的是shell script設計中常見的“迴圈”(loop)。所謂的loop就是script中的一段在一定條件下反覆執行的代碼。bash shell中常用的loop有如下三種:for while until

for loop是從一個清單列表中讀進變數值,並“依次”的迴圈執行do到done之間的命令列。例:

for var in one two three four fivedo  echo -----------  echo '$var is '$var  echodone

上例的執行結果將會是:

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=1while [ "$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=1until [ ! "$num" -le 10 ]; do  echo "num is $num"  num=$(($num + 1))done

或是:

num=1until [ "$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,也就是繼續當前的迴圈。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.