寫程式的時候,適當地列印出一些進度或者日誌資訊常常能協助我們跟蹤程式的運行結果。但是,這些結果或者日誌資訊列印到螢幕上並不能作為以後檢查問題的依據。這就是重新導向的作用,寫程式的時候,我們可以方便的將相關的資訊列印到螢幕或者是從鍵盤接收輸入(這樣的好處就是避免直接操作檔案),利用重新導向我們可以很方便地將輸入輸出重新導向到檔案或者其它地方。
1、檔案描述符(下面部分來自維基百科)
檔案描述符(File descriptor)是電腦科學中的一個術語,是一個用於表述指向檔案的引用的抽象化概念。
檔案描述符在形式上是一個非負整數。實際上,它是一個索引值,指向核心為每一個進程所維護的該進程開啟檔案的記錄表。當程式開啟一個現有檔案或者建立一個新檔案時,核心向進程返回一個檔案描述符。在程式設計中,一些涉及底層的程式編寫往往會圍繞著檔案描述符展開。但是檔案描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。
檔案描述符的優點主要有兩個:基於檔案描述符的I/O操作相容POSIX標準;在UNIX、Linux的系統調用中,大量的系統調用都是依賴於檔案描述符。檔案描述符的概念存在兩大缺點:在非UNIX/Linux作業系統上(如Windows NT),無法基於這一概念進行編程;由於檔案描述符在形式上不過是個整數,當代碼量增大時,會使編程者難以分清哪些整數意味著資料,那些意味著檔案描述符,因此,完成的代碼可讀性也就會變得很差,這一點一般通過消除魔術數字來解決。
對於ANSI C規範中定義的標準庫的檔案I/O操作。ANSI C規範給出了一個解決方案,就是使用FILE結構體的指標。事實上,UNIX/Linux平台上的FILE結構體的實現中往往都是封裝了檔案描述符變數在其中。
2、輸入輸出重新導向
2.1 stdin、stdout和stderr
簡單地說,檔案描述符是與已開啟檔案或裝置相關聯的整數,它們保持和已開啟檔案或裝置的關聯。最為常見的檔案描述符是stdin、stdout和stderr,它們分別是0、1、2,是系統保留的檔案描述符,分別對應標準輸入、標準輸出和標準錯誤。Unix系統會預設開啟這三個檔案描述符,並將stdin關聯到鍵盤,將stdout和stderr關聯到螢幕。這裡將的輸入輸出重新導向主要就是將這三個檔案描述符重新定向到其它我們希望的檔案或者裝置。對於stdin,stdout和stderr重新導向一般採用的操作符主要有<、>和>>,在沒有指定的具體檔案操作符的情況下,預設是這樣的:command < file.txt相當於command 0< file.txt,也就是說預設是將檔案重新導向到檔案描述符0;command > file.txt相當於command 1> file.txt也就是說預設將檔案描述符1重新導向到檔案,>>和>相同。如果要重新導向stderr,就要顯示指定,比如command 2> file.txt,將command的錯誤資訊輸出到file.txt。
2.2 重新導向輸入
為了避免每次手動輸入資料,我們可以將資料寫入一個檔案,然後使用重新導向將輸入重新導向到該檔案。這裡為了示範用的是linux的cat命令,cat命令如果不加參數,會讀取標準輸入並將其輸出到螢幕(實際上,cat用‘-’做參數也是這個效果,cat -),效果如下:
[lfqy@localhost ~]$ cat Hello, world!Hello, world!Ni hao!Ni hao!I'm unhappy.I'm unhappy.//這裡按下ctrl+D相當於EOF[lfqy@localhost ~]$
下面將上面的內容寫入檔案test.txt,然後將cat的輸入重新導向到該檔案,這裡用到的是"<",這個操作符:
[lfqy@localhost ~]$ cat <test.txt Hello, world!Ni hao!I'm unhappy.[lfqy@localhost ~]$
實際上,像上面說的cat 0<test.txt效果也一樣。當然,這裡只是用cat做一個例子,想用cat查看一個檔案的內容何須這麼麻煩。
2.2 重新導向輸出
2.2.1 重新導向標準輸入和標準錯誤
為了測試標準錯誤,首先建立了一個test目錄,然後在裡面建立了三個檔案f1.txt、f2.txt和f.txt。在f.txt中寫入上面的幾句話,然後將f1.txt和f2.txt的使用權限設定為000(chmod 000 f1.txt)。下面開始分別將標準輸出和標準錯誤重新導向。
1、沒有重新導向之前[lfqy@localhost test]$ cat f*cat: f1.txt: Permission deniedcat: f2.txt: Permission deniedHello, world!Ni hao!I'm unhappy.2、重新導向標準輸出[lfqy@localhost test]$ cat f* > stdout.txtcat: f1.txt: Permission deniedcat: f2.txt: Permission denied[lfqy@localhost test]$ cat stdout.txt Hello, world!Ni hao!I'm unhappy.3、重新導向標準輸出和標準錯誤[lfqy@localhost test]$ cat f* 2> stderr.txtHello, world!Ni hao!I'm unhappy.[lfqy@localhost test]$ cat stderr.txt cat: f1.txt: Permission deniedcat: f2.txt: Permission denied4、同時分別將標準輸出重新導向到stdout.txt,將標準錯誤重新導向到stderr.txt[lfqy@localhost test]$ rm stderr.txt stdout.txt [lfqy@localhost test]$ cat f* 2> stderr.txt >stdout.txt[lfqy@localhost test]$ cat stderr.txt stdout.txt cat: f1.txt: Permission deniedcat: f2.txt: Permission deniedHello, world!Ni hao!I'm unhappy.
如果輸出重新導向的檔案不存在,該檔案會預設被建立;如果檔案存在並有內容,其中原來的內容都會被清空。當然,如果想要新的重新導向的內容追加在原來內容的後面,可將上面的'>'換成'>>',可以達到效果。
2.2.2 將標準輸出和標準輸出同時重新導向到一個檔案
這個實現起來有好幾種辦法,方法如下:
1、cat f* &> stdall.txt[lfqy@localhost test]$ cat f* &> stdall.txt[lfqy@localhost test]$ cat stdall.txt cat: f1.txt: Permission deniedcat: f2.txt: Permission deniedHello, world!Ni hao!I'm unhappy.2、cat f* 1> stdall0.txt 2>&1[lfqy@localhost test]$ cat f* 1> stdall0.txt 2>&1[lfqy@localhost test]$ cat stdall0.txt cat: f1.txt: Permission deniedcat: f2.txt: Permission deniedHello, world!Ni hao!I'm unhappy.反例:[lfqy@localhost test]$ cat f* 2>&1 1> stdall0.txtcat: f1.txt: Permission deniedcat: f2.txt: Permission denied
注意上面的反例,不能是cat f* 2>&1 1> stdall0.txt(因為如果先將標準錯誤重新導向到標準輸出,而這時標準輸出沒有被重新導向,仍然是輸出到螢幕,所以會導致將標準錯誤重新導向到螢幕。),而必須是cat f* 1> stdall0.txt 2>&1(先將標準輸出重新導向到檔案,這時候,所有到標準輸出的內容都會被重新導向到檔案,因此後面重新導向到標準輸出的標準錯誤也會輸出到檔案。)。
上面1用的是一個稍微特殊的操作符&>可以同時重新導向標準輸出和標準錯誤;2是將標準錯誤重新導向到標準輸出,然後重新導向標準輸出來達到目的。二者的實現思路稍有不同。這裡之所以要用cat f* 1> stdall0.txt 2>&1,之所以要在1前面加一個&,是因為1作為一個系統預設建立的檔案描述符,要用&來引用它,下面應該也會講到。
2.2.3 新技能get
(1) 丟棄不想要的輸出
有一個類似於垃圾桶的裝置檔案:/dev/null。將不想要的輸出重新導向到該檔案就可以了。
1、丟棄標準輸出[lfqy@localhost test]$ cat f* > /dev/nullcat: f1.txt: Permission deniedcat: f2.txt: Permission denied2、丟棄標準錯誤[lfqy@localhost test]$ cat f* 2> /dev/nullHello, world!Ni hao!I'm unhappy.3、都丟棄(下面三種效果相同)[lfqy@localhost test]$ cat f* 2> /dev/null 1>/dev/null[lfqy@localhost test]$ cat f* &> /dev/null [lfqy@localhost test]$ cat f* 1>/dev/null 2>&1
(2)既儲存輸出到檔案,又讓輸出顯示在螢幕
有時候,我們要將程式的輸出存檔,而又想看到程式的輸出(上面的重新導向沒辦法看到輸出,只能後來查看檔案)。這裡,用到一個命令tee,它能夠讀取標準輸入的內容,然後將其既輸出到標準輸出又寫入到檔案(tee - read from standard input and write to standard output and files)。這裡要注意的是,這裡需要將命令的輸出送到管道,然後由tee來接收管線的輸入,而只有標準輸出的內容能夠經過管道,所以先要將標準錯誤重新導向到標準輸出。
[lfqy@localhost test]$ cat f* 2>&1 | tee tee.txtcat: f1.txt: Permission deniedcat: f2.txt: Permission deniedHello, world!Ni hao!I'm unhappy.[lfqy@localhost test]$ cat tee.txt cat: f1.txt: Permission deniedcat: f2.txt: Permission deniedHello, world!Ni hao!I'm unhappy.
這裡會自動建立不存在的檔案,並清空已存在檔案原來的內容,如果想用追加模式,請使用cat f* 2>&1 | tee -a tee.txt。
3、建立自訂的檔案描述符
0、1和2是系統保留的檔案描述符,我們也可以自己建立自訂的檔案描述符。自訂的檔案描述符主要有三種模式:唯讀模式,截斷模式和追加模式。假定已經建立了檔案描述符num,可以使用&num來引用它,上文中用過&1就是這個道理。
3.1 唯讀模式
使用"exec num<filename"建立唯讀模式的檔案描述符;唯讀方式的檔案描述符只能讀取一次,如需二次讀取,需要對檔案描述符重新建立。
[lfqy@localhost test]$ exec 5<f.txt[lfqy@localhost test]$ cat <&5Hello, world!Ni hao!I'm unhappy.[lfqy@localhost test]$ cat <&5//二次讀取的時候沒有任何輸出
3.2 截斷模式
截斷模式就是指在建立該檔案描述符的時候,對應檔案中原來的內容將會被清空。使用"exec num>filename"的模式建立截斷模式截斷模式的檔案描述符建立之後,所有輸出到該檔案描述符的內容都會依次追加在對應檔案的後面,只是該檔案描述符建立之前的內容被清空了。
[lfqy@localhost test]$ cat f.txt Hello, world!Ni hao!I'm unhappy.[lfqy@localhost test]$ exec 7>f.txt[lfqy@localhost test]$ cat f.txt [lfqy@localhost test]$ echo "Love" >&7[lfqy@localhost test]$ echo "Happy" >&7[lfqy@localhost test]$ cat f.txt LoveHappy
3.3 追加模式
使用"exec num>>filename"建立追加模式的檔案描述符,和截斷模式不同,在建立該檔案描述符的時候,對應檔案中原來的內容不會被清空,所有輸出到該檔案描述符的內容都會依次追加在對應檔案的後面。
[lfqy@localhost test]$ cat f.txt LoveHappy[lfqy@localhost test]$ exec 8>>f.txt[lfqy@localhost test]$ cat f.txt LoveHappy[lfqy@localhost test]$ echo "Friendship" >&7[lfqy@localhost test]$ echo "Flair" >&7[lfqy@localhost test]$ cat f.txt LoveHappyFriendshipFlair
好吧,關於檔案描述符今天就到這裡了,後面學到新的內容會再補上。