1. 程式人生 > 實用技巧 >shell 重定向以及檔案描述符

shell 重定向以及檔案描述符

1.對重定向的理解

  • Linux Shell重定向分為兩種,一種輸入重定向,一種是輸出重定向;從字面上理解,輸入輸出重定向就是「改變輸入與輸出的方向」的意思。
  • 輸入方向就是資料從哪裡流向程式。標準輸入方向是指資料從鍵盤流向程式,如果改變了它的方向,資料就從其它地方流入,這就是輸入重定向。
  • 輸出方向就是資料從程式流向哪裡。標準輸出方向是指資料從程式流向顯示器,如果改變了它的方向,資料就流向其它地方,這就是輸出重定向。

2.硬體裝置和檔案描述符

計算機的硬體裝置有很多,常見的輸入裝置有鍵盤、滑鼠、麥克風、手寫板等,輸出裝置有顯示器、投影儀、印表機等。不過,在Linux中,標準輸入裝置指的是鍵盤,標準輸出裝置指的是顯示器


Linux系統中把一切都看做檔案,包括普通檔案-、目錄檔案d、字元裝置檔案c、塊裝置檔案b、符號連結檔案l以及標準輸入裝置(鍵盤)和標準輸出裝置(顯示器)在內的所有計算機硬體都是檔案

檔案描述符是核心為了高效管理已被開啟的檔案所建立的索引(一個非負整數),用於指代已被開啟的檔案。

Linux下所有的的I/O操作的系統呼叫都是通過檔案描述符執行,一個檔案描述符只是一個和開啟的檔案相關聯的整數,它的背後可能是一個硬碟上的普通檔案、FIFO、管道、終端、鍵盤、顯示器,甚至是一個網路連線。例如0表示標準輸入(鍵盤)、1表示標準輸出(顯示器)、2表示標準錯誤(顯示器),檔案描述符會在這個基礎上遞增。
stdin、stdout、stderr 預設都是開啟的,在重定向的過程中,0、1、2 這三個檔案描述符可以直接使用。

表1:與輸入輸出有關的檔案描述符

檔案描述符檔名型別硬體
0 stdin 標準輸入檔案 鍵盤
1 stdout 標準輸出檔案 顯示器
2 stderr 標準錯誤輸出檔案 顯示器

檔案描述符到底是什麼

檔案描述符:在形式上是一個非負整數。實際上,它是一個索引值,指向核心為每一個程序所維護的該程序開啟檔案的記錄表。
一個 Linux 程序啟動後,會在核心空間中建立一個 PCB 控制塊,PCB 內部有一個檔案描述符表(File descriptor table),記錄著當前程序所有可用的檔案描述符,也即當前程序所有開啟的檔案。

除了檔案描述符表,系統還需要維護另外兩張表:

  • 開啟檔案表(Open file table)
  • i-node 表(i-node table)

檔案描述符表每個程序都有一個,開啟檔案表和 i-node 表整個系統只有一個,它們三者之間的關係如下圖所示。

檔案描述符表:程序級的列表,也就是使用者區的一部分,程序每開啟一個檔案就會新建一個檔案描述符。
系統級開啟檔案表:系統級的列表,對當前系統的所有程序都共享,每條條目包含檔案偏移量、訪問模式以及指向它的檔案描述符的條目計數
檔案系統索引節點表:inode索引節點表(UID、GID、ctime、mtime、atime、讀寫執行許可權、連結數、block位置)

程序級檔案描述符系統級開啟檔案表i-node表

1.檔案描述符標誌

2.檔案指標(open file handle)

通過檔案描述符,可以找到檔案指標,從而進入開啟檔案表

1.檔案偏移量,也就是檔案內部指標偏移量。呼叫 read() 或者 write() 函式時,檔案偏移量會自動更新,當然也可以使用 lseek() 直接修改。

2.狀態標誌,比如只讀模式、讀寫模式、追加模式、覆蓋模式等。

3.i-node 表指標。

要想真正讀寫檔案,要通過開啟檔案表的 i-node 指標進入 i-node 表

1.檔案型別,例如常規檔案、套接字或 FIFO。

2.檔案大小。

3.時間戳,比如建立時間、更新時間。

4.檔案鎖。

對上圖的進一步說明:

  • 在程序 A 中,檔案描述符 1 和 20 都指向了同一個開啟檔案表項,標號為 23(指向了開啟檔案表中下標為 23 的陣列元素),這可能是通過呼叫 dup()、dup2()、fcntl() 或者對同一個檔案多次呼叫了 open() 函式形成的。
  • 程序 A 的檔案描述符 2 和程序 B 的檔案描述符 2 都指向了同一個檔案,這可能是在呼叫 fork() 後出現的(即程序 A、B 是父子程序關係),或者是不同的程序獨自去呼叫 open() 函式打開了同一個檔案,此時程序內部的描述符正好分配到與其他程序開啟該檔案的描述符一樣。
  • 程序 A 的描述符 0 和程序 B 的描述符 3 分別指向不同的開啟檔案表項,但這些表項均指向 i-node 表的同一個條目(標號為 1976);換言之,它們指向了同一個檔案。發生這種情況是因為每個程序各自對同一個檔案發起了 open() 呼叫。同一個程序兩次開啟同一個檔案,也會發生類似情況。


有了以上對檔案描述符的認知,我們很容易理解以下情形:

  • 同一個程序的不同檔案描述符可以指向同一個檔案;
  • 不同程序可以擁有相同的檔案描述符;
  • 不同程序的同一個檔案描述符可以指向不同的檔案(一般也是這樣,除了 0、1、2 這三個特殊的檔案);
  • 不同程序的不同檔案描述符也可以指向同一個檔案。

檔案描述符、檔案、程序之間的關係

  • 每個檔案描述符都指向一個開啟的檔案相對應

  • 不同的檔案描述符可能指向同一個開啟的檔案

  • 相同的檔案可能被不同的程序開啟,也可以在被同一個程序開啟多次

LinuxShell輸出重定向

輸出重定向是指命令的結果不再輸出到顯示器上,而是輸出到其它地方,一般是檔案中。這樣做的最大好處就是把命令的結果儲存起來,當我們需要的時候可以隨時查詢。Bash 支援的輸出重定向符號如下表所示。
在輸出重定向中,>代表的是覆蓋,>>代表的是追加。

注意點:

  1. 輸出重定向的寫法是fd>file或者fd>>file,其中 fd 表示檔案描述符,如果不寫,預設為 1,也就是標準輸出檔案。即command 1>file與command >file相同,當檔案描述符為大於 1 的值時,比如 2,就必須寫上。
  2. fd>之間不能有空格,否則 Shell 會解析失敗;>file之間的空格可有可無
  3. /dev/null 檔案。如果想要將輸出結果丟棄,可以將命令結果重定向到 /dev/null 檔案中,即ls -l &>/dev/null,任何放入垃圾箱的資料都會被丟棄,不能恢復。
命令結果實際執行
1>之間有空格

echo "c.biancheng.net" 1 >log.txt

cat log.txt

c.biancheng.net1 echo "c.biancheng.net" 1 1>log.txt

表2:Bash 支援的輸出重定向符號

型別符號作用舉慄
標準輸出重定向 command>file 以覆蓋的方式,把 command 的正確輸出結果輸出到 file檔案中。

ls -l命令的輸出結果重定向到檔案中。

[c.biancheng.net]$ ls -l  #先預覽一下輸出結果
總用量 16
drwxr-xr-x. 2 root     root      21 7月   1 2016 abc
-rw-r--r--. 1 mozhiyan mozhiyan 399 3月  11 17:12 demo.sh
-rw-rw-r--. 1 mozhiyan mozhiyan  67 3月  22 17:16 demo.txt

[c.biancheng.net]$ ls -l >demo.txt  #重定向
[c.biancheng.net]$ cat demo.txt  #檢視檔案內容
總用量 12
drwxr-xr-x. 2 root     root      21 7月   1 2016 abc
-rw-r--r--. 1 mozhiyan mozhiyan 399 3月  11 17:12 demo.sh
-rw-rw-r--. 1 mozhiyan mozhiyan   0 3月  22 17:21 demo.txt
command >>file 以追加的方式,把 command 的正確輸出結果輸出到 file檔案中。

將 echo 命令的輸出結果以追加的方式寫入到 demo.txt 檔案中。

  1. #!/bin/bash
  2. forstrin"C語言中文網""http://c.biancheng.net/""成立7年了"
  3. do
  4.   echo$str>>demo.txt#將輸入結果以追加的方式重定向到檔案
  5. done

執行指令碼,檢視檔案demo.txt內容,顯示如下:
C語言中文網
http://c.biancheng.net/
成立7年了

標準錯誤輸出重定向 command 2>file 以覆蓋的方式,把 command 的錯誤資訊輸出到 file檔案中。

命令正確執行是沒有錯誤資訊的,我們必須刻意地讓命令執行出錯,如下所示:

[c.biancheng.net]$ ls java  #先預覽一下錯誤資訊
ls: 無法訪問java: 沒有那個檔案或目錄
[c.biancheng.net]$ ls java 2>err.log  #重定向
[c.biancheng.net]$ cat err.log  #檢視檔案
ls: 無法訪問java: 沒有那個檔案或目錄
command 2>>file 以追加的方式,把 command的錯誤資訊輸出到 file檔案中。
正確輸出和錯誤資訊同時儲存 command >file2>&1 覆蓋的方式,把正確輸出和錯誤資訊同時儲存到同一個檔案(file)中。

正確輸出和錯誤資訊同時儲存

【例項1】把正確結果和錯誤資訊都儲存到一個檔案中,例如:

[c.biancheng.net]$ ls -l >out.log 2>&1
[c.biancheng.net]$ ls java >>out.log 2>&1
[c.biancheng.net]$ cat out.log
總用量 12
drwxr-xr-x. 2 root     root      21 7月   1 2016 abc
-rw-r--r--. 1 mozhiyan mozhiyan 399 3月  11 17:12 demo.sh
-rw-rw-r--. 1 mozhiyan mozhiyan 278 3月  16 17:17 main.c
ls: 無法訪問java: 沒有那個檔案或目錄

out.log 的最後一行是錯誤資訊,其它行都是正確的輸出結果。

command >>file2>&1 追加的方式,把正確輸出和錯誤資訊同時儲存到同一個檔案(file)中。
command >file1 2>file2 以覆蓋的方式,把正確的輸出結果輸出到 file1 檔案中,把錯誤資訊輸出到 file2 檔案中。
command >>file1 2>>file2 以追加的方式,把正確的輸出結果輸出到 file1 檔案中,把錯誤資訊輸出到 file2 檔案中。

上面的例項將正確結果和錯誤資訊都寫入同一個檔案中,這樣會導致視覺上的混亂,不利於以後的檢索,

所以我建議把正確結果和錯誤資訊分開儲存到不同的檔案中,也即寫成下面的形式:

ls -l >>out.log 2>>err.log

這樣一來,正確的輸出結果會寫入到 out.log,而錯誤的資訊則會寫入到 err.log。

command >file 2>file 不推薦】這兩種寫法會導致 file 被開啟兩次,引起資源競爭,所以 stdout 和 stderr 會互相覆蓋
command >>file 2>>file

Linux Shell 輸入重定向

輸入重定向就是改變輸入的方向,不再使用鍵盤作為命令輸入的來源,而是使用檔案作為命令的輸入。
和輸出重定向類似,輸入重定向的完整寫法是fd<file,其中 fd 表示檔案描述符,如果不寫,預設為 0,也就是標準輸入檔案。

表3:Bash 支援的輸出重定向符號

符號說明舉慄
command <file 將file 檔案中的內容作為 command 的輸入。 統計 readme.txt 檔案中有多少行文字:
[c.biancheng.net]$ cat readme.txt  #預覽一下檔案內容
C語言中文網
http://c.biancheng.net/
成立7年了
[c.biancheng.net]$ wc -l <readme.txt  #輸入重定向
3
command <<END

從標準輸入(鍵盤)中讀取資料,直到遇見分界符 END 才停止

(分界符可以是任意的字串,使用者自己定義)。

輸入重定向符號<<,這個符號的作用是使用特定的分界符作為命令輸入的結束標誌,而不使用 Ctrl+D 鍵。

<<之後的分界符可以自由定義,只要再碰到相同的分界符,兩個分界符之間的內容將作為命令的輸入(不包括分界符本身)

統計使用者在終端輸入的文字的行數。

[c.biancheng.net]$ wc -l <<END

> 123
> 789
> abc
> END
3

wc 命令會一直等待用輸入,直到遇見分界符 END 才結束讀取。

command <file1 >file2 將 file1 作為 command 的輸入,並將 command 的處理結果輸出到 file2。

程式碼塊重定向

{}<file1

逐行讀取檔案內容。
#!/bin/bash

while read str; do
    echo $str
done <readme.txt
執行結果:
C語言中文網
http://c.biancheng.net/
成立7年了
日IP數萬

總結:

1>log.txt 2>&1 將輸出全重定向到log.txt中
  • 本來1----->螢幕 (1指向螢幕)
  • 執行>log後, 1----->log (1指向log)
  • 執行2>&1後, 2----->1 (2指向1,而1指向log,因此2也指向了log)
可以縮寫為&>log 或者 >&log 2>&1 1>log.txt 將標準錯誤輸出重定向到螢幕,將標準輸出重定向到log.txt中
  • 本來1----->螢幕 (1指向螢幕)
  • 執行2>&1後, 2----->1 (2指向1,而1指向螢幕,因此2也指向了螢幕)
  • 執行>log後, 1----->log (1指向log,2還是指向螢幕)
>/dev/null 2>&1 將輸出全定向到/dev/null
  • /dev/null 代表空裝置檔案
  • 2 表示stderr標準錯誤,& 表示等同於的意思,2>&1,表示2的輸出重定向等同於1
  • 1 表示stdout標準輸出,系統預設值是1,所以">/dev/null"等同於 "1>/dev/null"
最常用的方式有:command > file 2>file 與command > file 2>&1 它們有什麼不同的地方嗎? 首先command >file 2>file 的意思是將命令所產生的stdout和stderr送到file中,file會被開啟兩次,stdout和stderr會互相覆蓋,這樣寫相當使用了FD1和FD2兩個同時去搶佔file 的管道 command >file 2>&1 這條命令將stdout直接送向file,stderr 繼承了FD1管道後再被送往file,此時,file 只被打開了一次,也只使用了一個管道FD1,它包括了stdout和stderr的內容。 從IO效率上,前一條命令的效率要比後面一條的命令效率要低,所以在編寫shell指令碼的時候,較多的時候我們會command > file 2>&1 這樣的寫法。

參考文獻: https://www.cnblogs.com/yujianfei/p/9068472.html