Linux重定向理解與代碼實證

虎哥數碼宅 發佈 2024-03-08T00:33:35.001560+00:00

當年在學習《Linux Shell腳本攻略》第一章時,到第1.6節「玩轉文件描述符及重定向」中,發現有一處言語不明(也或者是例子不當之處)。當時對這段百思不得其解,後來經仔細學習與思考,解決了問題。這個是當年筆記的匯總。1 文件描述符文件描述符在形式上是一個非負整數。


當年在學習《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);

結果是一樣的。

通上過述的一系類步驟,終於明白了。

關鍵字: