當年在學習《Linux Shell腳本攻略》第一章時,到第1.6節「玩轉文件描述符及重定向」中,發現有一處言語不明(也或者是例子不當之處)。
當時對這段百思不得其解,後來經仔細學習與思考,解決了問題。這個是當年筆記的匯總。
1 文件描述符
文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。在windows中,內核記錄應用打開的對象,就是handle句柄,也是一個整數值,兩者有相似之處。
在Linux中,在編寫腳本的時候會頻繁使用標準輸入( stdin)、標準輸出( stdout)和標準錯誤( stderr)。通過內容過濾將輸出重定向到文件是我們平日裡的基本任務之一。文件描述符是與某個打開的文件或數據流相關聯的整數。文件描述符0、 1以及2是系統預留的,相當於每個進程的全局變量。
2 重定向試驗與理解
於是開始按照示例來試。
~$ echo "A sample test" echo命令把這段文字輸出到 stdout
A sample test
~$ echo "A sample test to out.txt" > out.txt
上面一行命令,把本來要輸出到stdout文件(屏幕),重定向到磁碟文件out.txt中
~$ cat out.txt
A sample test to out.txt 顯然是重定向成功了
~$ echo "A sample test append to out.txt" >> out.txt
上面一行命令,把本來要輸出到stdout文件(屏幕),重定向到磁碟文件out.txt中,並且加到尾部
~$ cat out.txt
A sample test to out.txt
A sample test append to out.txt
顯然是重定向成功了,內容也成功的加到了尾部
前面這一部分非常簡單。
~$ ls +abcd 故意讓這個命令出錯,出錯信息會輸出到stderr
ls: 無法訪問 '+abcd': 沒有那個文件或目錄 出錯信息
$ ls +abcd >stderr.txt
ls: 無法訪問 '+abcd': 沒有那個文件或目錄 出錯信息
~$ cat stderr.txt
這一行命令,什麼也不會輸出,因為出錯信息是輸出到stderr中的
~$ ls +abcd 2>stderr.txt
~$ cat stderr.txt
ls: 無法訪問 '+abcd': 沒有那個文件或目錄
出錯信息重定向到了stderr.txt中
重定向的命令語法是:cmd [參數] 文件描述符>文件
如果「文件描述符」不輸入的法,默認是1,也就是stdout。比如:echo "a test" > out.txt, 相當於:echo "a test" 1> out.txt。所以上面的「ls +abcd 2>stderr.txt」才能把出錯信息重定向到stderr.txt文件中。紅字「2」就是指stderr。
3 多重定向命令的理解
如上圖,執行「ls + 2>stderr.txt 1>stdout.txt」後,這個命令是想達到:stderr單獨重定向到磁碟文件stderr.txt,將stdout重定向到另一個磁碟文件stdout.txt。結果:可以cat到stderr.txt有內容,但stdout.txt沒有任何內容。
沒有得到合適的觀察結果,讓人懷疑是錯了?
沒辦法,自製了一個命令,C代碼文件:redirection_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("This line out to stdout \n"); //輸出到stdout
perror("This line out to stderr "); //輸出到stderr
return 0;
}
使用「gcc redirection_test.c -o redirection_test.out」編譯成可執行程序redirection_test.out。
$./redirection_test 2>stderr.txt 1>stdout.txt
這一行命令,故意讓這個命令出錯,出錯信息會輸出到stderr
$ cat stderr.txt
This line out to stderr : Success
$ cat stdout.txt
This line out to stdout
其實,腳本攻略上講的沒有錯,只是我沒用到合適的測試程序。「ls +」出錯後,只向stderr輸出信息,而沒有向stdout輸出信息,所以測不出來。
4 系統保留文件描述符
前面提到過:文件描述符0(stdin)、 1(stdout)以及2(stderr)是系統預留的,相當於每個進程的全局變量。
每個進程創建時,內核為進程打開的文件創建了一個文件描述符表,該表的每一條都記錄了進程打開的文件,這個表格的索引序號就是文件描述。
那麼,stdin、 stdout以及stderr,這三個妖怪在哪裡呢?
原來是躲藏在"/dev"目錄下面。看到沒有,它們分別指向內核的「/proc/self/fd/0(0/1/2)」處。至此,我挖到了它們,感覺真相大白。
5 驗證一下「/dev/stdout」的真身
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#define WRITE_STRING "0123456789ABC\n"
#define LEN_OF_WRITE_STR strlen(WRITE_STRING)
int main(int argc,char *argv[])
{
size_t write_len=0 ;
write_len = write(2,WRITE_STRING,LEN_OF_WRITE_STR);
if (write_len != LEN_OF_WRITE_STR)
{
printf("File : /dev/stdout write error\n") ;
exit(1);
}
printf("Write %d bytes to /dev/stdout \n",(int)write_len) ;
return 0;
}
使用「gcc main.c -o stdout_test.out」編譯成可執行程序:stdout_test.out。
我得到了以下驗證:
(1)stdout的序號是全局的,且默認打開的,因為這裡我沒有open動作。
(2)系統API函數write,把字符串寫到「/dev/stdout」,意味著輸出在屏幕上了。
仔細看看,上面的代碼沒有「/dev/stdout」,為何說是成功了呢?如果再想進一步驗證,可用以下代碼來替代前幾行。
fd = open("/dev/stdout",O_WRONLY);
if (fd < 0)
{
perror("perror: file open error ");
exit(1);
}
write_len = write(fd,WRITE_STRING,LEN_OF_WRITE_STR);
結果是一樣的。
通上過述的一系類步驟,終於明白了。