這堂課,我們來介紹可能是命令行最酷的特性。它叫做 I/O 重定向。”I/O”代表輸入/輸出, 通過這個工具,你可以重定向命令的輸入輸出,命令的輸入來自文件,而輸出也存到文件。 也可以把多個命令連接起來組成一個強大的命令管道。為了炫耀這個工具,我們將敘述 以下命令:
> * cat - 連接文件
> * sort - 排序文本行
> * uniq - 報道或省略重復行
> * grep - 打印匹配行
> * wc - 打印文件中換行符,字,和字節個數
> * head - 輸出文件第一部分
> * tail - 輸出文件最后一部分
## 標準輸入,輸出,和錯誤
到目前為止,我們用到的許多程序都會產生某種輸出。這種輸出,經常由兩種類型組成。 第一,程序運行結果;這是說,程序要完成的功能。第二,我們得到狀態和錯誤信息, 這些告訴我們程序進展。如果我們觀察一個命令,像 ls,會看到它的運行結果和錯誤信息 顯示在屏幕上。
與 Unix 主題“任何東西都是一個文件”保持一致,程序,比方說 ls,實際上把他們的運行結果 輸送到一個叫做標準輸出的特殊文件(經常用 stdout 表示),而它們的狀態信息則送到另一個 叫做標準錯誤的文件(stderr)。默認情況下,標準輸出和標準錯誤都連接到屏幕,而不是 保存到磁盤文件。除此之外,許多程序從一個叫做標準輸入(stdin)的設備得到輸入,默認情況下, 標準輸入連接到鍵盤。
I/O 重定向允許我們可以更改輸出走向和輸入來向。一般地,輸出送到屏幕,輸入來自鍵盤, 但是通過 I/O 重定向,我們可以改變輸入輸出方向。
## 重定向標準輸出
I/O 重定向允許我們來重定義標準輸出送到哪里。重定向標準輸出到另一個文件除了屏幕,我們使用 “>” 重定向符,其后跟著文件名。為什么我們要這樣做呢?因為有時候把一個命令的運行結果存儲到 一個文件很有用處。例如,我們可以告訴 shell 把 ls 命令的運行結果輸送到文件 ls-output.txt 中去, 由文件代替屏幕。
~~~
[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt
~~~
這里,我們創建了一個長長的目錄/usr/bin 列表,并且輸送程序運行結果到文件 ls-output.txt 中。 我們檢查一下重定向的命令輸出結果:
~~~
[me@linuxbox ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 me me 167878 2008-02-01 15:07 ls-output.txt
~~~
好;一個不錯的大型文本文件。如果我們用 less 閱讀器來查看這個文件,我們會看到文件 ls-output.txt 的確包含 ls 命令的執行結果。
~~~
[me@linuxbox ~]$ less ls-output.txt
~~~
現在,重復我們的重定向測試,但這次有改動。我們把目錄換成一個不存在的目錄。
~~~
[me@linuxbox ~]$ ls -l /bin/usr > ls-output.txt
ls: cannot access /bin/usr: No such file or directory
~~~
我們收到一個錯誤信息。這很有意義,因為我們指定了一個不存在的目錄/bin/usr, 但是為什么這條錯誤信息顯示在屏幕上而不是被重定向到文件 ls-output.txt?答案是, ls 程序不把它的錯誤信息輸送到標準輸出。反而,像許多寫得不錯的 Unix 程序,ls 把 錯誤信息送到標準錯誤。因為我們只是重定向了標準輸出,而沒有重定向標準錯誤, 所以錯誤信息被送到屏幕。馬上,我們將知道怎樣重定向標準錯誤,但是首先看一下 我們的輸出文件發生了什么事情。
~~~
me@linuxbox ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 me me 0 2008-02-01 15:08 ls-output.txt
~~~
文件長度成為零!這是因為,當我們使用 “>” 重定向符來重定向輸出結果時,目標文件總是從開頭被重寫。 因為我們 ls 命令沒有產生運行結果,只有錯誤信息,重定向操作開始重寫文件,然后 由于錯誤而停止,導致文件內容刪除。事實上,如果我們需要刪除一個文件內容(或者創建一個 新的空文件),可以使用這樣的技巧:
~~~
[me@linuxbox ~]$ > ls-output.txt
~~~
簡單地使用重定向符,沒有命令在它之前,這會刪除一個已存在文件的內容或是 創建一個新的空文件。
所以,怎樣才能把重定向結果追加到文件內容后面,而不是從開頭重寫文件?為了這個目的, 我們使用”>>“重定向符,像這樣:
~~~
[me@linuxbox ~]$ ls -l /usr/bin >> ls-output.txt
~~~
使用”>>“操作符,將導致輸出結果添加到文件內容之后。如果文件不存在,文件會 被創建,就如使用了”>”操作符。把它放到測試中:
~~~
[me@linuxbox ~]$ ls -l /usr/bin >> ls-output.txt
[me@linuxbox ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 me me 503634 2008-02-01 15:45 ls-output.txt
~~~
我們重復執行命令三次,導致輸出文件大小是原來的三倍。
## 重定向標準錯誤
重定向標準錯誤缺乏專用的重定向操作符。重定向標準錯誤,我們必須參考它的文件描述符。 一個程序可以在幾個編號的文件流中的任一個上產生輸出。然而我們必須把這些文件流的前 三個看作標準輸入,輸出和錯誤,shell 內部參考它們為文件描述符0,1和2,各自地。shell 提供 了一種表示法來重定向文件,使用文件描述符。因為標準錯誤和文件描述符2一樣,我們用這種 表示法來重定向標準錯誤:
~~~
[me@linuxbox ~]$ ls -l /bin/usr 2> ls-error.txt
~~~
文件描述符”2”,緊挨著放在重定向操作符之前,來執行重定向標準錯誤到文件 ls-error.txt 任務。
## 重定向標準輸出和錯誤到同一個文件
可能有這種情況,我們希望捕捉一個命令的所有輸出到一個文件。為了完成這個,我們 必須同時重定向標準輸出和標準錯誤。有兩種方法來完成任務。第一個,傳統的方法, 在舊版本 shell 中也有效:
~~~
[me@linuxbox ~]$ ls -l /bin/usr > ls-output.txt 2>&1
~~~
使用這種方法,我們完成兩個重定向。首先重定向標準輸出到文件 ls-output.txt,然后 重定向文件描述符2(標準錯誤)到文件描述符1(標準輸出)使用表示法2>&1。
* * *
注意重定向的順序安排非常重要。標準錯誤的重定向必須總是出現在標準輸出 重定向之后,要不然它不起作用。上面的例子,
~~~
>ls-output.txt 2>&1
~~~
重定向標準錯誤到文件 ls-output.txt,但是如果命令順序改為:
~~~
2>&1 >ls-output.txt
~~~
則標準錯誤定向到屏幕。
* * *
現在的 bash 版本提供了第二種方法,更精簡合理的方法來執行這種聯合的重定向。
~~~
[me@linuxbox ~]$ ls -l /bin/usr &> ls-output.txt
~~~
在這個例子里面,我們使用單單一個表示法 &> 來重定向標準輸出和錯誤到文件 ls-output.txt。
## 處理不需要的輸出
有時候“沉默是金”,我們不想要一個命令的輸出結果,只想把它們扔掉。這種情況 尤其適用于錯誤和狀態信息。系統為我們提供了解決問題的方法,通過重定向輸出結果 到一個特殊的叫做”/dev/null”的文件。這個文件是系統設備,叫做位存儲桶,它可以 接受輸入,并且對輸入不做任何處理。為了隱瞞命令錯誤信息,我們這樣做:
~~~
[me@linuxbox ~]$ ls -l /bin/usr 2> /dev/null
~~~
> Unix 文化中的/dev/null
>
> 位存儲桶是個古老的 Unix 概念,由于它的普遍性,它的身影出現在 Unix 文化的 許多部分。當有人說他/她正在發送你的評論到/dev/null,現在你應該知道那是 什么意思了。更多的例子,可以閱讀 Wikipedia 關于”/dev/null”的文章。
## 重定向標準輸入
到目前為止,我們還沒有遇到一個命令是利用標準輸入的(實際上我們遇到過了,但是 一會兒再揭曉謎底),所以我們需要介紹一個。
## cat - 連接文件
cat 命令讀取一個或多個文件,然后復制它們到標準輸出,就像這樣:
~~~
cat [file]
~~~
在大多數情況下,你可以認為 cat 命令相似于 DOS 中的 TYPE 命令。你可以使用 cat 來顯示 文件而沒有分頁,例如:
~~~
[me@linuxbox ~]$ cat ls-output.txt
~~~
將會顯示文件 ls-output.txt 的內容。cat 經常被用來顯示簡短的文本文件。因為 cat 可以 接受不只一個文件作為參數,所以它也可以用來把文件連接在一起。比方說我們下載了一個 大型文件,這個文件被分離成多個部分(USENET 中的多媒體文件經常以這種方式分離), 我們想把它們連起來。如果文件命名為:
我們能用這個命令把它們連接起來:
~~~
cat movie.mpeg.0* > movie.mpeg
~~~
因為通配符總是以有序的方式展開,所以這些參數會以正確順序安排。
這很好,但是這和標準輸入有什么關系呢?沒有任何關系,讓我們試著做些其他的工作。 如果我們輸入不帶參數的”cat”命令,會發生什么呢:
~~~
[me@linuxbox ~]$ cat
~~~
沒有發生任何事情,它只是坐在那里,好像掛掉了一樣。看起來是那樣,但是它正在做它該做的事情:
如果 cat 沒有給出任何參數,它會從標準輸入讀入數據,因為標準輸入,默認情況下,連接到鍵盤。 它正在等待我們輸入數據!試試這個:
~~~
[me@linuxbox ~]$ cat
The quick brown fox jumped over the lazy dog.
~~~
下一步,輸入 Ctrl-d(按住 Ctrl 鍵同時按下”d”),來告訴 cat,在標準輸入中, 它已經到達文件末尾(EOF):
~~~
[me@linuxbox ~]$ cat
The quick brown fox jumped over the lazy dog.
~~~
由于文件名參數的缺席,cat 復制標準輸入到標準輸出,所以我們看到文本行重復出現。 我們可以使用這種行為來創建簡短的文本文件。比方說,我們想創建一個叫做”lazy_dog.txt” 的文件,這個文件包含例子中的文本。我們這樣做:
~~~
[me@linuxbox ~]$ cat > lazy_dog.txt
The quick brown fox jumped over the lazy dog.
~~~
輸入命令,其后輸入要放入文件中的文本。記住,最后輸入 Ctrl-d。通過使用這個命令,我們 實現了世界上最低能的文字處理器!看一下運行結果,我們使用 cat 來復制文件內容到 標準輸出:
~~~
[me@linuxbox ~]$ cat lazy_dog.txt
The quick brown fox jumped over the lazy dog.
~~~
現在我們知道怎講接受標準輸入,除了文件名參數,讓我們試著重定向標準輸入:
~~~
[me@linuxbox ~]$ cat < lazy_dog.txt
The quick brown fox jumped over the lazy dog.
~~~
使用“<”重定向操作符,我們把標準輸入源從鍵盤改到文件 lazy_dog.tx。我們看到結果 和傳遞單個文件名作為參數的執行結果一樣。把這和傳遞一個文件名參數作比較,尤其沒有意義, 但它是用來說明把一個文件作為標準輸入源。
在我們繼續之前,查看 cat 的手冊頁,因為它有幾個有趣的選項。
## 管道線
命令可以從標準輸入讀取數據,然后再把數據輸送到標準輸出,命令的這種能力被 一個 shell 特性所利用,這個特性叫做管道線。使用管道操作符”|”(豎杠),一個命令的 標準輸出可以管道到另一個命令的標準輸入:
~~~
command1 | command2
~~~
為了全面地說明這個命令,我們需要一些命令。是否記得我們說過,我們已經知道有一個 命令接受標準輸入?它是 less 命令。我們用 less 來一頁一頁地顯示任何命令的輸出,命令把 它的運行結果輸送到標準輸出:
~~~
[me@linuxbox ~]$ ls -l /usr/bin | less
~~~
這極其方便!使用這項技術,我們可以方便地檢測會產生標準輸出的任一命令的運行結果。
## 過濾器
管道線經常用來對數據完成復雜的操作。有可能會把幾個命令放在一起組成一個管道線。 通常,以這種方式使用的命令被稱為過濾器。過濾器接受輸入,以某種方式改變它,然后 輸出它。第一個我們想試驗的過濾器是 sort。想象一下,我們想把目錄/bin 和/usr/bin 中 的可執行程序都聯合在一起,再把它們排序,然后瀏覽執行結果:
~~~
[me@linuxbox ~]$ ls /bin /usr/bin | sort | less
~~~
因為我們指定了兩個目錄(/bin 和/usr/bin),ls 命令的輸出結果由有序列表組成, 各自針對一個目錄。通過在管道線中包含 sort,我們改變輸出數據,從而產生一個 有序列表。
## uniq - 報道或忽略重復行
uniq 命令經常和 sort 命令結合在一起使用。uniq 從標準輸入或單個文件名參數接受數據有序 列表(詳情查看 uniq 手冊頁),默認情況下,從數據列表中刪除任何重復行。所以,為了確信 我們的列表中不包含重復句子(這是說,出現在目錄/bin 和/usr/bin 中重名的程序),我們添加 uniq 到我們的管道線中:
~~~
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | less
~~~
在這個例子中,我們使用 uniq 從 sort 命令的輸出結果中,來刪除任何重復行。如果我們想看到 重復的數據列表,讓 uniq 命令帶上”-d”選項,就像這樣:
~~~
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq -d | less
~~~
## wc - 打印行,字和字節數
wc(字計數)命令是用來顯示文件所包含的行,字和字節數。例如:
~~~
[me@linuxbox ~]$ wc ls-output.txt
7902 64566 503634 ls-output.txt
~~~
在這個例子中,wc 打印出來三個數字:包含在文件 ls-output.txt 中的行數,單詞數和字節數, 正如我們先前的命令,如果 wc 不帶命令行參數,它接受標準輸入。”-l”選項限制命令輸出只能 報道行數。添加 wc 到管道線來統計數據,是個很便利的方法。查看我們的有序列表中程序個數, 我們可以這樣做:
~~~
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | wc -l
2728
~~~
## grep - 打印匹配行
grep 是個很強大的程序,用來找到文件中的匹配文本。這樣使用 grep 命令:
~~~
grep pattern [file...]
~~~
當 grep 遇到一個文件中的匹配”模式”,它會打印出包含這個類型的行。grep 能夠匹配的模式可以 很復雜,但是現在我們把注意力集中在簡單文本匹配上面。在后面的章節中,我們將會研究 高級模式,叫做正則表達式。
比如說,我們想在我們的程序列表中,找到文件名中包含單詞”zip”的所有文件。這樣一個搜索, 可能讓我們了解系統中的一些程序與文件壓縮有關系。這樣做:
~~~
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | grep zip
bunzip2
bzip2
gunzip
...
~~~
grep 有-對方便的選項:”-i”導致 grep 忽略大小寫當執行搜索時(通常,搜索是大小寫 敏感的),”-v”選項會告訴 grep 只打印不匹配的行。
## head / tail - 打印文件開頭部分/結尾部分
有時候你不需要一個命令的所有輸出。可能你只想要前幾行或者后幾行的輸出內容。 head 命令打印文件的前十行,而 tail 命令打印文件的后十行。默認情況下,兩個命令 都打印十行文本,但是可以通過”-n”選項來調整命令打印的行數。
~~~
[me@linuxbox ~]$ head -n 5 ls-output.txt
total 343496
...
[me@linuxbox ~]$ tail -n 5 ls-output.txt
...
~~~
它們也能用在管道線中:
~~~
[me@linuxbox ~]$ ls /usr/bin | tail -n 5
znew
...
~~~
tail 有一個選項允許你實時的瀏覽文件。當觀察日志文件的進展時,這很有用,因為 它們同時在被寫入。在以下的例子里,我們要查看目錄/var/log 里面的信息文件。在 一些 Linux 發行版中,要求有超級用戶權限才能閱讀這些文件,因為文件/var/log/messages 可能包含安全信息。
~~~
[me@linuxbox ~]$ tail -f /var/log/messages
Feb 8 13:40:05 twin4 dhclient: DHCPACK from 192.168.1.1
....
~~~
使用”-f”選項,tail 命令繼續監測這個文件,當新的內容添加到文件后,它們會立即 出現在屏幕上。這會一直繼續下去直到你輸入 Ctrl-c。
## tee - 從 Stdin 讀取數據,并同時輸出到 Stdout 和文件
為了和我們的管道隱喻保持一致,Linux 提供了一個叫做 tee 的命令,這個命令制造了 一個”tee”,安裝到我們的管道上。tee 程序從標準輸入讀入數據,并且同時復制數據 到標準輸出(允許數據繼續隨著管道線流動)和一個或多個文件。當在某個中間處理 階段來捕捉一個管道線的內容時,這很有幫助。這里,我們重復執行一個先前的例子, 這次包含 tee 命令,在 grep 過濾管道線的內容之前,來捕捉整個目錄列表到文件 ls.txt:
~~~
[me@linuxbox ~]$ ls /usr/bin | tee ls.txt | grep zip
bunzip2
bzip2
....
~~~
## 總結歸納
一如既往,查看這章學到的每一個命令的文檔。我們已經知道了他們最基本的用法。 它們還有很多有趣的選項。隨著我們 Linux 經驗的積累,我們會了解命令行重定向特性 在解決特殊問題時非常有用處。有許多命令利用標準輸入和輸出,而幾乎所有的命令行 程序都使用標準錯誤來顯示它們的詳細信息。
> Linux 可以激發我們的想象
>
> 當我被要求解釋 Windows 與 Linux 之間的差異時,我經常拿玩具來作比喻。
>
> Windows 就像一個游戲機。你去商店,買了一個包裝在盒子里面的全新的游戲機。 你把它帶回家,打開盒子,開始玩游戲。精美的畫面,動人的聲音。玩了一段時間之后, 你厭倦了它自帶的游戲,所以你返回商店,又買了另一個游戲機。這個過程反復重復。 最后,你玩膩了游戲機自帶的游戲,你回到商店,告訴售貨員,“我想要一個這樣的游戲!” 但售貨員告訴你沒有這樣的游戲存在,因為它沒有“市場需求”。然后你說,“但是我只 需要修改一下這個游戲!“,售貨員又告訴你不能修改它。所有游戲都被封裝在它們的 存儲器中。到頭來,你發現你的玩具只局限于別人為你規定好的游戲。
>
> 另一方面,Linux 就像一個全世界上最大的建造模型。你打開它,發現它只是一個巨大的 部件集合。有許多鋼支柱,螺釘,螺母,齒輪,滑輪,發動機,和一些怎樣來建造它的說明書。 然后你開始擺弄它。你建造了一個又一個樣板模型。過了一會兒,你發現你要建造自己的模型。 你不必返回商店,因為你已經擁有了你需要的一切。建造模型以你構想的形狀為模板,搭建 你想要的模型。
>
> 當然,選擇哪一個玩具,是你的事情,那么你覺得哪個玩具更令人滿意呢?
- 第一章:引言
- 第二章:什么是shell
- 第三章:文件系統中跳轉
- 第四章:研究操作系統
- 第五章:操作文件和目錄
- 第六章:使用命令
- 第七章:重定向
- 第八章:從shell眼中看世界
- 第九章:鍵盤高級操作技巧
- 第十章:權限
- 第十一章:進程
- 第十二章:shell環境
- 第十三章:VI簡介
- 第十四章:自定制shell提示符
- 第十五章:軟件包管理
- 第十六章:存儲媒介
- 第十七章:網絡系統
- 第十八章:查找文件
- 第十九章:歸檔和備份
- 第二十章:正則表達式
- 第二十一章:文本處理
- 第二十二章:格式化輸出
- 第二十三章:打印
- 第二十四章:編譯程序
- 第二十五章:編寫第一個shell腳本
- 第二十六章:啟動一個項目
- 第二十七章:自頂向下設計
- 第二十八章:流程控制 if分支結構
- 第二十九章:讀取鍵盤輸入
- 第三十章:流程控制 while/until 循環
- 第三十一章:疑難排解
- 第三十二章:流程控制 case分支
- 第三十三章:位置參數
- 第三十四章:流程控制 for循環
- 第三十五章:字符串和數字
- 第三十六章:數組
- 第三十七章:奇珍異寶