04 Linux Shell 文件描述符 及 stdin stdout stderr 重定向


1) Linux Shell 命令的標準輸入、標準輸出、標準錯誤,及其重定位;
2)Linux Shell 操作自定義文件描述符;

    文件描述符是與文件相關聯的一些整數,他們保持與已打開文件的關聯。衆所周知的文件描述符是標準輸入stdin、標準輸出stdout、標準錯誤stderr,我們可以重定位這些文件描述符關聯文件的內容到另外一個文件文件描述符。

1. Linux Shell 命令的標準輸入、標準輸出、標準錯誤
    當我們在編寫 shell 腳本時,我們會非常頻繁地操作執行命令的標準輸入stdin、標準輸出stdout、標準錯誤stderr。過濾 shell 腳本或者執行命令的輸出信息並且把它們重定位到特定的地方,成了我們分析腳本執行情況的必要工作。當我們執行腳本文件或者執行一個 shell 命令的時候,單從終端輸出我們很難區分哪些是標準輸出,哪些是標準錯誤。然而,我們把這些信息重定向特定的地方,以便於我們分析腳本文件及 shell 命令的執行情況。

文件描述符是與打開文件或者數據流相關聯的整數,0、1、2 是系統保留的三個文件描述符,分別對應標準輸入、標準輸出、標準錯誤
Linux Shell 使用 " > "  ">>"  進行對文件描述符進行重定位。
">"  與 ">>"  的作用是不一樣的,前者使用本次輸出內容替換原有文件的內容,後者則是把本次輸出追加到原文件的後面。


在對文件描述符進行詳細分析之前,先執行以下命令,爲後面的實驗操作準備一些必要的文件:

  1. echo -e "\e[42;31m --- Prepare resource file ---\e[0m";
  2. echo "This is example text 1." > temp.txt;
  3. echo "This is example text 2." >> temp.txt;
  4. cat temp.txt;
  5. echo "a1" > a1;
  6. echo "a2" > a2;
  7. echo "a3" > a3;
  8. sudo chmod 000 a3;
  9. ls -alF a*;


1.1 獲取 Linux Shell 命令執行結果
Linux Shell 中每一條 shell 命令的執行都有返回值,用以返回命令的執行結果。
在命令執行結束後,通過引用 $? 獲取最後一條命令執行的返回值,當返回值爲零代表命令執行成功。

  1. echo -e "\e[42;31m --- Get command execution result! ---\e[0m";
  2. ls + ;
  3. echo -e "\e[1;32m --- 'ls + ' executed and return value is: $? \e[0m";
  4. ls -alF ./ ;
  5. echo -e "\e[1;32m --- 'ls -alF ./' executed and return value is: $? \e[0m";


由於 “ ls + ” 的輸入參數 “ + ”是一個 “ls” 命令不能接受的參數,所以由上面的執行結果可以看到命令執行的錯誤信息以及命令執行的返回值爲: 2 ;
接下來的命令 “ls -alF” 是一條合法的命令,所以,我們看到它成功執行的返回值爲:0 ;

重定位運算符 ">" ">>" 的默認參數爲標準輸出 stdout ,即 1 ;
所以 ">" 等價於 "1>"; ">>" 等價於 "1>>"

1.2 Linux Shell 標準輸出 stdout 的重定位

  1. echo -e "\e[42;31m --- Redirect stdout ! ---\e[0m";
  2. ls + > out.txt;
  3. echo -e "\e[1;32m --- 'ls + > out.txt' executed and return value is: $? \e[0m";
  4. echo -e "\e[1;32m --- Contents of out.txt as following descriptions:\e[0m \e[1;33m";
  5. cat out.txt;
  6. echo -e "\e[0m";
  7. rm out.txt;

由實驗結果可以看出標準輸出 stdout 重定向輸出文件 out.txt 的內容爲空;
因爲 “ ls + ”命令的輸入參數 “+” 爲非法參數,所以命令執行失敗,沒有輸出結果,並且同時看到命令執行返回值爲: 2 ,非零,表示執行失敗。

1.3 Linux Shell 標準錯誤 stderr 的重定位

  1. echo -e "\e[42;31m --- Redirect stderr ! --- \e[0m";
  2. ls + 2> out.txt;
  3. echo -e "\e[1;32m 'ls + 2> out.txt' executed and return value is: $? \e[0m";
  4. echo -e "\e[1;32m Contents of out.txt as following descriptions:\e[0m \e[1;33m";
  5. cat out.txt;
  6. echo -e "\e[0m";
  7. rm out.txt;


由實驗結果看出非法命令 "ls + 2> out.txt" 的執行並沒有任何的輸出,與之前的姐果一樣;
不同的是,錯誤信息也沒有在命令執行的時候輸出到終端上面,而是被重定向到了文件 out.txt 裏面;

1.4 Linux Shell 分別重定向標準輸出 stdin 與標準錯誤 stderr 到指定的文件:

  1. echo -e "\e[42;31m --- Redirect stdout and stderr to exclusive files ! ---\e[0m";
  2. cat a* 2>stderr.txt 1>stdout.txt;
  3. echo -e "\e[1;32m'cat a* 2>stderr.txt 1>stdout.txt' executed and return value is: $? .\e[0m";
  4. echo -e "\e[1;32mContents of stderr.txt is:\e[0m \e[1;33m";
  5. cat stderr.txt;
  6. echo -e "\e[0m";
  7. echo -e "\e[1;32mContents of stdout.txt is:\e[0m \e[1;33m";
  8. cat stdout.txt;
  9. echo -e "\e[0m";
  10. rm stderr.txt stdout.txt;


這次的執行中無論是標準輸出 stdout 與標準錯誤 stderr 都沒有在命令執行時輸出到終端上,只是分別輸出到 stdout.txt 與 stderr.txt 中;
由於 a3 是一個沒有讀、寫、執行命令權限的文件,所以當 cat a3 的時候就會有錯誤信息輸出到 stderr.txt ;
a1、a2 則把文件內容輸出到 stdout.txt ;

1.5 Linux Shell 重定向標準錯誤到標準輸出:
1.5.1 使用 " 2>&1" 把標準錯誤 stderr 重定向到標準輸出 stdout ;

  1. echo -e "\e[42;31m --- Redirect stderr to stdout approach first! ---\e[0m";
  2. cat a* > output.txt 2>&1;
  3. echo -e "\e[1;32m'cat a* 2>&1 output.txt' executed and return value is: $?\e[0m";
  4. echo -e "\e[1;32mContents of output.txt is:\e[0m \e[1;33m";
  5. cat output.txt;
  6. echo -e "\e[0m";
  7. rm output.txt;


由上面的實驗結果看出, 無論是標準輸出 stdin 還是標準錯誤 stderr ,都被重定向到了 output.txt 裏面。

1.5.2 使用 "&>" 把標準錯誤 stderr 重定向到標準輸出 stdout ;

  1. echo -e "\e[42;31m --- Redirect stderr to stdout approach second ! ---\e[0m";
  2. cat a* &> output.txt;
  3. echo -e "\e[1;32m'cat a* &> output.txt' executed and return value is: $?\e[0m";
  4. echo -e "\e[1;32mContents of output.txt is:\e[0m \e[1;33m";
  5. cat output.txt;
  6. echo -e "\e[0m";
  7. rm output.txt

由實驗結果,標準錯誤 stderr 被轉化爲標準輸出 stdout ,並重定向到了 output.txt 中;

1.6 把標準錯誤 stderr 重定向到 /dev/null 

  1. echo -e "\e[42;31m --- Redirect stderr to /dev/null ! ---\e[0m";
  2. cat a* 2>/dev/null;
  3. echo -e "\e[1;32m'cat a* 2>/dev/null' executed and return value is $?\e[0m";


"/dev/null" 是一個特殊的設備文件,所有輸入到其中的比特流都會被丟棄;
由實驗結果看出,標準輸出如常輸出到終端上,而標準錯誤則沒有輸出,被重定位到 "/dev/null" 。

1.7 使用 tee 命令,實現重定向與終端雙輸出
當我們使用重定向輸出時,所有信息都被重定向輸出到我們指定的文件描述符,終端上面就再也看不到這些信息了;
我們可以通過 tee 命令,實現把信息重定向輸出到文件的同時輸出到終端,附上 tee 的使用說明:

tee 命令會從標準輸出接受信息,把信息保存到指定的文件同時,把信息輸出到標準輸出;

1.7.1 把標準輸出 stdout 重定向到 tee 的標準輸入 stdin :

  1. echo -e "\e[42;31m --- Redirect stdout as stdin of tee! --- \e[0m";
  2. cat a* | tee out.txt | cat -n;
  3. echo -e "\e[1;32m'cat a* | tee out.txt | cat -n' executed and return value is: $?\e[0m";
  4. echo -e "\e[1;32mContents of out.txt is:\e[0m \e[1;33m";
  5. cat out.txt;
  6. echo -e "\e[0m";
  7. rm out.txt;



"cat a*" 的標準輸出 stdout 通過pipe( "|" )重定向爲 tee 的標準輸入;
tee 把這些信息重定向到 out.txt 的同時,輸出到 tee 的標準輸出 stdout ;
tee 的標準輸出通過 pipe("|") 重定向到了 "cat -n" 的標準輸入;

所以以上實驗結果應該這樣看:
1)因爲pipe("|") 只處理標準輸出的信息,所以 cat a3 的錯誤信息依舊在終端直接輸出,沒有被重定向處理;
2)tee 從它的標準輸入 stdin 只接收到 a1、a2 的內容,所以我們通過 "cat -n" 給輸出信息加上行號來標識出 tee 接收到的信息;
3)再通過查看 out.txt 文件,發現 tee 命令輸出到 out.txt 的信息,與 "cat -n"的信息是一致的;

1.7.2 把標準錯誤 stderr 轉換成標準輸出 stdout ,一同輸出到 tee 的標準輸入,以便把所有信息重定向到文件的同時,輸出到終端:

  1. echo -e "\e[42;31m --- Redirect stdout burden with stderr as stdin of tee! --- \e[0m";
  2. cat a* 2>&1 | tee out.txt | cat -n;
  3. echo -e "\e[1;32m'cat a* 2>&1 | tee out.txt | cat -n' executed and return value is: $?\e[0m"
  4. echo -e "\e[1;32mContents of out.txt is:\e[0m \e[1;33m";
  5. cat out.txt;
  6. echo -e "\e[0m";
  7. cat a* 2>&1 | tee -a out.txt | cat -n;
  8. echo -e "\e[1;32m'cat a* 2>&1 | tee -a out.txt | cat -n' executed and return value is: $?\e[0m"
  9. echo -e "\e[1;32mContents of out.txt is:\e[0m \e[1;33m";
  10. cat out.txt;
  11. echo -e "\e[0m";
  12. rm out.txt;


前面提到的 " 2>&1 " 參數可以把命令的標準錯誤轉換爲標準輸出,這裏利用一下,舉個實用的例子,呵呵!
tee 命令會把輸出文件之前的內容沖掉,再把自己的輸出信息輸出到文件,但可以使用 " -a " 參數,實現追加方式把信息重定向到指定文件。

1.8 重定位標準輸入 stdin 
1.8.1 把重定向的標準輸入作爲命令的輸入參數:
先來看看我們貌似已經十分熟悉的 cat 命令的幫助,不知道各位是否留意到有這樣一招,反正我之前是沒有留意到,呵呵!!!

原來 cat 使用 "-" 作爲參數,就是把它接受的標準輸入stdin 直接輸出到它的標準輸出;
呵呵!只能說之前學藝不精,沒看到啊!
這裏也學會了一點,就是在看linux 下命令的 help 時,我們要多加留心一下是否接受 stdin ;
如果接受 stdin 作爲輸入,這些命令的擴展用法就可以多好多咯。。。
好,言歸正傳,來一段實踐代碼再說,有圖有真相嘛,呵呵!!!

  1. echo -e "\e[42;31m --- Use stdin as a command argument ! --- \e[0m";
  2. ls -alF | cat;
  3. echo -e "\e[1;32m'ls -alF | cat' executed and return value is: $?\e[0m"
  4. ls -alF | cat -;
  5. echo -e "\e[1;32m'ls -alF | cat -' executed and return value is: $?\e[0m";
  6. ls -alF | cat -n;
  7. echo -e "\e[1;32m'ls -alF | cat -n' executed and return value is: $?\e[0m";
  8. ls -alF | cat /dev/stdin;
  9. echo -e "\e[1;32m'ls -alF | cat /dev/stdin' executed and return value is: $?\e[0m"



這裏要提提的就是 "-" 等價於 /dev/stdin ,你懂的。。。

1.8.2 從文件重定向 shell 命令的標準輸入:
Linux Shell 使用 "cmd < FILE " 的方式從一個文件重定向命令的標準輸入;

  1. echo -e "\e[42;31m --- Redirect stdin from file ! --- \e[0m";
  2. cat < a1
  3. echo -e "\e[1;32m'cat < a1' executed and return value is: $?\e[0m";
  4. cat - < a2;
  5. echo -e "\e[1;32m'cat - < a2 executed and return value is: $?\e[0m";
  6. cat /dev/stdin < temp.txt;
  7. echo -e "\e[1;32m'cat /dev/stdin < temp.txt' executed and return value is: $?\e[0m";


上面的 3 條執行命令把 a1、a2、a3 三個文件的內容重定向到命令的標準輸入 stdin 。

1.8.3 把文本塊重定向爲 shell 命令的標準輸入 stdin :
有時候,我們需要把一個文本塊(多行的文本)重定向給一個 shell 命令作爲標準輸入。
我們假設有這樣一種情況,文本塊來源於一個 shell 執行腳本里面,在這個 shell 腳本執行的時候,需要把這個文本塊輸出到一個文件;
如這樣一個例子,一個 shell 腳本需要把它的執行 log 寫到一個文件裏面,
在文件的開頭,需要加入幾行信息作爲這個 log 文件的開頭用以註釋一下這個文件,我們可以如下操作:

  1. echo -e "\e[42;31m --- Redirecting from a text block to a file within a script! --- \e[0m";
  2. cat <<EOF>log.txt
  3. Hello
  4. EOF
  5. echo -e "\e[1;32m'cat < log.txt Hello world! EOF' executed and return value is: $?\e[0m";
  6. echo -e "\e[1;32mContents of log.txt as following: \e[0m \e[1;33m";
  7. cat log.txt;
  8. echo -e "\e[0m";
  9. echo -e "\e[42;31m --- Redirecting from a text block to append into a file within a script! --- \e[0m";
  10. cat <<EOF >> log.txt
  11. Nice to meet
  12. EOF
  13. echo -e "\e[1;32m'cat <> log.txt Nice to meet you! EOF' executed and return value is $?\e[0m";
  14. echo -e "\e[1;32mContents of log.txt as following: \e[0m \e[1;33m";
  15. cat log.txt;
  16. echo -e "\e[0m";
  17. rm log.txt;


1)使用 "<text block   EOF" 的形式把一個 text block 重定向到一個 shell command 的標準輸入;
2)上述實驗把 text block 重定向給 cat 命令的標準輸入, cat 再把它自己的標準輸出 stdout 重定向到 log.txt ;
3)上述實驗採取了 "> log.txt" 與 ">> log.txt" 兩種方式對 cat 的標準輸出 stdout 進行重定向到 log.txt ,
    前者是重新創建文件進行信息的輸出,後者是以追加的方式進行重定向輸出;

2. 用戶自定義文件描述符:
現在我們知道 0、1、2是 shell 保留的文件描述符;
另外,我們還可以定義自己的文件描述符,並對其進行讀寫;
用戶自定義文件描述符使用 "exec" 命令進行創建;
用戶自定義文件描述符的創建有三種形式:
    1)只讀方式的文件描述符;
    2)截斷模式創建寫方式的文件描述符;
    3)追加模式創建寫方式的文件描述符;
2.1 以讀方式創建文件描述符:
使用 " exec descriptor<file Name 的模式創建只讀模式的用戶自定義文件描述符;
使用 "&descriptor" 的模式引用文件描述符;
只讀方式的文件描述符只能讀取一次,要再次讀取,需要對文件描述符重新打開賦值;
 
  1. echo -e "\e[42;31m --- Create a file descriptor for reading file! --- \e[0m";
  2. exec 3<a1;
  3. echo -e "\e[1;32m'exec 3;
  4. echo -e "\e[1;32mContents of a1 as following output:\e[0m \e[1;33m";
  5. cat <&3;
  6. echo -e "\e[0m";
  7. echo -e "\e[1;32mContents of a1 with file descriptor 3 to read again as following output:\e[0m \e[1;33m";
  8. cat <&3;
  9. echo -e "\e[0m";


如實驗結果,當我們再次打開文件對文件描述符重新賦值之前,我們再次使用文件描述符 3 對文件進行讀取時,沒有任何輸出內容。

2.2 以截斷模式創建寫方式的文件描述符:
所謂截斷模式,就是相當於重新創建文件,文件之前的內容將會丟失;
使用 " exec descriptor>file Name 的模式創建截斷模式的用戶自定義的寫方式的文件描述符;
使用 "&descriptor" 的模式引用文件描述符;
寫方式的文件描述符只要一次打開便可多次寫入,並且後續的寫入操作會一直追加到文件的結尾,但是文件打開之前的內容就會丟失

  1. echo -e "\e[42;31m --- Create a file descriptor for writing file in truncation mode!\e[0m";
  2. echo -e "\e[1;32mContents of a2 before writing it as following output: \e[0m \e[1;33m";
  3. cat a2;
  4. echo -e "\e[0m";
  5. exec 4>a2;
  6. echo -e "\e[1;32m 'exec 4>a2' executed to create a file descriptor 4 for writing.\e[0m";
  7. echo "newline in truncation mode" >&4;
  8. echo -e "\e[1;32m 'echo \"newline in truncation mode\" >&4' executed and return value is: $?\e[0m";
  9. echo -e "\e[1;32mContents of a2 after writing in truncation mode as follwing output: \e[0m \e[1;33m";
  10. cat a2;
  11. echo -e "\e[0m";
  12. echo "newline in truncation mode 2" >&4;
  13. echo -e "\e[1;32m 'echo \"newline in truncation mode 2\" >&4' executed and return value is: $?\e[0m";
  14. echo -e "\e[1;32mContents of a2 after writing twice in truncation mode as follwing output: \e[0m \e[1;33m";
  15. cat a2;
  16. echo -e "\e[0m";
  17. exec 4>a2;
  18. echo -e "\e[1;32m 'exec 4>a2' executed to reassign file descriptor 4 for writing\e[0m";
  19. echo "newline in truncation mode 3" >&4;
  20. echo -e "\e[1;32m 'echo \"newline in truncation mode 3\" >&4' executed and return value is: $?\e[0m";
  21. echo -e "\e[1;32mContents of a2 after reassign its file descriptor for writing in truncation mode: \e[0m \e[1;33m";
  22. cat a2;
  23. echo -e "\e[0m";

由上述實驗結果可以看到,我們引用 "&4" 文件描述符,並且對其進行多次寫入,後面的寫入內容會追加到之前寫入信息的後面;
當我們對文件描述符進行再次打開後,再引用 "&4" 文件描述符,並且對其進行寫入,發現之前兩次寫入的內容已經丟失,只剩下最後一次寫入的信息了。

2.3 以追加模式創建寫方式的文件描述符:
所謂追加模式,就是打開文件進行寫操作,文件之前的內容不會丟失,寫操作寫入的內容以追加方式添加到文件的末尾;
使用 " exec descriptor>>file Name 的模式創建追加模式的用戶自定義的寫方式的文件描述符;
使用 "&descriptor" 的模式引用文件描述符;
寫方式的文件描述符只要一次打開便可多次寫入,並且後續的寫入操作會一直追加到文件的結尾;


  1. echo -e "\e[42;31m --- Create a file descriptor for writing file in append mode!\e[0m";
  2. echo -e "\e[1;32mContents of temp.txt before writing it as following output: \e[0m \e[1;33m";
  3. cat temp.txt;
  4. echo -e "\e[0m";
  5. exec 5>>temp.txt;
  6. echo -e "\e[1;32m 'exec 5>>temp.txt' executed to create a file descriptor 5 for appending.\e[0m";
  7. echo "newline in append mode" >&5;
  8. echo -e "\e[1;32m 'echo \"newline in append mode\" >&5' executed and return value is: $?\e[0m";
  9. echo -e "\e[1;32mContents of temp.txt after writing in append mode as follwing output: \e[0m \e[1;33m";
  10. cat temp.txt;
  11. echo -e "\e[0m";
  12. echo "newline in append mode 2" >&5;
  13. echo -e "\e[1;32m 'echo \"newline in append mode 2\" >&5' executed and return value is: $?\e[0m";
  14. echo -e "\e[1;32mContents of temp.txt after writing twice in append mode as follwing output: \e[0m \e[1;33m";
  15. cat temp.txt;
  16. echo -e "\e[0m";
  17. exec 5>>temp.txt;
  18. echo -e "\e[1;32m 'exec 5>>temp.txt' executed to reassign file descriptor 5 for writing: $?\e[0m";
  19. echo "newline in append mode 3" >&5;
  20. echo -e "\e[1;32m 'echo \"newline in append mode 3\" >&5' executed and return value is: $?\e[0m";
  21. echo -e "\e[1;32mContents of temp.txt after reassign its file descriptor for writing in append mode: \e[0m \e[1;33m";
  22. cat temp.txt;
  23. echo -e "\e[0m";

    

呵呵!經過這樣折騰了一番,算是對 Linux Shell 下面的文件描述符及其重定向,以及標準輸入 stdin、標準輸出 stdout 、標準錯誤 stderr 有了跟進一步的理解咯。。。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章