探討shell命令中 >/dev/null 2>&1的實現原理

首先標準輸入,標準輸出,標準錯誤:
  • 標準輸入是程序能夠讀取其輸入的位置。缺省狀況下,進程從鍵盤讀取 stdin
  • 標準輸出是程序寫入其輸出的位置。缺省狀況下,進程將 stdout 寫到終端屏幕上。
  • 標準錯誤是程序寫入其錯誤消息的位置。缺省狀況下,進程將 stderr 寫到終端屏幕上。

爲何有這三個很重要的概念呢?咱們知道,一個程序要運行,須要有輸入、輸出,若是出錯,還要能表現出自身的錯誤。這是就要從某個地方讀入數據、將數據輸出到某個地方,出錯了還要把錯誤給弄到一個地方去.這就夠成了數據流(stream)。因此一般狀況,每一個 Unix 程序在啓動時都會打開三個流,一個用於輸入,一個用於輸出,一個用於打印診斷或錯誤消息。web

有了這三個概念.shell

再說說重定向:網絡

數據流重導向(重定向)就是將某個指令(命令)執行後的執行返回值,通常這些返回值就是你執行完後出如今屏幕上那些結果數據,若是我不想讓他默認流向屏幕.那麼我能夠把這些結果數據傳輸到其餘的地方,例如文件或者裝置(例如打印機,不過在Linux裏面一切都一切都是文件,因此打印機這樣的設備也是文件咯).這樣數據就跑被我導向其餘地方了.你懂的.因此東西都輸出到屏幕,若是數據太多太亂.咱們也受不了啊.並且屏幕的terminal一關.東西就再也找不到了.若是我重定向到一個文件.這樣就能夠長期保存執行的日誌了.spa

  • >數據流重導向:輸出導向,會替換被導向的文件內容.
  • >>數據流重導向:輸出導向,不會替換被導向的文件內容.會在屁股後面累加數據.

繼續看看文件描述符:操作系統

維基百科,自由的百科全書上面是這樣說的,文件描述符(File descriptor)是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫每每會圍繞着文件描述符展開。可是文件描述符這一律念每每只適用於UNIX、Linux這樣的操做系統。文件描述符的優勢主要有兩個:基於文件描述符的I/O操做兼容POSIX標準。在UNIX、Linux的系統調用中,大量的系統調用都是依賴於文件描述符。看來這東西還真的有點抽象.也就是說若是程序不打開,文件孤單的在磁盤上面的時候是沒有文件描述符的.能夠想象一下.第一個打開的文件是0,第二個是1,依此類推。Unix 操做系統一般給每一個進程能打開的文件數量強加一個限制。更甚的是,unix 一般有一個系統級的限制。固然真是的狀況是0,1,2通常已經被某些概念佔用.再加上系統啓動後已經不知道打開了多少文件.因此.咱們本身通常打開文件的時候描述符估計也已經到很大的數據了.可是這文件描述符缺點也是有的.好比完成的代碼可讀性也就會變得不好.你想啊.0,1,2....22231是知道是啥玩意兒?還好POSIX 定義了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 來代替 0、一、2。這三個符號常量的定義位於頭文件 unistd.h。文件描述符的有效範圍是 0 到 OPEN_MAX。通常來講,每一個進程最多能夠打開 64 個文件(0 — 63)。對於 FreeBSD 5.2.一、Mac OS X 10.3 和 Solaris 9 來講,每一個進程最多能夠打開文件的多少取決於系統內存的大小,int 的大小,以及系統管理員設定的限制。Linux 2.4.22 強制規定最多不能超過 1,048,576 。.net

綜合上面的基本概念:下面的也就不難理解了.命令行

標準輸入 (stdin) :文件描述符爲 0 ,使用 < 或 << ;(你不會非要寫成0<或0<<吧.其實這個也沒錯.不過太累人了)其實能夠理解爲這個箭頭指向哪裏數據就往哪裏跑.這裏是輸入(stdin).命令就經過<來獲取數據.等於數據是從左邊往命令裏面流.設計

標準輸出 (stdout):文件描述符爲 1 ,使用 > 或 >> ;(你不會非要寫成1>或1>>吧.其實這個也沒錯.不過太累人了)輸出的時候當如不能用<或者<<,由於命令老是在前面嘛.這裏命令要輸出數據.因此數據的來源是命令,數據就會隨着箭頭指向你給的方向.unix

標準錯誤輸出(stderr):文件描述符爲 2 ,使用 2> 或 2>>;日誌

再舉例說明:

首先command >file 2>file 的意思是將命令所產生的標準輸出信息,和錯誤的輸出信息送到file中.command >file 2>file 這樣的寫法,stdout和stderr都直接送到file中, file會被打開兩次,這樣stdout和stderr會互相覆蓋,這樣寫至關使用了兩個同時去搶佔file的管道.定向了2次.

那若是使用command >file 2>&1 這條命令就將stdout直接送向file, stderr 繼承了第一次重定向(FD1)到管道後,再被送往file,此時,file 只被打開了一次,也只使用了一個管道FD1,它包括了stdout和stderr的內容.還能夠這樣理解.想是把file用管道接通了標準輸出.而後把2表明的標準錯誤輸出接到1表明的標準信息輸出上面.就都通向了file了.

 從IO效率上,前一條命令的效率要比後面一條的命令效率要低,因此在編寫shell腳本的時候,較多的時候咱們會用command > file 2>&1 這樣的寫法.

在看看一個實例(加深相關的理解,此實例引用網上博客.說是intel的筆試題):

問題:下面程序的輸出是什麼?(intel筆試2011)

int main(){
  fprintf(stdout,"Hello ");
  fprintf(stderr,"World!");
  return0;
}

而後發現輸出是
World!Hello
而不是:
Hello World!

這是爲何呢?在默認狀況下,stdout是行緩衝的,他的輸出會放在一個buffer裏面,只有到換行的時候,纔會輸出到屏幕。而stderr是無緩衝的,會直接輸出,舉例來講就是printf(stdout, "xxxx") 和 printf(stdout, "xxxx\n"),前者會憋住,直到遇到新行纔會一塊兒輸出。而printf(stderr, "xxxxx"),無論有麼有\n,都輸出.

最後:

看看什麼叫/dev/null

UFO@UFO~:cd /dev
UFO@UFO :/dev$ls -l null
crw-rw-rw-  1 root root 1, 3 Feb 14  2012 null

看到了吧?是個字符設備文件(c).而這個東西呢?你能夠叫他"黑洞", Blackhole?NO.不是天文學裏面的黑洞.它很是等價於一個只寫文件. 全部寫入它的內容都會永遠丟失. 而嘗試從它那兒讀取內容則什麼也讀不到. 然而, /dev/null 對命令行和腳本都很是的有用.

再來看看在glibc庫的stdio.h頭文件中:

#define    stdin    (&__sF[0])
#define    stdout    (&__sF[1])
#define    stderr    (&__sF[2])

好比

fprintf(stderr, "UFO\n");//那麼將把"UFO"做爲標準錯誤輸出

在shell命令中,0,1和2分別對應glibc中的stdin,stdout和stderr,上面咱們已經大概瞭解到了:

  • 0 對應stdin     即標準輸入
  • 1 對應stdout    即標準輸出
  • 2 對應stderr    即標準錯誤輸出

因此>/dev/null表示將程序經過printf或者fprintf打印到handle爲1的stdout文件的信息,送到/dev/null空洞文件,/dev/null節點對應的kernel實現就是直接返回寫入的字節數,因此程序認爲成功存儲到/dev/null了,可是>/dev/null這個操做不能將fprintf(stderr, "UFO\n")打印到stderr上的字符串送到>/dev/null下,因此必須使用2>&1命令,表示shell將送到2 stderr中的數據轉送到1 stdout中,因此這樣stderr中會顯示到terminal上的信息也將被轉送到/dev/null下了.

又看個實例吧:

luther@gliethttp:~$ cat a.c
#include <stdio.h>
int main(int argc, char *argv[])
{
    fprintf(stdout,"luther stdout\n");
    fprintf(stderr,"luther stderr\n");
    return 0;
}
luther@gliethttp:~$ gcc a.c
luther@gliethttp:~$ ./a.out
luther stdout
luther stderr
luther@gliethttp:~$ ./a.out >/dev/null
luther stderr//能夠看到>/dev/null操做並不會將stderr信息送到/dev/null下

寫一個test.sh腳本

luther@gliethttp:~$ chmod +x test.sh
luther@gliethttp:~$ cat test.sh
exec ./a.out >/dev/null
luther@gliethttp:~$ ./test.sh
luther stderr       //能夠看到shell也不會將stderr信息送到/dev/null下
luther@gliethttp:~$ cat test.sh
exec ./a.out >/dev/null 2>&1
luther@gliethttp:~$ ./test.sh
luther@gliethttp:~$ //什麼也沒有輸出,stderr信息被2>&1命令成功變爲stdout信息,進而送入了/dev/null淹沒
luther@gliethttp:~$ cat test.sh
exec ./a.out >/dev/null 1>&2
./test.sh
luther stdout
luther stderr
luther@gliethttp:~$

固然對於tee操做也一樣存在如上問題,若是打印到stderr的log將不能被tee操做捕獲,因此能夠將stderr重定向到stdout來解決這個問題,

繼續看看實例:

luther@gliethttp:~$ ./a.out
luther stdout
luther stderr
luther@gliethttp:~$ ./a.out|tee luther.txt
luther stdout
luther stderr
luther@gliethttp:~$ cat luther.txt
luther stdout
luther@gliethttp:~$ ./a.out 2>&1|tee luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ cat luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ ./a.out 1>&2|tee luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ cat luther.txt
luther@gliethttp:~$ //啥東西都沒有,由於stdout被定向到stderr,因此全部log信息都不能被tee捕獲
對於1>&2由於做爲一個命令將被shell解析,因此放在哪裏均可以,1>&2將影響到該組命令中全部的log輸出,好比:
luther@gliethttp:~$ 1>&2 ./a.out|tee luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ ./a.out 1>2 //只輸出stderr
luther stderr
luther@gliethttp:~$ ./a.out 2>1 //只輸出stdout
luther stdout
OK.不想寫了.忙的很.上面的文章我只是添油加醋的把網絡上不少由於大抄而散落各地的文章亂七八糟的整合了一下.因此我是站在巨人的肩上來了一次大抄.感謝那些巨人吧.其實上面提到的概念估計就這篇整合文章也不能徹底解釋的很是完全.相關的概念更多的還在於實踐和看文檔再加本身的大腦過濾與思考.但願和你們探討!
相關文章
相關標籤/搜索