读Netman大哥的Study-Area之重导向和管道

详细出处参考http://www.study-area.org/compu/compufr.htm

命令重導向

好了﹐相信您已經對您的 shell 有一定的了解了。然後﹐讓我們看看 shell 上面的一些命令功能吧﹐這些技巧都是作為一個系統管理員基本要素。其中之一就是﹕命令重導向 (command redirection) 和 命令管線 (command pipe) 。

在深入講解這兩個技巧之前﹐先讓我們了解一下 shell 命令的基本概念﹕

名稱 代號 代表意思 設備
STDIN 0 標準輸入 鍵盤
STDOUT 1 標準輸出 熒幕
STDERR 2 標準錯誤 熒幕

 

表格中分別是我們在 shell 中一個命令的標準 I/O (輸出與輸入)。當我們執行一個命令的時候﹐先讀入輸入 (STDIN)﹐然後進行處理﹐最後將結果進行輸出 (STDOUT)﹔如果處理過程中遇到錯誤﹐那麼命令也會顯示錯誤 (STDERR)。我們可以很容易發現﹕一般的標準輸入﹐都是從我們的鍵盤讀取﹔而標準輸出和標準錯誤﹐都從我們的銀幕顯示。

同時﹐在系統上﹐我們通常用號碼來代表各不同的 I/O﹕STDIN 是 0﹑STDOUT 是 1﹑STDERR 是 2。

當您了解各個 I/O 的意思和所代表號碼之後﹐讓我們看比較如下命令的結果﹕

# ls mbox mbox # ls mbox 1> file.stdout

 

請小心看第二個命令﹕在命令的後面多了一個 1 ﹐而緊接著(沒有空白﹗)是一個大於符號 (>)﹐然後是另外一個檔案名稱。但是﹐熒幕上卻沒有顯示命令的執行結果﹐也就是說﹕ STDOUT 不見了﹗那到底發生什麼事情了呢﹖

呵﹐相信您不會這麼快忘記了 STDOUT 的代號是 1 吧﹗沒錯了﹐因為我們這裡將 1 用一個 > 符號重導到一個檔案中了。結果過是﹕我們將標準輸出從熒幕改變到檔案中﹐所以我們在銀幕就看不到 STDOUT﹐而原先的 STDOUT 結果則保存在大於符號右邊的檔中了。不信﹐您看看這個檔案的內容就知道了﹕

# cat file.stdout mbox

 

當我們用一個 > 將命令的 STDOUT 導向到一個檔案的時候﹐如果檔案不存在﹐則會建立一個新檔﹔如果檔案已經存在﹐那麼﹐這個檔案的內容就換成 STDOUT 的結果。有時候﹐您或許想保留原有檔案的內容﹐而將結果增加在檔案末端而已。那您可以多加一個 >﹐也就是使用 >> 就是了。您可以自己玩玩看哦~~﹐通常﹐我們要將一些命令或錯誤記錄下來﹐都用這個方法。

Tips﹕如果您不希望 > 意外的蓋掉一個原有檔﹐那您可以執行這個命令﹕

set -o noclobber

不過﹐仍可以用 >| 來強迫寫入。

上前面的例子中﹐我們指定了 I/O 1 (STDOUT) 進行重導向﹐這也是預設值﹐如果您沒有指定代號﹐那麼就是進行 STDOUT 的重導向﹐所以 1> 和 > 是一樣的﹔1>> 和 >> 也是一樣的。但如果您使用了數字﹐那麼數字和 > 之間一定不能有空白存在。

好了﹐下面再比較兩個命令﹕

# ls no_mbox ls: no_mbox: No such file or directory # ls no_mbox 2>> file.stderr

嗯﹐相信不用我多解釋了吧﹖(如果檔案不存在﹐>> 和 > 都會建立新的。)

事實上﹐在我們的日常管理中﹐重導向的應用是非常普遍的。我只舉下面這個例子就好了﹕

當我們進行核心編譯的時候(我們下一章再介紹)﹐熒幕會飛快的顯示出成千上萬行信息﹔其中有大部份是 STDOUT﹐但也有些是 STDERR。除非您的眼睛真的那麼厲害﹐否則您很難分辯出哪些是正常信息﹐哪些是錯誤信息。當您要編譯失敗﹐嘗試找錯誤的時候﹐如果已經將 STDERR 重導出來﹐就非常方便了﹕

# make dep clean bzImage modules 1>/dev/null 2>/tmp/kernel.err &

這裡﹐我一共有三個打算﹕(1) 將標準輸出送到一個叫 null 的設備上﹐如果您記性夠好﹐我在前面的文章中曾比喻它為黑洞﹕所有東西進去之後都會消失掉。憑我個人的習慣﹐我會覺得編譯核心時跑出來的信息﹐如果您不感興趣的話﹐那都是垃圾﹐所以我將 STDOUT 給重導到 null 去﹐眼不見為乾淨﹔ (2) 然後﹐我將 STDERR 重導到 /tmp/kernel.err 這個檔去﹐等命令結束後﹐我就可以到那裡看看究竟有部份有問題。有些問題可能不是很重要﹐有些則可能需要重新再編核心﹐看您經驗啦。(3) 最後﹐我將命令送到 background 中執行 (呵~~ 相信您還沒忘記吧﹗)。因為﹐編譯核心都比較花時間﹐所以我將之送到背景去﹐這樣我可以繼續做其它事情。

Tips﹕這時﹐因為系統太忙了﹐可能反應速度上會比較慢些﹐如果您真的很在意﹐不妨考慮把 make 的 nice level 提高。(忘記怎麼做了﹖那翻看前一章吧)

前面的例子﹐我們是分開將 STDOUT 和 STDERR 重導到不同的檔案去﹐那麼﹐我們能否把兩者都重導到同一個檔呢﹖當然是可以的﹐請比較下面三行﹕

# make dep clean bzImage modules >/tmp/kernel.result 2>/tmp/kernel.result # make dep clean bzImage modules >/tmp/kernel.result 2>&1 # make dep clean bzImage modules &>/tmp/kernel.resultt

我這裡告訴您﹕第一行的命令不怎麼正確﹐因為這樣會造成這兩個輸出同時在‘搶’一個檔案﹐寫入的順序很難控制。而第 2 行和第 3 行的結果都是一樣的﹐看您喜歡用哪個格式了。不過﹐要小心的是﹕& 符號後面不能有空白鍵﹐否則會當成將命令送到背景執行﹐而不是將 STDOUT 和 STDERR 整合。

好了﹐前面我們都在談 STDOUT 和 STDERR 的重導向﹐那麼﹐我們是否能重導 STDIN 呢﹖

當然可以啦~~~

有些命令﹐當我們執行之後﹐它會停在那裡等待鍵盤的 STDIN 輸入﹐直到遇到 EOF (Ctrl+D) 標籤才會真正結束命令。比方說﹐在同一個系統上﹐如果有多位使用者同時登入的話﹐您可以用 write 命令向特的使用者送出短訊。而短訊的內容就是鍵盤敲入的文字﹐這時候命令會進入輸入模式﹐您每輸入一行並按 Enter 之後﹐那麼訊息就會在另外一端﹐直到您按 Ctrl+D 鍵才離開並結束命令。

# write user1 Hello! It is me... ^_^ How r u!
(Ctrl+D)

這樣通常都需要花一些時間輸入﹐假如對方在寫什麼東西和查看某些資料的時候﹐就很混亂。這時候﹐您或許可以先將短訊的內容寫在一個檔案裡面﹐例如 greeting.msg﹐然後這樣輸入就可以了﹕

write user1 < greeting.msg

就這樣﹐這裡我們用小於符號 (<) 來重導 STDIN 。簡單吧﹖^_^

不過﹐我們用 cat 命令建立簡單的檔案的時候﹐卻是使用 > 符號的﹕

cat > file.tmp

 

等您按 Ctrl+D 之後﹐從鍵盤輸入的 STDIN﹐就保存在 file.tmp 中了。請想想看為什麼會如此﹖(我在 LPI 的考試中碰到過這道題目哦~~~)

pipe

查字典﹐pipe 這個英文是水管﹑管道﹑管線的意思。那麼﹐它和命令又有什麼牽連呢﹖簡單的說﹐一個命令管線﹐就是將一個命令的 STDOUT 作為另一個命令的 STDIN 。

其實﹐這樣的例子我們前面已經碰到多次了﹐例如上一章介紹 tr 命令的時候﹕

# cat /path/to/old_file | tr -d '\r' > /path/to/new_file

上面這個命令行﹐事實上有兩個命令﹕cat 和 tr ﹐在這兩個命令之間﹐我們用一個 “ | ”符號作為這兩個命令的管線﹐也就是將 cat 命令的 STDOUT 作為 tr 命令的 STDIN ﹔然後﹐tr 命令的 STDOUT 用 > 重導到另外一個檔案去。

上面只是一個非常簡單的例子而已﹐事實上﹐我們可以用多個管線連接多個程式﹐最終獲得我們確切想要的結果。比方說﹕我想知道目前有多少人登錄在系統上面﹕

# w | tail +3 | wc -l

我們不妨解讀一下這個命令行﹕(1) w 命令會顯示出當前登錄者的資源使用情況﹐並且每一個登錄者佔一行﹔(2) 再用 tail 命令抓取第 3 行開始的字行﹔(3) 然後用 wc -l 計算出行數。這樣﹐就可以知道當前的登錄人數了。

許多朋友目前都採用撥接 ADSL 上網﹐每次連線的 IP 都未必一樣﹐只要透過簡單的命令管線﹐您就可以將當前的 IP 抓出來了﹕

  1. 我們不妨觀察 ifconfig ppp0 這個命令的輸出結果﹕
    # ifconfig ppp0
    ppp0      Link encap:Point-to-Point Protocol
              inet addr:211.74.48.254  P-t-P:211.74.48.1  Mask:255.255.255.255
              UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1492  Metric:1
              RX packets:5 errors:0 dropped:0 overruns:0 frame:0
              TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:3
    

     

  2. 不難發現 IP 位址所在的句子中有著其它句子所沒有的字眼﹕inet addr 。然後﹐我們就可用 grep 把這行抓出來﹕
    # ifconfig ppp0 | grep "inet addr"
              inet addr:211.74.48.254  P-t-P:211.74.48.1  Mask:255.255.255.255
    

     

  3. 再來﹐我們先用相同的分隔符號將句子分成數列﹐然後抓出 IP 位址所在的那列。 

    嗯﹐這裡﹐我們可以用“ : ”來分出 4 列﹔也可以用空白鍵來分出 5 列(空因為句子開首就是一個空白鍵)。如果用空白鍵來分的話﹐由於有些間隔有多個空白鍵的原因﹐那麼﹐我們可以用 tr 命令﹐將多個空白鍵集合成一個空白鍵﹕

    # ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' '
     inet addr:211.74.48.254 P-t-P:211.74.48.1 Mask:255.255.255.255
    

    (注意﹕在 ' ' 之間是一個空白鍵﹗)

  4. 然後用 cut 命令抓出 IP 所在的列﹐細心數一數﹐應該是第 3 列﹕
    # ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' ' | cut -d ' ' -f3
    addr:211.74.48.254
    

     

  5. 然後我們用“ : ”再分兩列﹐抓第 2 列就是 IP 了﹕
    # ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' ' \
    	| cut -d ' ' -f3 | cut -d ':' -f2
    211.74.48.254
    

這裡﹐我們一共用 5 個 pipe 將 4 個命令連接起來﹐就抓出機器當前的 IP 位址了。是否很好用呢﹖

在同一個命令行裡面出現多個命令的情形﹐除了 “ | ”之外﹐或許您會看到 " ` ` " 符號﹐也就是和 ~ 鍵同一個鍵的符號(不用按 Shift )。它必須是一對使用的﹐其中可以包括單一命令﹐或命令管線。那它的效果和命令管線又有什麼分別呢﹖

我們使用 pipe 將一個命令的 STDOUT 傳給下一個命令的 STDIN﹐但使用 `` 的時候﹐它所產生的 STDOUT 或 STDERR 僅作為命令行中的一個參數而已。嗯﹐不如看看下面命令好了﹕

# TODAY=`date +%D` # echo Today is $TODAY. Today is 08/17/01.

從結果我們可以看出﹐我們用 `` 將 date 這個命令括起來(可含參數)﹐那麼它的執行結果可以作為 TODAY 的變數值。我們甚至還可以將一串命令管線直接用在命令行上面﹕

# echo My IP is `ifconfig ppp0 | grep "inet addr" \     | tr -s ' ' ' ' | cut -d ' ' -f3 | cut -d ':' -f2` My IP is 211.74.48.254.

註意﹕第一行的 CR 被 \ 跳脫了﹐所以這個命令行‘看起來’有兩行。我之所以弄這麼複雜﹐是告訴您這對 `` 符號可以適用的範圍。

Tips﹕在變數中使用 `` 可以將命令的執行結果當成變數值的部份。事實上﹐除了用 `` 之外﹐您也可以用這樣的格式﹕

VAR_NAME=$(command)﹐那是和 VAR_NAME=`command` 的結果是一樣的。

除了這對 `` 和 | 之外﹐還有另外一個符號 “ ; ”來分隔命令的。不過﹐這個比較簡單﹕就是當第一命令結束之後﹐再執行第二個命令﹐如此類推﹕

# ./configure; make; make install

呵~~ 如果您對您的安裝程式有絕對信心﹐用上面一行命令就夠了﹗

posted on 2013-03-27 11:11  夜月升  阅读(679)  评论(0)    收藏  举报

导航