對話 UNIX:您好,shell!
UNIX 系統中一項最奇特和突出的特性就是其命令列功能。您只需輸入包含一定邏輯關係的少量文本,即可使用命令列來將有限的 UNIX 工具 + 生產力組合成無限的即時可用的資料轉換。
例如,要在當前工作目錄下的資料夾階層中尋找獨特檔案名稱的列表,您可在 shell 提示符下輸入以下命令:
find . -type f -print | sort | uniq |
該命令列中組合了三種不同的工具 + 生產力:
find
對指定目錄進行深度搜尋,在本例中,是對從 .
或 點(代表當前工作目錄)開始的檔案系統進行搜尋並產生滿足給定條件的所有項的名稱。這裡,-type f
告訴 find
只尋找文字檔。
sort
,顧名思義,將對列表進行處理並產生按字母排序的新列表。
uniq
(讀做 “unique”),掃描列表,比較列表中的相鄰元素,以去除任何重複項。例如,假設您具有以下列表:
清單 1. 列表示例
GrouchoGrouchoChicoChicoGrouchoHarpoZeppoZeppo |
uniq
可將列表精簡為:
清單 2. uniq 命令
GrouchoChicoGrouchoHarpoZeppo |
但是,如果首先對 Marx Brothers 的初始列表進行排序(在連續運行中對多次出現的名稱進行重排),運行 uniq
會產生以下結果:
清單 3. 運行 uniq
要瞭解 find
、sort
和 uniq
的更多擴充特性,請參閱您的 UNIX 系統中每種工具 + 生產力的 man
頁。
輸入資料、輸出資料和全部資料
獨立使用 find
時,總是以檔案系統的內容作為輸入資料。但是 sort
和 uniq
則需要從標準輸入裝置 (stdin) 請求資料輸入。多數情況下,您會使用鍵盤作為 stdin:例如,您需要輸入要排序的資料行。
預設情況下,find
在標準輸出裝置(stdout,通常是您的終端視窗)上列印結果。sort
和 uniq
都將輸出列印到 stdout。
為了說明 stdin 和 stdout,您可在終端視窗中輸入以下文本(假設前面的百分比符號 (%
) 為您的 shell 提示符):
清單 4. stdin 和 stdout
% sortmustachehornhatControl-D |
sort
從 stdin 讀取您輸入的三行文本,並對其進行排序,然後將結果寫出到 stdout。圖 1 所示為從命令列運行 sort
和多數 UNIX 命令列工具 + 生產力的。
圖 1. 典型 UNIX 命令列工具 + 生產力從 stdin 讀取並寫入到 stdout
某些工具 + 生產力,例如 find
並不從 stdin 讀取內容。它們是從系統資源(例如檔案系統或系統核心)讀取需要處理的資料,然後將結果寫入到 stdout。要直觀查看 find
的工作方式,請參見以下的圖 2。
圖 2. 某些工具 + 生產力從系統資源讀取資料並將結果寫入到 stdout
除了使用 stdin 和 stdout 之外,UNIX 命令還將產生的錯誤訊息輸出到一種特殊出口以便進行診斷,該出口通常並不是強制的。此出口稱為標準錯誤裝置(通常簡稱為 stderr)。圖 3 所示為運行工具 + 生產力的簡單命令列。
圖 3. UNIX 命令建置錯誤並輸出到特殊通道,即標準錯誤裝置
如圖 3 中所示,多數 UNIX 命令從終端讀取輸入,將結果發送到終端,並將錯誤列印到終端上。預設情況下,除非另行指定,您的終端既是 stdin 的資料來源,也是 stdout 和 stderr 的輸出目標。
資料轉送
不過,您可更改 stdin 的源以及 stdout 和 stderr 的目標。您可強制 stdin 從文字檔、裝置(比如串連到電腦的探頭)或網路連接中進行讀取。類似地,您可將輸出結果發送到檔案、裝置或網路連接。在 UNIX 中,所有資源都被視作檔案,因此某種源或目標很容易作為另外的源或目標而被接受或產生。
更改進程資料的源和目標被稱為重新導向。您可重新導向 stdin 以從檔案或其他源讀取資料,還可分別對 stdout 和 stderr 重新導向以將資料寫到終端視窗之外的其他位置。在許多情況下,如前面所列出的初始 find
命令中,您還可重新導向工具 + 生產力以從其他工具接收和為它們產生所需的資料。這就是管道 (|
) 的用途。您可在命令中通過管道來產生進程鏈路,即將某條命令的資料發送到下一條命令,這類似於通過銅管將水從熱水器傳輸到洗手池中。
圖 4 所示為 find . -type f -print | sort | uniq
命令的。
圖 4. 通過管道進行連結的三個工具 + 生產力的
find
命令的 stdout 成為 uniq
stdin,然後 uniq
的 stdout 又成為 sort
的 stdin。最後,sort
將結果列印到其標準輸出裝置,即所串連的終端視窗上。這些命令的 stderr 未進行重新導向,因此所有三個工具 + 生產力都會將錯誤訊息列印到終端上。(來自三個工具 + 生產力的錯誤訊息會混在一起,但保持正確的順序。)
如有需要,您可進一步擴充管道,將 uniq
的輸出重新導向到另外的工具 + 生產力。這隻需使用另一個管道即可對轉換進行擴充。例如,您可在命令之後附加 | less
以使用 less
對輸出結果進行分頁,或者您可添加 | wc -l
以統計獨特檔案名稱的數目。(wc
為 word count 的首字母縮寫,wc
可統計字元、單詞和行數。)
此外,您還可使用 >
來將整個命令序列的輸出結果儲存到一個檔案中(這將覆蓋現有的檔案內容)。您可使用 >>
以將命令輸出結果附加 到現有檔案之後(如果檔案不存在,則建立新檔案)。
另一個有用的重新導向是 <
。圖 5 所示為如何重新導向 stdin 以從檔案中進行讀取。命令 sort
從指定檔案中讀取單字清單並按字母順序進行排序。
圖 5. 重新導向標準輸入以讀取檔案內容
您常常會需要捕獲 stdout 和 stderr。例如,如果您正在運行大型的資料採礦任務,則可能要檢查執行過程中的中間輸出以及可能出現的任何錯誤。您可使用重新導向文法的一些變種來實現該功能:|&
, >&
, >>&
可分別對 stdout 和 stderr 實現管道、建立、附加功能。圖 6 所示為如何將 stdout 和 stderr 合并到單一的輸出資料流。
圖 6. 合并標準輸出和標準錯誤裝置
Z shell 介紹
包括 Bourne shell (bash
) 和 Korn shell (ksh
) 在內的多數現代 UNIX shell 都支援這裡提到的重新導向功能,儘管在這些 shell 中具體文法可能存在細微差別。(請查看您的 shell 文檔以瞭解詳細資料)。
重新導向中的多數操作符在所有的 UNIX shell 中已經連續使用了至少 25 年。但是,多數 shell 並沒有提供新的特性或採用新方式來應用重新導向。例如,多數 shell 只能將輸入重新導向到單個檔案,因此您必須使用如 tee
等工具 + 生產力來輸出到多個目標。(類似於水管工人使用的 T 型管 (Tee),tee
只支援單個或兩個輸出。)這裡提供一個使用 bash
作為 shell(命令列解譯器)的樣本:
清單 5. bash 樣本
bash$ lstellmebash$ cat tellmeecho Your current login, working directory, and system are...whoamipwdsystemnamebash$ bash < tellme |& tee logYour current login and working directory are...strike/home/strikebash: systemname: command not foundbash$ lstellme logbash$ cat logYour current login and working directory arestrike/home/strikebash: systemname: command not found |
儘管 UNIX shell 具有較高的專用性,且通常使用鍵盤進行互動,但某些 shell 如 bash
等也能從檔案讀取輸入內容。(實際上,stdin 也是一種檔案。)在前面的片段中,短語 bash < commands
告訴 bash
執行在檔案 tellme 中找到的命令列表。短語 |&tee log
將 bash
的 stdout 和 stderrto 通過管道重新導向到 tee
工具 + 生產力,後者將其 stdin 列印到 stdout 和 檔案 log 中。
但是,如果您打算使用 bash
來處理多個檔案,該怎麼辦呢?cat file1 file2 file3 | bash
是一種可行的方法,這也許也是唯一的一種方法,因為在 bash
中並不支援如 bash < file1 < file2 < file3
的文法。
而且,bash
無法將輸出重新導向到多個目標。例如,您可從 bash
命令列中輸入指令 bighairyscript > ~/log | mail -s "Important stuff" team
。
但在相對較新的 shell 如 Z shell(zsh
;請參閱參考資料)中,可以在同一命令列內處理多個輸入和輸出。例如,使用以下命令可將 stdout 儲存到名為 log 的檔案中並通過電子郵件發送給您自己:
清單 6. Z shell
zsh% bash < tellme > log | mail -s "Who you are" 'whoami'bash: line 4: systemname: command not foundzsh% <logYour current login, working directory, and system are...strike/home/strike |
(短語 “whoami”
運行命令 whoami
並將該命令的結果插入到短語所在位置。它類似於在運行命令列的其他部分之前先運行一條短小 shell 命令。)
現在我們對上一條命令從左向右進行分析。bash
命令建立檔案 log 並將在 tellme 檔案中找到的命令的 stdout 發送給您自己。由於 stderr 沒有通過 >
或管道進行定向,因此錯誤訊息將被列印到 stdout。命令 <log
為另一個 Z shell 捷徑,它與 cat
相同。(因此,命令 > file
等同於 cat > file
。)
Z shell 還可處理多個輸入重新導向。Z shell 命令列 cat < file1 < file2 < file3
等同於 cat file1 file2 file3
。顯然,原有文法較後者更加繁瑣,總的來說,多個 stdout 重新導向要更加常用的多。但是,如果您要啟動並執行工具 + 生產力不接受多個輸入參數,則可使用 Z shell 的多個輸入重新導向。
Z shell 中還具有其他新特性,包括更好的 globbing(萬用字元匹配)、先進的模式比對和擴充的命令自動完成系統,從而減少您在命令列中的字元輸入。本系列中的後續兩篇文章將進一步探討 Z shell。
Shell 技巧
通過一些功能強大的命令列組合,能明顯提高您的工作效率。這些命令可以在所有的 shell 中工作,而不僅僅是 zsh
。
使用 tar
為任何目錄建立包括符號連結在內的完整副本:
tar cf - /path/to/original | / (mkdir -p /path/to/copy; cd /path/to/copy; tar xvf -) |
第一個 tar
命令將目錄 /path/to/original 進行歸檔並將歸檔檔案寫到 stdout,建立 (c
) 選項後面使用的連字號 (-
) 表示 stdout。括弧中的命令為一個 subshell:subshell 中的命令不會影響當前 shell 的環境。mkdir -p
建立指定目錄,包括任何需要建立的中間目錄;cd
命令則切換到新目錄。第二個 tar
命令從 stdin 讀取歸檔檔案並進行展開,展開 (x
) 選項後面使用的連字號表示 stdin。
要在儲存命令序列的 stdout 同時進行查看,可使用 less -O file
。-O
選項會將 stdin 複製到指定的 file
中。如下例所示:
sort /etc/aliases | less -Osorted |
如果目錄中包含數千個檔案,則您的 shell(包括 zsh
,取決於檔案數目及其名稱)可能無法使用萬用字元匹配來列舉出所有檔案,因為命令列通常具有一定的字元數限制。因此,類似以下 shell 指令碼:
可能會執行失敗。(當超出允許命令列長度時,您可能看到類似 Line length exceeded
的訊息。)如果出現此類錯誤,可使用管道 xargs
工具 + 生產力。xargs
命令可從管道中讀取資料並為每行讀取內容運行指定命令。
例如,如果您要尋找伺服器上的所有引用 www.example.com 的網頁,可使用以下命令列:
% find / -name '*html' -print / | xargs grep -l 'www.example.com' / | less -Opages |
xargs
接收來自 find
的檔案名稱並重複運行 grep -l
以處理每個檔案,而不論有多少個檔案。(grep -l
在發現一個匹配項之後即列印檔案的名稱並停止在該檔案中的進一步匹配。) less
允許您對結果進行分頁並將列表儲存在檔案指定頁中。命令結果為包含字串“www.example.com”的檔案名稱列表。