本文整理并轉自CU上的帖子[學習共享] shell 十三問 ?,此貼是2003年發(fā)表的,但卻是相當不錯的linux基礎知識匯集貼,原帖主使用的臺灣風格,本文加以簡體化和整理。 第11問:> 與 < 差在哪?11.1談到I/O redirection ,不妨先讓我們認識一下File Descriptor (FD)。程序的運算,在大部份情況下都是進行數(shù)據(jù)(data)的處理,這些數(shù)據(jù)從哪讀進?又,送出到哪里呢?這就是File descriptor(FD)的功用了。 在shell程序中,最常使用的FD大概有三個,分別為: - 0: Standard Input(STDIN)
- 1: Standard Output(STDOUT)
- 2: Standard Error Output(STDERR)
在標準情況下,這些FD分別跟如下設備(device)關聯(lián): - stdin(0):keyboard
- stdout(1):monitor
- stderr(2):monitor
我們可以用如下下命令測試一下: $ mail -s test root
this is a test mail.
please skip.
^d(同時按crtl跟d鍵)
很明顯,mail程序所讀進的數(shù)據(jù),就是從stdin 也就是keyboard讀進的。不過,不見得每個程序的stdin都跟mail一樣從keyboard讀進,因為程序作者可以從檔案參數(shù)讀進stdin,如: $ cat /etc/passwd
但,要是cat之后沒有檔案參數(shù)則又如何呢?哦,請您自己玩玩看啰….^_^ $ cat
(請留意數(shù)據(jù)輸出到哪里去了,最后別忘了按^d離開…)至于stdout與stderr,嗯…等我有空再續(xù)吧…^_^ 還是,有哪位前輩要來玩接龍呢? 11.2沿文再續(xù),書接上一回…^_^ 相信,經(jīng)過上一個練習后,你對stdin與stdout應該不難理解吧?然后,讓我們繼續(xù)看stderr好了。 事實上,stderr沒甚么難理解的:說穿了就是“錯誤信息”要往哪邊送而已…比方說,若讀進的檔案參數(shù)是不存在的,那我們在monitor上就看到了: $ ls no.such.file
ls: no.such.file: No such file or directory
若,一個命令同時產(chǎn)生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及其名稱、還有相關聯(lián)的設備,相信你已經(jīng)沒問題了吧?那好,接下來讓我們看看如何改變這些FD的預設數(shù)據(jù)信道,我們可用<來改變讀進的數(shù)據(jù)信道(stdin),使之從指定的檔案讀進。我們可用>來改變送出的數(shù)據(jù)信道(stdout,stderr),使之輸出到指定的檔案。比方說: $ cat < my.file
就是從my.file讀進數(shù)據(jù) $ 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讀進數(shù)據(jù)且要等^d結束輸入。至> 又如何呢?且聽下回分解… 11.3okay,又到講古時間~~~當你搞懂了0< 原來就是改變stdin 的數(shù)據(jù)輸入信道之后,相信要理解如下兩個redirection就不難了:1> 2> 。前者是改變stdout 的數(shù)據(jù)輸出信道,后者是改變stderr 的數(shù)據(jù)輸出信道。兩者都是將原本要送出到monitor的數(shù)據(jù)轉向輸出到指定檔案去。 由于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
這樣,不就皆大歡喜了嗎?呵~~~ ^_^ 不過,光解決了同時寫入的問題還不夠,我們還有其他技巧需要了解的。故事還沒結束,別走開﹗廣告后,我們再回來…﹗ 11.4okay,這次不講I/O Redirction,講佛吧…(有沒搞錯?﹗網(wǎng)中人是否頭殼燒壞了?…)嘻~~~ ^_^ 學佛的最高境界,就是“四大皆空”。至于是空哪四大塊?我也不知,因為我還沒到那境界…但這個“空”字,卻非常值得我們返復把玩的:—-色即是空、空即是色﹗好了,施主要是能夠領會“空”的禪意,那離修成正果不遠矣~~~ 在Linux檔案系統(tǒng)里,有個設備檔位于/dev/null 。許多人都問過我那是甚么玩意兒?我跟你說好了:那就是“空”啦﹗沒錯﹗空空如也的空就是null了….請問施主是否忽然有所頓誤了呢?然則恭喜了~~~ ^_^ 這個null在I/O Redirection中可有用得很呢: - 若將
FD1 跟FD2 轉到/dev/null 去,就可將stdout與stderr弄不見掉。 - 若將
FD0 接到/dev/null 來,那就是讀進nothing。
比方說,當我們在執(zhí)行一個程序時,畫面會同時送出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
那接下來,假如單純只跑程序,不想看到任何輸出結果呢?哦,這里留了一手上次節(jié)目沒講的法子,專門贈予有緣人﹗…^_^ 除了用>/dev/null 2>&1 之外,你還可以如此: $ ls my.file no.such.file &>/dev/null
(提示:將&> 換成>& 也行啦~~?。?/p> okay?講完佛,接下來,再讓我們看看如下情況: $ echo "1" > file.out
$ cat file.out
1
$ echo "2" > file.out
$ cat file.out
2
看來,我們在重導stdout或stderr進一份檔案時,似乎永遠只獲得最后一次導入的結果。那,之前的內(nèi)容呢?呵~~~要解決這個問提很簡單啦,將> 換成>> 就好: $ echo "3" >> file.out
$ cat file.out
2
3
如此一來,被重導的目標檔案之內(nèi)容并不會失去,而新的內(nèi)容則一直增加在最后面去。easy?呵…^_^ 但,只要你再一次用回單一的>來重導的話,那么,舊的內(nèi)容還是會被“洗”掉的﹗這時,你要如何避免呢?——備份﹗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
留意到?jīng)]有:在>后面再加個“|”就好(注意:>與|之間不能有空白哦)… 呼…(深呼吸吐納一下吧)~~~ ^_^再來還有一個難題要你去參透的呢: $ 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
嗯?!注意到?jīng)]有?!!——怎么最后那個cat命令看到的file竟是空的?﹗why?why?why?同學們:下節(jié)課不要遲到啰~~~! 11.5當當當~上課啰~ ^_^ 前面提到:$ cat < file > file 之后原本有內(nèi)容的檔案結果卻被洗掉了﹗要理解這一現(xiàn)像其實不難,這只是priority的問題而已: 在IO Redirection中,stdout與stderr的管道會先準備好,才會從stdin讀進數(shù)據(jù)。也就是說,在上例中,> file 會先將file清空,然后才讀進< file ,但這時候檔案已經(jīng)被清空了,因此就變成讀不進任何數(shù)據(jù)了…哦~原來如此~~ ^_^ 那…如下兩例又如何呢? $ cat <> file
$ cat < file >> file
嗯…同學們,這兩個答案就當練習題啰,下節(jié)課之前請交作業(yè)﹗好了,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一同輸出呢?若你答不出來,下課之后再來問我吧…(如果你臉皮真夠厚的話…) 或許,你仍意尤未盡﹗或許,你曾經(jīng)碰到過下面的問題: 在cm1 | cm2 | cm3… 這段pipe line中,若要將cm2 的結果存到某一檔案呢? 若你寫成cm1 | cm2 > file | cm3 的話,那你肯定會發(fā)現(xiàn)cm3的stdin是空的﹗(當然啦,你都將水管接到別的水池了﹗)聰明的你或許會如此解決:cm1 | cm2 > file; cm3 < file 是的,你的確可以這樣做,但最大的壞處是:這樣一來,file I/O會變雙倍﹗在command執(zhí)行的整個過程中,file I/O是最常見的最大性能殺手。凡是有經(jīng)驗的shell操作者,都會盡量避免或降低file I/O的頻率。那,上面問題還有更好方法嗎?有的,那就是tee 命令了。 所謂tee 命令是在不影響原本I/O的情況下,將stdout復制一份到檔案去。因此,上面的命令行可以如此打: cm1 | cm2 | tee file | cm3
在預設上,tee會改寫目標檔案,若你要改為增加內(nèi)容的話,那可用-a參數(shù)達成。 基本上,pipe line的應用在shell操作上是非常廣泛的,尤其是在text filtering方面, 凡舉cat ,more ,head ,tail ,wc ,expand ,tr ,grep ,sed ,awk ,…等等文字處理工具,搭配起pipe line來使用,你會驚覺command line原來是活得如此精彩的﹗常讓人有“眾里尋他千百度,驀然回首,那人卻在燈火闌珊處﹗”之感…^_^ 好了,關于I/O Redirection的介紹就到此告一段落。若日后有空的話,再為大家介紹其它在shell上好玩的東西﹗bye…^_^ 第12問:你要 if 還是 case 呢?放了一個愉快的春節(jié)假期,人也變得懶懶散散的…只是,答應了大家的作業(yè),還是要堅持完成就是了~~~ 還記得我們在第10章所介紹的return value嗎?是的,接下來介紹的內(nèi)容與之有關,若你的記憶也被假期的歡樂時光所抵消掉的話,那,建議您還是先回去溫習溫習再回來… 若你記得return value,我想你也應該記得了&& 與|| 是甚么意思吧?用這兩個符號再配搭command group的話,我們可讓shell script變得更加聰明哦。比方說: comd1 && {
comd2
comd3
:
} || {
comd4
comd5
}
意思是說:假如comd1的return value為true的話,然則執(zhí)行comd2與comd3,否則執(zhí)行comd4與comd5。 事實上,我們在寫shell script的時候,經(jīng)常需要用到這樣那樣的條件以作出不同的處理動作。 用&& 與|| 的確可以達成條件執(zhí)行的效果,然而,從“人類語言”上來理解,卻不是那么直觀。 更多時候,我們還是喜歡用if….then…else…這樣的keyword來表達條件執(zhí)行。在bash shell中,我們可以如此修改上一段代碼: if comd1
then
comd2
comd3
else
comd4
comd5
fi
這也是我們在shell script中最常用到的if 判斷式:只要if后面的command line返回true的return value(我們最常用test命令來送出return value),然則就執(zhí)行then后面的命令,否則執(zhí)行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,然則執(zhí)行comd2;否則再測試comd3,然則執(zhí)行comd4;倘若comd1與comd3均不成立,那就執(zhí)行comd5。 if判斷式的例子很常見,你可從很多shell script中看得到,我這里就不再舉例子了…接下來要為大家介紹的是case判斷式。 雖然if判斷式已可應付大部份的條件執(zhí)行了,然而,在某些場合中,卻不夠靈活,尤其是在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參數(shù)以執(zhí)行不同的命令。若你有興趣、且用Linux系統(tǒng)的話,不妨挖一挖/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的印像已經(jīng)模糊了,請重看第9章吧。)okay,十三問還剩一問而已,過幾天再來搞定之….^_^ 第13問:for what?while與until差在哪?終于,來到shell十三問的最后一問了…長長吐一口氣~~~~ 最后要介紹的是shell script設計中常見的“循環(huán)”(loop)。所謂的loop就是script中的一段在一定條件下反復執(zhí)行的代碼。bash shell中常用的loop有如下三種:for while until for loop是從一個清單列表中讀進變量值,并“依次”的循環(huán)執(zhí)行do到done之間的命令行。例: for var in one two three four five
do
echo -----------
echo '$var is '$var
echo
done
上例的執(zhí)行結果將會是: - 1.for會定義一個叫var的變量,其值依次是one two three four five。
- 2.因為有5個變量值,因此do與done之間的命令行會被循環(huán)執(zhí)行5次。
- 3.每次循環(huán)均用echo產(chǎn)生三行句子。而第二行中不在hard quote之內(nèi)的$var會依次被替換為one two three four five。
- 4.當最后一個變量值處理完畢,循環(huán)結束。
我們不難看出,在for loop中,變量值的多寡,決定循環(huán)的次數(shù)。然而,變量在循環(huán)中是否使用則不一定,得視設計需求而定。倘若for loop沒有使用in這個keyword來指定變量值清單的話,其值將從$@ (或$* )中繼承: for var; do
…
done
(若你忘記了positional parameter,請溫習第9章…) for loop用于處理“清單”(list)項目非常方便,其清單除了可明確指定或從positional parameter取得之外,也可從變量替換或命令替換取得…(再一次提醒:別忘了命令行的“重組”特性?。?/p> 然而,對于一些“累計變化”的項目(如整數(shù)加減),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,則執(zhí)行
do 與done 之間的命令,然后重新判斷while 后的return value。 - 若為false,則不再執(zhí)行do與done之間的命令而結束循環(huán)。
分析上例: - 1.在while之前,定義變量num=1。
- 2.然后測試(test)$num是否小于或等于10。
- 3.結果為true,于是執(zhí)行echo并將num的值加一。
- 4.再作第二輪測試,此時num的值為1+1=2,依然小于或等于10,因此為true,繼續(xù)循環(huán)。
- 5.直到num為10+1=11時,測試才會失敗…于是結束循環(huán)。
我們不難發(fā)現(xiàn):若while的測試結果永遠為true的話,那循環(huán)將一直永久執(zhí)行下去: while : ;do
echo looping…
done
上例的:是bash的null command,不做任何動作,除了送回true的return value。因此這個循環(huán)不會結束,稱作死循環(huán)。死循環(huán)的產(chǎn)生有可能是故意設計的(如跑daemon),也可能是設計錯誤。若要結束死循環(huán),可透過signal來終止(如按下ctrl-c)。(關于process與signal,等日后有機會再補充,十三問暫時略過。) 一旦你能夠理解while loop的話,那,就能理解until loop: 與while相反,until是在return value為false時進入循環(huán),否則結束。
因此,前面的例子我們也可以輕松的用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 這兩個命令常用在復合式循環(huán)里,也就是在do…done之間又有更進一層的loop,當然,用在單一循環(huán)中也未嘗不可啦…^_^ break 是用來打斷循環(huán),也就是“強迫結束”循環(huán)。若break后面指定一個數(shù)值n的話,則“從里向外”打斷第n個循環(huán),預設值為break 1 ,也就是打斷當前的循環(huán)。 在使用break時需要注意的是,它與return及exit是不同的:
- break是結束loop
- return是結束function
- exit是結束script/shell
而continue 則與break 相反:強迫進入下一次循環(huán)動作。若你理解不來的話,那你可簡單的看成:在continue 到done 之間的句子略過而返回循環(huán)頂端…與break相同的是:continue 后面也可指定一個數(shù)值n,以決定繼續(xù)哪一層(從里向外計算)的循環(huán),預設值為continue 1 ,也就是繼續(xù)當前的循環(huán)。 在shell script設計中,若能善用loop,將能大幅度提高script在復雜條件下的處理能力。請多加練習吧…. 好了,該是到了結束的時候了。 婆婆媽媽的跟大家羅唆了一堆關于shell的基礎概念,目的不是要告訴大家“答案”,而是要帶給大家“啟發(fā)”…在日后關于shell的討論中,我或許會經(jīng)常用“連接”方式指引回來十三問中的內(nèi)容,以便我們在進行技術探討時彼此能有一些討論基礎,而不至于各說各話、徒費時力。但,更希望十三問能帶給你更多的思考與樂趣,至為重要的是透過實作來加深理解。 是的,我很重視“實作”與“獨立思考”這兩項學習要素,若你能夠掌握其中真義,那請容我說聲: —-恭喜﹗十三問你沒白看了﹗^_^ p.s. 至于補充問題部份,我暫時不寫了。而是希望: Good luck and happy studying!
原文鏈接:https://segmentfault.com/a/1190000004325734 原文鏈接:http://bbs./thread-218853-1-1.html
注:由于微信文章字數(shù)限制,本文章設置為連載系列, 本篇是連載的最后一篇,感謝關注~
|