來源:IBM / DW
簡介
UNIX 的基本哲學之一就是建立只做一件事並將這一件事做好的程式(或程式)。這一哲學要求認真考慮介面以及結合這些更小(有望更簡單)流程的方法,以產生有用的 結果。文字資料通常是在這些介面之間流動。多年以來,越來越高階的文字處理工具和語言已經開發出來。從語言上來講,早期專門處理文字的語言有 perl,後來又出現了 python 和 ruby。雖然這些語言以及其他語言都是非常強大的文字處理器,但這些工具並不是一直可行的,在生產環境下尤其如此。本文將介紹一些基本的 UNIX 文字處理命令,這些命令既可單獨使用也可結合使用,可用來解決需要更新的語言才能解決的問題。對許多人來說,與長篇大論的解釋相比,例項能夠提供更多的信 息。請注意,由於有多種可用的 UNIX 和 UNIX 類系統,因此不同實現之間的命令標誌、程式行為和輸出結果會略有不同。
使用 cat
cat 命令是最基本的命令之一。這個命令用來建立、追加、顯示以及合併文字檔案。
我們可以使用 cat 命令建立檔案,方法是:使用 ‘>’ 將標準輸入 (stdin) 重定向到檔案。使用 ‘>’ 操作符會縮短指定輸出檔案的內容。在此之後輸入的文字會重定向到 ‘>’ 操作符右側指定的檔案。control-d 表示檔案結束,將控制權返回給 shell。
使用 cat 建立檔案的示例
1 2 3 4 5 6 |
$ cat > grocery.list apples bananas plums <ctrl-d> $ |
使用 ‘>>’ 操作符將標準輸入追加到現有檔案。
使用 cat 追加檔案的示例
1 2 |
$ cat >> grocery.list carrots |
使用 cat 命令不加標誌,可檢視 grocery.list 檔案的內容。請注意檔案的內容如何包含來自重定向的輸入以及追加操作符的示例。
使用無標誌 cat 的示例
1 2 3 4 5 |
$ cat grocery.list apples bananas plums carrots |
可以使用 cat 命令對檔案行進行編號。
使用 cat 計算行的示例:
1 2 3 4 5 |
$ cat -n grocery.list 1 apples 2 bananas 3 plums 4 carrots |
使用 nl
nl 過濾器會從 stdin 或指定檔案讀取行。輸出則會寫入 stdout 並重定向到檔案,或傳到另一個程式中。nl 的行為是由不同命令列選項控制的。
在預設情況下,nl 會計算行數,與 cat -n 的功能類似。
nl 預設用法示例:
1 2 3 4 5 |
$nl grocery.list 1 apples 2 bananas 3 plums 4 carrots |
使用 -b 標誌指定要進行編號的行。此標誌將引數作為 “型別”。該型別告訴 nl 需要給哪些行編號,使用 ‘a’ 給所有行編號,‘t’ 告訴 nl 不對空行和只有空格的行進行編號,‘n’ 指定不編號行。在示例中顯示針對模式的型別 ‘p’。nl 給正規表示式模式指定的行編號,在本用例中,是以字母 ‘a’ 或 ‘b’ 開始的行。
使用 nl 對符合正規表示式的行進行編號的示例
1 2 3 4 5 |
$ nl -b p^[ba]grocery.list 1 apples 2 bananas plums carrots |
在預設情況下,nl 行號和文字之間使用製表符進行分隔。使用 -s 指定其他分隔符,例如 ‘=’ 號。
使用 nl 指定其他分隔符的示例
1 2 3 4 5 |
$nl -s= grocery.list 1=apples 2=bananas 3=plums 4=carrots |
使用 wc
wc (wordcount) 命令計算指定檔案或來自 stdin 的行數、單詞數(由空格分隔)和字元數。
wc 用法示例
1 2 3 4 5 6 7 8 |
$wc grocery.list 4 4 29 grocery.list $wc -l grocery.list 4 grocery.list $wc -w grocery.list 4 grocery.list $wc -c grocery.list 29 grocery.list |
使用 grep
grep 命令在指定檔案或 stdin 中搜尋與給定表示式相匹配的模式。grep 的輸出由多個選項標誌控制。
為了演示,這裡新建立了一個檔案,與 grocery.list 配合使用。
1 2 3 4 5 6 |
$cat grocery.list2 Apple Sauce wild rice black beans kidney beans dry apples |
grep 基本用法示例
1 2 3 |
$ grep apple grocery.list grocery.list2 grocery.list:apples grocery.list2:dry apples |
grep 擁有相當可觀的選項標誌。下面的示例將演示其中幾個選項的用法。
要顯示檔名(處理多個檔案的情況下)以及發現模式匹配的行數,在本用例中使用的模式是計算每個檔案中出現單詞 ‘apple’ 的行數。
grep 示例:計算檔案中匹配數
1 2 3 |
$ grep -c apple grocery.list grocery.list2 grocery.list:1 grocery.list2:1 |
在搜尋多個檔案時,可以使用 -h 選項取消在輸出中顯示檔名。
grep 示例:取消在輸出中顯示檔名
1 2 3 |
$ grep -h apple grocery.list grocery.list2 apples dry apples |
在很多情況下,需要進行不區分大小寫的搜尋。grep 命令的 -i 選項可以在搜尋時忽略大小寫。
grep 示例:不區分大小寫
1 2 3 4 |
$ grep -i apple grocery.list grocery.list2 grocery.list:apples grocery.list2:Apple Sauce grocery.list2:dry apples |
有些時候,只需要輸出檔名,不需要輸出模式匹配的行。grep 提供 -l 選項,用於只輸出包含匹配模式行的檔名。
grep 示例:只輸出檔名
1 2 |
$ grep -l carrot grocery.list grocery.list2 grocery.list |
行號可以顯示在輸出中。使用 -n 選項來包含行號。
grep 示例:包含行號
1 2 |
$ grep -n carrot grocery.list grocery.list2 grocery.list:4:carrots |
有時期望輸出與模式不匹配的行。這時就要使用 -v 選項。
grep 示例:輸出不匹配行
1 2 3 4 |
$ grep -v beans grocery.list2 Apple Sauce wild rice dry apples |
有時,需要的模式是一個單詞,兩邊被空格或其他字元(例如連字元或括號)包圍。grep 的多數版本都提供了 -w 選項,能方便地編寫此類模式的搜尋。
grep 示例:單詞匹配
1 2 3 |
$ grep -w apples grocery.list grocery.list2 grocery.list:apples grocery.list2:dry apples |
流、管道、重定向、tee 和 here docs
在 UNIX 中,一個終端預設包含三個流,一個輸入流,兩個基於輸出的流。輸入流稱為 stdin,通常對映到鍵盤(也可使用其他輸入裝置,或從其他程式中傳入)。標準輸出流稱為 stdout,並通常輸出到終端上,輸出也可供其他程式使用(就像 stdin 一樣)。另一個輸出流 stderr 主要用作狀態報告,通常輸出到終端,如 stdout。這三個流都有各自的檔案描述符,每一個流都可以從其他流中傳入或重定向,即使是它們全都連線到終端。每個流的檔案描述符分別是:
●stdin = 0
●stdout = 1
●stderr = 2
這三個流可以傳送或重定向到檔案或其他程式。這個構造通常稱為 “構建一個管道”。例如,程式設計師可能想將 stdout 流和 stderr 流合併,然後在終端上顯示它們,再將結果儲存到檔案中以便檢查版本問題。使用 2>&1,stderr 流以及檔案描述符 2 會被重定向到 &1(指向 stdout 流)。這樣就能有效地將 stderr 合併到 stdout。使用 ‘|’ 符號表示管道。管道連線左側程式 (make) 的 stdout 和右側程式 (tee) 的 stdin。tee 命令會複製(合併)將資料傳送到終端以及檔案的 stdout 流,在本示例中,稱為 build.log。
合併和拆分標準流的示例
1 |
$ make –f build_example.mk 2>&1 | tee build.log |
另一個重定向示例,使用 cat 命令和一些流重定向製作文字檔案副本。
使用重定向製作備份檔案的示例
1 |
$ cat < grocery.list > grocery.list.bak |
前面使用 nl 命令為在 stdout 中顯示的檔案加行號。管道可用於將 stdout 流(來自 cat grocery.list)傳送到另外一個程式,在本用例中,是 nl 命令。
簡單管道傳送到 nl 的示例
1 2 3 4 5 |
$ cat grocery.list | nl 1 apples 2 bananas 3 plums 4 carrots |
先前顯示的另一個示例是針對模式執行不區分大小寫的搜尋。這也可以使用重定向來實現(在這本用例中為來自 stdin 或使用管道,與上述簡單管道示例相似)。
grep 示例:通過 stdin 重定向和管道
1 2 3 4 5 6 |
$ grep -i apple < grocery.list2 Apple Sauce dry apples $cat grocery.list2 | grep -i apple Apple Sauce dry apples |
在某些情況下,要將文字塊重定向到某個命令或檔案中作為指令碼的一部分。實現此操作的機制是使用 ‘here document’(或 ‘here-doc’)。要將 here-doc 嵌入到指令碼,需要使用 ‘<
示例:命令列上的基本 here-doc
1 2 3 4 5 6 7 |
$ cat << EOF 3 > oranges > mangos > pinapples > EOF oranges mangos pinapples |
可將此輸出重定向到檔案,在本示例中,分隔符 ‘EOF’ 改為 ‘!’。然後使用 tr 命令(稍後說明)將 here-doc 中的字母全部變成大寫。
示例:將基本 here-doc 重定向到檔案
1 2 3 4 5 6 7 8 9 10 11 12 |
cat << ! > grocery.list3 oranges mangos pinapples ! $ cat grocery.list3 oranges mangos pinapples $tr [:lower:] [:upper:] << ! 12 > onions > ! ONIONS |
使用 head 和 tail
head 和 tail 命令用來檢視檔案的頂部 (head) 或底部 (tail)。要顯示檔案頂部兩行和底部兩行,請分別使用這兩個命令加 -n 選項標誌。相同地,-c 選項顯示檔案中的前幾個或最後幾個字元。
示例:head 和 tail 命令的基本用法
1 2 3 4 5 6 7 8 9 10 11 12 |
$ head -n2 grocery.list apples bananas $ tail -n2 grocery.list plums carrots $ head -c12 grocery.list apples banan $ tail -c12 grocery.list ums carrots |
tail 命令的常見用途就是觀察日誌檔案或者正在執行的程式輸出,檢視其中是否有問題,或者關注程式何時結束。-f (tail –f) 選項使 tail 持續觀察流,即使是到達檔案結束標記也繼續觀察,並在流包含更多資料時,持續顯示輸出。
使用 tr
tr 命令用來轉換來自 stdin 的字元,在 stdout 中顯示。tr 一般接受兩個字符集合,用第二個集合中的字元替換第一個集合中的字元。有許多預定義的字元類(集合)可供 tr 使用,還有其他命令可用。
這些預定義的字元類是:
●alnum:字母數字字元
●alpha:字母字元
●blank:空白字元
●cntrl:控制字元
●digit:數字字元
●graph:圖形字元
●lower:小寫字母字元
●print:可列印字元
●punct:標點字元
●space:空間字元
●upper:大寫字元
●xdigit:16 進位制字元
tr 命令夠將字串中的小寫字元轉換成大寫。
tr 示例:將字串轉換成大寫
1 2 |
$ echo "Who is the standard text editor?" |tr [:lower:] [:upper:] WHO IS THE STANDARD TEXT EDITOR? |
tr 可以用來從字串中刪除指定字元。
tr 示例:從字串中刪除指定字元
1 2 |
$ echo 'ed, of course!' |tr -d aeiou d, f crs! |
使用 tr 將字串中任何指定字元轉換成空格。在序列中遇到多個指定字元時,它們會轉換成一個空格。
-s 選項標誌的行為在不同系統中表現不同。
tr 示例:將字元轉變成空格
1 2 |
$ echo 'The ed utility is the standard text editor.' |tr -s astu ' ' The ed ili y i he nd rd ex edi or. |
-s 選項標誌可以用來取消字串中多餘的空格。
1 2 3 4 |
$ echo 'extra spaces – 5’ | tr -s [:blank:] extra spaces - 5 $ echo ‘extra tabs – 2’ | tr -s [:blank:] extra tabs – 2 |
在基於 UNIX 和 Windows 系統之間轉換檔案時發生的常見問題就是行分隔符 (line delimiters)。在 UNIX 系統中,行分隔符為一個換行符,而在 Windows 系統中,則是用兩個字元(即一個回車符和一個換行符)。使用 tr 配合某種重定向,可以解決這個格式問題。
tr 示例:消除回車符
1 |
$ tr -d '\r' < dosfile.txt > unixfile.txt |
使用 colrm
使用 colrm,可以從流中剪下出文字列。在第一個示例中,使用 colrm 命令從管道的每行文字中剪下出第 4 列到行尾。然後,將同一個檔案傳送至 colrm,以刪除第 4 列到第 5 列。
使用 colrm 刪除列的示例
1 2 3 4 5 6 7 8 9 10 |
$ cat grocery.list |colrm 4 app ban plu car $ cat grocery.list |colrm 4 5 apps banas plu carts |
使用 expand 和 unexpand
expand 命令將製表符變成空格,而 unexpand 將空格變成製表符。這兩個命令都接受 stdin 輸入以及命令列指定檔案的輸入。使用 -t 選項可以設定一個或多個製表符停止位。
expand 和 unexpand 示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ cat grocery.list|head -2|nl|nl 1 1 apples 2 2 bananas $ cat grocery.list|head -2|nl|nl|expand -t 5 1 1 apples 2 2 bananas $ cat grocery.list|head -2|nl|nl|expand -t 5,20 1 1 apples 2 2 bananas $ cat grocery.list|head -2|nl|nl|expand -t 5,20|unexpand -t 1,5 1 1 apples 2 2 bananas |
使用 comm、cmp 和 diff
為了演示這些命令,要新建兩個檔案。
新建演示檔案:
1 2 3 4 5 6 7 8 9 10 11 12 |
cat << EOF > dummy_file1.dat 011 IBM 174.99 012 INTC 22.69 013 SAP 59.37 014 VMW 102.92 EOF cat << EOF > dummy_file2.dat 011 IBM 174.99 012 INTC 22.78 013 SAP 59.37 014 vmw 102.92 EOF |
diff 命令會對兩個檔案進行比較,報告兩者之間的不同之處。diff 可接受多種選項標誌。在下面示例中,首先顯示預設的 diff,然後是使用 -w 選項的 diff 忽略空格,並以使用 -i 選項標誌在比較中忽略大小寫區別而結束。
diff 命令的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ diff dummy_file1.dat dummy_file2.dat 1,2c1,2 < 011 IBM 174.99 < 012 INTC 22.69 06 --- 07 > 011 IBM 174.99 > 012 INTC 22.78 4c4 < 014 VMW 102.92 11 --- 12 > 014 vmw 102.92 $ diff -w dummy_file1.dat dummy_file2.dat 2c2 < 012 INTC 22.69 17 --- 18 > 012 INTC 22.78 4c4 < 014 VMW 102.92 21 --- 22 > 014 vmw 102.92 $ diff -i dummy_file1.dat dummy_file2.dat 1,2c1,2 < 011 IBM 174.99 < 012 INTC 22.69 28 --- 29 > 011 IBM 174.99 > 012 INTC 22.78 |
comm 命令會對兩個檔案進行比較,但比較的方式與 diff 差別很大。comm 產生三列輸出,僅出現在第 1 個檔案(第 1 列)的行,僅出現在第 2 個檔案(第 2 列)的行,兩個檔案中都有的常見行(第 3 列)。可使用選項標誌來取消輸出列。此命令可能在取消第 1 列和第 2 列時最有用,只顯示兩個檔案中常見的行,如下所示。
comm 命令示例
1 2 3 4 5 6 7 8 9 10 |
$ comm dummy_file1.dat dummy_file2.dat 011 IBM 174.99 011 IBM 174.99 012 INTC 22.69 012 INTC 22.78 013 SAP 59.37 014 VMW 102.92 014 vmw 102.92 $ comm -12 dummy_file1.dat dummy_file2.dat 013 SAP 59.37 |
cmp 命令也會對這兩個檔案進行比較。但是,與 comm 或 diff 不同,cmp 命令(預設)報告這兩個檔案剛開始不同的位元組和行號。
cmp 命令示例
1 2 |
$ cmp dummy_file1.dat dummy_file2.dat dummy_file1.dat dummy_file2.dat differ: char 5, line 1 |
使用 fold
使用 fold 命令可以將行拆分為指定的寬度。這個命令最初是用來對無法支援換行的定寬輸出裝置進行文字格式化。-w 選項標誌允許使用指定行寬,而不是隻使用預設的 80 列。
使用 fold 示例
1 2 3 4 5 6 7 8 9 |
$ fold -w8 dummy_file1.dat 011 IBM 174.99 012 INTC 22.69 013 SAP 59.37 014 VMW 102.92 |
使用 paste
paste 命令用來合併檔案,將每個檔案的記錄逐一合併。利用重定向,可以通過將一個檔案中的每個記錄與另一個檔案的記錄合併,來新建檔案。
新建演示檔案:
1 2 3 4 5 6 7 8 9 10 11 12 |
cat << EOF > dummy1.txt IBM INTC SAP VMW EOF cat << EOF > dummy2.txt 174.99 22.69 59.37 102.92 EOF |
paste 示例:來自多檔案的行
1 2 3 4 5 |
$ paste dummy1.txt dummy2.txt grocery.list IBM 174.99 apples INTC 22.69 bananas SAP 59.37 plums VMW 102.92 carrots |
-s 選項標誌用來一次處理多個檔案(連續地),而不是並行處理。請注意,下面的列與上面示例中的行合併。
paste 示例 2:來自多檔案的行
1 2 3 4 |
$ paste -s dummy1.txt dummy2.txt grocery.list IBM INTC SAP VMW 174.99 22.69 59.37 102.92 apples bananas plums carrots |
如果只指定一個檔案,或者 paste 正處理 stdin,輸入會預設顯示在一個列中。使用 -s 選項標誌,輸出會顯示在一個行中。由於輸出縮減到一行,所以使用分隔符來分隔返回的域(預設的分隔符是製表符)。在本示例中,使用 find 命令尋找 64 位庫所在的目錄,然後構建一個合適的路徑,附加到變數 $LD_LIBRARY_PATH 中。
paste 示例:使用分隔符
1 2 3 4 5 6 7 8 |
$ find /usr -name lib64 -type d|paste -s -d: /usr/lib/qt3/lib64:/usr/lib/debug/usr/lib64:/usr/X11R6/lib/X11/locale/lib64:/usr/X11R6/ lib64:/usr/lib64:/usr/local/ibm/gsk7_64/lib64:/usr/local/lib64 $ paste -d, dummy1.txt dummy2.txt IBM,174.99 INTC,22.69 SAP,59.37 VMW,102.92 |
使用 bc
在 Shell 上進行算術計算的簡易方法是使用 bc(“basic calculator” 或 “bench calculator”)。有些 shell 自帶了算術計算功能,有些則依靠 expr 對錶達式進行運算。使用 bc,計算可以在不同的 Shell 和 UNIX 系統間移植,只要注意不同廠商的擴充套件即可。
bc 示例:簡單計算
1 2 3 4 5 6 7 8 9 |
$ echo 2+3|bc 5 $ echo 3*3+2|bc 11 $ VAR1=$(echo 2^8|bc) $ echo $VAR1 256 $ echo "(1+1)^8"|bc 256 |
bc 不僅可以執行這些簡單計算。它是一個直譯器,有自己內部的和使用者自定義的函式、語法和流程控制,就像程式語言一樣。在預設情況下,bc 在小數點右側不包含任何數字。要提高輸出的精度,需要使用特殊的 scale 變數。如示例所示,bc 支援大數字,可實現更長的精度。使用 obase 或 ibase 可以控制輸入和輸出數字的轉換基礎。在下面的示例中:
●obase 改變預設的輸入基(10 進位制),將結果轉變成 16 進位制
●對於 2 的平方根,scale 指定了小數點右側的數字個數
●求 2 的 128 次方演示了對大數字的支援
●呼叫內部函式 sqrt() 計算 2 的平方根
●在 ksh 中,計算和輸出百分比
bc 示例:更多計算
1 2 3 4 5 6 7 8 9 10 11 12 |
$ echo "obase=16; 2^8-1"|bc FF $ echo "99/70"|bc 1 $ echo "scale=20; 99/70"|bc 1.41428571428571428571 $ echo "scale=20;sqrt(2)"|bc 1.41421356237309504880 $ echo 2^128|bc 340282366920938463463374607431768211456 $ printf "Percentage: %2.2f%%\n" $(echo .9963*100|bc) Percentage: 99.63% |
的手冊頁面中有詳細說明,並有相關示例。
使用 split
split 命令的一大作途就是將大型資料檔案分解成小的檔案以方便處理。在本示例中,BigFile.dat 經 wc 命令統計有 165782 行。-l 選項標誌規定了 split 為每個輸出檔案生成的最大行數。split 支援為輸出檔名指定字首,例如下面的示例指定 BigFile_ 為字首。其他選項支援字尾控制,在 BSD 系統上的 -p 選項標誌支援按正規表示式進行拆分,就像 csplit(上下文拆分)命令一樣。更多資訊請參閱手冊頁面。
split 示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ wc BigFile.dat 165782 973580 42557440 BigFile.dat $ split -l 15000 BigFile.dat BigFile_ $ wc BigFile* 165782 973580 42557440 BigFile.dat 15000 87835 3816746 BigFile_aa 15000 88483 3837494 BigFile_ab 15000 89071 3871589 BigFile_ac 15000 88563 3877480 BigFile_ad 15000 88229 3855486 BigFile_ae 7514 43817 1908914 BigFile_af 248296 1459578 63725149 total |
使用 cut
cut 命令用來 “裁剪” 檔案中以列為基礎小節或從 stdin 傳送而來的資料。它可按位元組 (-b)、字元 (-c) 和列表指定的域 (-f) 進行修剪。使用逗號分隔列表和連字元指定域列表或位元組/字元位置。如果只需要輸出一個位置或域,則直接指定位置或域即可。可以使用連字元指定一系列域,例 如 1-3 輸出 1-3 域(或位置),-2 從行開始前兩個域(或位元組/字元)開始輸出,3- 則讓 cut 從域(或位置)3 開始輸出到行尾。多個域之間以逗號分隔。其他有用的標誌有:-d 指定域分隔符,-s 取消沒有分隔符的行。
cut 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
$ cat << EOF > dummy_cut.dat # this is a data file ID,Name,Score 13BA,John Smith,100 24BC,Mary Jones,95 34BR,Larry Jones,94 36FT,Joe Ruiz,93 40RM,Kay Smith,91 EOF $ cat dummy_cut.dat |cut -d, -f1,3 # this is a data file ID,Score 13BA,100 24BC,95 34BR,94 36FT,93 40RM,91 $ cat dummy_cut.dat |cut -b6- s is a data file me,Score John Smith,100 Mary Jones,95 Larry Jones,94 Joe Ruiz,93 Kay Smith,91 $ cat dummy_cut.dat |cut -f1- -d, -s ID,Name,Score 13BA,John Smith,100 24BC,Mary Jones,95 34BR,Larry Jones,94 36FT,Joe Ruiz,93 40RM,Kay Smith,91 |
使用 uniq
uniq 命令通常用來惟一地列出輸入源(通常是檔案或 stdin)中的行。要正確操作,重複的行必須連續放置於輸入中。uniq 命令的輸入通常會進行排序,因此重複的行會進行合併。與 uniq 命令搭配使用的兩個常用標誌是:-c 輸出每行出現的次數,-d 用來顯示重複行的一個例項。
uniq 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
$ cat << EOF > dummy_uniq.dat 13BAR Smith John 100 13BAR Smith John 100 24BC Jone Mary 95 34BRR Jones Larry 94 36FT Ruiz Joe 93 40REM Smith Kay 91 13BAR Smith John 100 99BAR Smith John 100 13XIV Smith Cindy 91 EOF $ cat dummy_uniq.dat | uniq 13BAR Smith John 100 24BC Jone Mary 95 34BRR Jones Larry 94 36FT Ruiz Joe 93 40REM Smith Kay 91 13BAR Smith John 100 99BAR Smith John 100 13XIV Smith Cindy 91 $ cat dummy_uniq.dat | sort |uniq 13BAR Smith John 100 13XIV Smith Cindy 91 24BC Jone Mary 95 34BRR Jones Larry 94 36FT Ruiz Joe 93 40REM Smith Kay 91 99BAR Smith John 100 $ cat dummy_uniq.dat | sort |uniq -d 13BAR Smith John 100 $ cat dummy_uniq.dat | sort |uniq -c 3 3 13BAR Smith John 100 1 13XIV Smith Cindy 91 1 24BC Jone Mary 95 1 34BRR Jones Larry 94 1 36FT Ruiz Joe 93 1 40REM Smith Kay 91 1 99BAR Smith John 100 |
使用 sort
要按指定順序對 stdin 或檔案的內容排序,例如按字母順序或數字順序,則可以使用 sort 命令。在預設情況下,sort 的輸出寫在 stdout 中。LC_ALL、LC_COLLATE 和 LANG 等環境變數可以影響 sort 及其他命令的輸出。請注意,示例檔案顯示了 2 個分離的重複記錄,一個重複是 IBM,另一個重複是空行。
sort 示例:預設行為
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ cat << EOF > dummy_sort1.dat 014 VMW, 102.92 013 INTC, 22.69 012 sap, 59.37 011 IBM, 174.99 011 IBM, 174.99 EOF $ sort dummy_sort1.dat 011 IBM, 174.99 011 IBM, 174.99 012 sap, 59.37 013 INTC, 22.69 014 VMW, 102.92 |
sort 有一個非常強大的標誌,在多數情況下可以代替 uniq 命令。-u 選項標誌對檔案進行排序,刪除重複行,以生成一個只包含惟一行的輸出清單。
sort 示例:惟一排序
1 2 3 4 5 |
$ sort -u dummy_sort1.dat 011 IBM, 174.99 012 sap, 59.37 013 INTC, 22.69 014 VMW, 102.92 |
有時,希望將輸入倒序輸出。在預設情況下,sort 按從小到大(數字)和字元資料的字母順序排序。使用 -r 選項標誌可以將預設排序倒過來。
sort 示例:倒序排序
1 2 3 4 5 |
$ sort -ru dummy_sort1.dat 014 VMW, 102.92 013 INTC, 22.69 012 sap, 59.37 011 IBM, 174.99 |
不同情況下,可能要求根據某種域或 “鍵” 對檔案進行排序。幸運的是,sort 的 -k 選項標誌支援按位置指定排序鍵。域之間預設以空格分隔。
sort 示例:按鍵排序
1 2 3 4 5 |
$ sort -k2 -u dummy_sort1.dat 011 IBM, 174.99 013 INTC, 22.69 014 VMW, 102.92 012 sap, 59.37 |
如果需要區分大小寫,sort 的 -f 選項標誌可以在進行比較時忽略大小寫。在結合如下所示的多個標誌時,有些版本的 UNIX 需要用不同的順序指定這些標誌。
sort 示例:不區分大小寫排序
1 2 3 4 5 |
$ sort -k2 -f -u dummy_sort1.dat 011 IBM, 174.99 013 INTC, 22.69 012 sap, 59.37 014 VMW, 102.92 |
以上的排序均是對字母的排序。如果需要按數字順序對資料排序,則要使用 -n 選項標誌。
sort 示例:按數字排序
1 2 3 4 5 |
$ sort -n -k3 -u dummy_sort1.dat 013 INTC, 22.69 012 sap, 59.37 014 VMW, 102.92 011 IBM, 174.99 |
有些輸入可能不使用空格而是使用字元在行中區分域。使用 -t 選項標誌指定非預設分隔符,例如逗號。
sort 示例:使用非預設分隔符對域排序
1 2 3 4 5 |
$ sort -k2 -t"," -un dummy_sort1.dat 013 INTC, 22.69 012 sap, 59.37 014 VMW, 102.92 011 IBM, 174.99 |
使用 join
凡是熟悉資料庫查詢編寫的人,都認得 join 命令這個實用工具。與多數 UNIX 命令一樣,這個命令的輸出也顯示在 stdout 中。要將檔案連線 (“join”) 在一起,請逐行比較來自兩個檔案中指定的域。如果沒有指定域,join 則會從每一行的開始進行域區匹配。預設域分隔符是空格(有些系統使用一個空格,有的則使用相鄰多個空格)。找到域匹配後,會根據域匹配的兩行輸出一行結果。要得到合理的結果,每個檔案都應該按照匹配的域進行排序。各個系統實現 join 的方式略有不同。
本示例使用 -t 指定域分隔符,並演示了在逗號分隔的第一個域(預設)上對兩個檔案進行連線。資料庫操作人員將其看作是內部連線,只顯示匹配的行。
join 示例:使用非預設域分隔符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
cat << EOF > dummy_join1.dat 011,IBM,Palmisano 012,INTC,Otellini 013,SAP,Snabe 014,VMW,Maritz 015,ORCL,Ellison 017,RHT,Whitehurst EOF cat << EOF > dummy_join2.dat 011,174.99,14.6 012,22.69,10.4 013,59.37,26.4 014,102.92,106.1 016,27.77,31.2 EOF cat << EOF > dummy_join3.dat IBM,Armonk INTC,Santa Clara SAP,Walldorf VMW,Palo Alto ORCL,Redwood City EMC,Hopkinton EOF $ join -t, dummy_join1.dat dummy_join2.dat 011,IBM,Palmisano,174.99,14.6 012,INTC,Otellini,22.69,10.4 013,SAP,Snabe,59.37,26.4 014,VMW,Maritz,102.92,106.1 |
要指定在每個檔案中 “連線” 哪個域,可以使用 -j[1,2] x 選項標誌(或者只使用 -1 x 或 -2 x)。選項標誌 -j1 2 或 -1 2 指定第 1 個檔案的第 2 個域,第 1 個檔案是命令中列出的第一個檔案。本示例將演示如何根據第 1 個檔案的第 1 個域和第 2 個檔案的第 2 個域連線檔案,這個連線也是內部連線,只連線匹配的行。
join 示例:指定域
1 2 3 4 5 6 |
$ join -t, -j1 1 -j2 2 dummy_join3.dat dummy_join1.dat IBM,Armonk,011,Palmisano INTC,Santa Clara,012,Otellini SAP,Walldorf,013,Snabe VMW,Palo Alto,014,Maritz ORCL,Redwood City,015,Ellison |
與資料庫相關示例概念一致,可以用標誌實現一個左外連線 (left outer join)。左外連線包含左側第一個檔案或表中的所有行以及第二個檔案或表中的匹配行。使用 -a 可以包含指定檔案中的所有行。
join 示例:左外連線
1 2 3 4 5 6 7 |
$ join -t, -a1 dummy_join1.dat dummy_join2.dat 011,IBM,Palmisano,174.99,14.6 012,INTC,Otellini,22.69,10.4 013,SAP,Snabe,59.37,26.4 014,VMW,Maritz,102.92,106.1 015,ORCL,Ellison 017,RHT,Whitehurst |
全外連線包含兩個檔案或表中的所有行,並不關心域是否匹配。可以使用 -a 選項標誌指定兩個檔案來實現全外連線。
join 示例:全外連線
1 2 3 4 5 6 7 8 |
$ join -t, -a1 -a2 -j1 2 -j2 1 dummy_join1.dat dummy_join3.dat IBM,011,Palmisano,Armonk INTC,012,Otellini,Santa Clara SAP,013,Snabe,Walldorf VMW,014,Maritz,Palo Alto ORCL,015,Ellison,Redwood City EMC,Hopkinton 017,RHT,Whitehurst |
使用 sed
sed 流編輯器 (stream editor) 是個有用的文字解析和操作實用工具,可以方便地進行檔案或資料流的轉換。它一次一行地讀取文字,在文字行中應用指定的命令。預設輸出到 stdout。sed 使用的命令可以執行多種操作,如刪除緩衝區的文字、將文字附加或插入到緩衝區、寫入到一個檔案中,以及根據正規表示式轉換文字等。
sed 替換的基本示例顯示使用 -e 選項標誌來指定表示式或編輯文字。在一個 sed 執行中可以指定多個表示式或編輯文字。請注意 sed 文字編輯的元件。文字編輯開始的 “s” 代表這是個替換命令。使用 “/” 作為分隔符,先指明要替換的 “IBM”。接下來,替換模式出現在兩個 “/” 分隔符之間。最後,“g” 指明在當前文字緩衝區中進行全域性修改。本示例的第三個演示解釋了三個文字編輯的組合:用斜槓代替反斜槓,用下劃線代替空格,以及刪除冒號(請注意其中反斜 槓 “\” 字元的轉義表示方式)。
sed 示例:基本替換/多個編輯文字
1 2 3 4 5 6 |
$ echo "IBM 174.99" |sed –e 's/IBM/International Business Machines/g' International Business Machines 174.99 $ echo "Oracle DB"|sed -e 's/Oracle/IBM/g' -e 's/DB/DB2/g' IBM DB2 $ echo "C:\Program Files\PuTTY\putty.exe"| sed -e 's/\\/\//g' -e 's/ /_/g' -e 's/://g' C/Program_Files/PuTTY/putty.exe |
在下面的示例中,將建立一個檔案來演示 sed 的另外一個特性。除了替換之外,篩選也是 sed 常用的功能。UNIX 的 grep 命令是常用的篩選器,在命令列上發現多種文字操作方式很常見。本示例將演示如何使用 sed 刪除命令刪除以 “#” 或以空格加 “#” 開始的行。同時還列出了採用相同模式的 grep 示例以作參考。
sed 示例:篩選
1 2 3 4 5 6 7 8 9 10 11 12 13 |
cat << EOF > dummy_sed.txt # top of file # the next line here # Last Name, Phone Smith, 555-1212 Jones, 555-5555 # last number EOF $ sed '/^[[:space:]]*#/d' dummy_sed.txt Smith, 555-1212 Jones, 555-5555 # last number $ grep -v ^[[:space:]]*# dummy_sed.txt Smith, 555-1212 Jones, 555-5555 # last number |
為了更好地理解 sed 行為,這裡要多演示幾個模式。為了讓這些模式有文字可處理,還新建了一個檔案。第一個 sed 模式顯示瞭如何從檔案列出的字串(檔名)中刪除最後 4 個字元。接著,該模式刪除圓點 (“.”) 右側的全部字元,即副檔名。還列出一個刪除空行的模式。特殊字元 “&” 允許在輸出中使用搜尋模式。在本示例中,IBM 是輸入模式的一部分,並使用 “&” 將其指定為輸出的一部分。本系列最後展示的一個模式顯示瞭如何使用 sed 刪除從基於 Windows 系統上傳輸過來的文字檔案的回車符。 要在命令列向指令碼中輸入 “^M”,請先按 control-v,再按 control-m。請注意,終端的特徵可能影響 control-v、control-m 組合的輸入。
sed 示例:更多模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
cat << EOF > filelist.txt PuTTY.exe sftp.exe netstat.exe servernames.list EOF $ sed 's/....$//' filelist.txt PuTTY sftp netstat servernames. $ sed 's/\..*$//g' filelist.txt PuTTY sftp netstat servernames $ sed '/^$/d' filelist.txt PuTTY.exe sftp.exe netstat.exe servernames.list $ echo "IBM 174.99" |sed 's/IBM/&-International Business Machines/g' IBM-International Business Machines 174.99 $ cat dosfile.txt | sed 's/^M//' > unixfile.txt |
sed 命令可以在指定的地址範圍內操作。下面的示例顯示了一些 sed 可以控制的定址方式。“-n” 選項標誌取消 sed 將每行輸入顯示在輸出的預設行為。在第一個示例中,sed 在檔案第 4 行到第 7 行上操作。請注意其中如何只顯示檔案最先列出的錶行(行 4 到 7)。接下來,sed 只顯示檔案中的第一行和最後一行。有些版本的 sed 支援使用模式來指定在命令上應用地址範圍。請注意在輸出中,只是從表中刪除了逗號,並未在註釋中刪除。
sed 示例:地址範圍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
cat << EOF > dummy_table.frag <!--This, is a comment. --> <p>This, is a paragraph.</p> <table border="1"> <tr> <td>row 1, 1st cell</td> <td>row 1, 2nd cell</td> </tr> <tr> <td>row 2, 1st cell</td> <td>row 2, 2nd cell</td> </tr> </table> <!--This, is another comment. --> EOF $ sed -n 4,7p dummy_table.frag <tr> <td>row 1, 1st cell</td> <td>row 1, 2nd cell</td> </tr> $ sed -n -e 1p -e \$p dummy_table.frag <!--This, is a comment. --> <!--This, is another comment. --> $ sed '/^<table/,/^<\/table/s/,//g' dummy_table.frag <!--This, is a comment. --> <p>This, is a paragraph.</p> <table border="1"> <tr> <td>row 1 1st cell</td> <td>row 1 2nd cell</td> </tr> <tr> <td>row 2 1st cell</td> <td>row 2 2nd cell</td> </tr> </table> <!--This, is another comment. --> |
表示式內的模式可以進行分組,並引用在輸出中。在許多環境中,這種做法很有用,例如在進行值交換或使用位置變數時。括號用來在表示式中標記出 模式,必須用反斜槓 \ 轉義(模式 \)。在表示式其他地方使用 \n 引用模式,其中 n 是模式在標記模式中的順序號。將表示式分解後,可以更容易理解它的工作方式:
模式 註解
/^#.*$/d 從輸出中刪除以 # 開始的行
/^$/d 從輸出中刪除空行
s/\([:a-z:]*\):\(.*\) /\2:\1 / 這個語句標記著以冒號結尾的第一串小寫字母,然後標記緊接冒號後的字串。在輸出時,這些標記的字串會交換位置。
sed 示例:分組模式
1 2 3 4 5 6 7 8 9 10 11 12 |
cat << EOF > sed_chown_example.txt # use sed to swap the group:owner to owner:group sudo chown dba:jdoe oraenv.ksh sudo chown staff:jdoe sysenv.ksh ... EOF $ sed '/^#.*$/d;/^$/d;s/\([:a-z:]*\):\(.*\) /\2:\1 /' sed_chown_example.txt sudo chown jdoe:dba oraenv.ksh sudo chown jdoe:staff sysenv.ksh ... |
使用 awk
awk 程式是個方便的文字操作工具,執行的工作包括文字的解析、篩選和簡易格式化。它的輸入來自 stdin 或檔案,預設在 stdout 上顯示輸出。awk 有不同的發行版,並使用不同的名稱,例如 nawk 和 gawk。不同版本和供應商提供的 awk 其行為也各不相同。awk 與本文介紹的其他命令不同,因為它是一個程式語言。該語言內建算術、字串操作、流程控制以及文字格式化的函式。程式設計師也可以自己定義函式,建立使用者自定義函式庫或獨立指令碼。因為 awk 包含的特性如此之多,所以這裡只演示少量的示例。欲瞭解更多資訊,請參閱 參考資料 小節或手冊頁面。
在本示例中,首先將 awk 作為篩選器,只輸出 Linux 系統上的完整檔案系統。在預設情況下,awk 使用空白來識別各個列。該示例檢查了第 5 列,因為此列顯示的是磁碟已用空間百分比。如果磁碟利用率是 100%,則第一個示例會在 stdout 中輸出記錄。接下來的語句對第一個示例做了擴充套件,對訊息進行格式化,可能要在電子郵件中傳送,或者放在訊息中寫入日誌檔案。接下來的示例顯示如何建立一個使用數值比較的匹配。
awk 示例:篩選器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ df –k Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 61438632 61381272 57360 100% / udev 255788 148 255640 1% /dev /dev/mapper/datavg 6713132 3584984 3128148 54% /data rmthost1:/archives/backup - - - - /backups rmthost1:/archives/ - - - - /amc rmthost1:/archives/data2 - - - - /data2 $ df -k |awk '$5 ~ /100%/ {print $0}' /dev/sda1 61438632 61381272 57360 100% / $ df -k |awk '$5 ~ /100%/ {printf("full filesystem: %s, mountpoint: %s\n",$6,$1)}' full filesystem: /, mountpoint: /dev/sda1 $ df -k |awk '$4 > 3000000 {print $0}' Filesystem 1K-blocks Used Available Use% Mounted on /dev/mapper/datavg 6713132 3584984 3128148 54% /data |
有時,資料不是用空白分隔的。例如 /etc/passwd 檔案就是用冒號 “:” 分隔的。本示例顯示了 awk 如何使用 -F 標誌輸出 /etc/passwd 中前 5 個條目的使用者名稱和 UID。接下來,將通過輸出 /etc/passwd 檔案中第 1 列的前 3 個字元來展示 awk 的 substr() 函式。
awk 示例:域分隔符/字串函式
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ cat /etc/passwd |awk -F: '{printf("%s %s\n", $1,$3)}' |head -5 root 0 daemon 1 bin 2 sys 3 adm 4 cat /etc/passwd |awk -F: '{printf("%s \n", substr($1,1,3))}'|head -5 roo dae bin sys adm |
很多時候,系統管理員或程式設計師會編寫自己的 awk 指令碼來執行某種作業。下面示例為 awk 程式求檔案中第 3 列中發現的數字的平均值。這個計算是通過手動將第 3 列資料新增到彙總變數中。NR 是一個特殊的內部變數,awk 用它來跟蹤已經處理的記錄數量。將總彙變數除以 NR,就得到了第 3 列的平均值。該程式會顯示中間結果和資料,所以很容易理解其中的邏輯。
awk 示例:程式/算術
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
cat << EOF > dummy_file2.dat 011 IBM 174.99 012 INTC 22.78 013 SAP 59.37 014 vmw 102.92 EOF $ cat avg.awk awk 'BEGIN {total=0;} {printf("tot: %.2f arg3: %.2f NR: %d\n",total, $3, NR); total+=$3;} END {printf "Total:%.3f Average:%.3f \n",total,total/NR}' $ cat dummy_file2.dat | avg.awk tot: 0.00 arg3: 174.99 NR: 1 tot: 174.99 arg3: 22.78 NR: 2 tot: 197.77 arg3: 59.37 NR: 3 tot: 257.14 arg3: 102.92 NR: 4 Total:360.060 Average:90.015 |
基於 Shell 的字串操作
Shell 可以是強大的程式語言。與 awk 一樣,Shell 提供了豐富的選項來執行字串操作、算術功能、陣列、流程控制,以及檔案操作。下面的幾個示例將顯示如何從某一方提取字串的各個部分。此操作並不改變字 符串的值,只是提取出需要的結果,通常用來對變數賦值。使用百分號 “%” 截斷模式的右側,並用 “#” 號截斷模式的左側。
Shell 指令碼的示例:字串提取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ cat string_example1.sh #!/bin/sh FILEPATH=/home/w/wyoes/samples/ksh_samples-v1.0.ksh echo '${FILEPATH} =' ${FILEPATH} " # the full filepath" echo '${#FILEPATH} =' ${#FILEPATH} " # length of the string" echo '${FILEPATH%.*} =' ${FILEPATH%.*} " # truncate right of the last dot" echo '${FILEPATH%%.*} =' ${FILEPATH%%.*} " # truncate right of the first dot" echo '${FILEPATH%%/w*} =' ${FILEPATH%%/w*} " # truncate right of the first /w" echo '${FILEPATH#/*/*/} =' ${FILEPATH#/*/*/} " # truncate left of the third slash" echo '${FILEPATH##/*/} =' ${FILEPATH##/*/} " # truncate left of the last slash" $ ./string_example1.sh ${FILEPATH}=/home/w/wyoes/samples/ksh_samples-v1.0.ksh # the full filepath ${#FILEPATH} = 42 # length of the string ${FILEPATH%.*}=/home/w/wyoes/samples/ksh_samples-v1.0 # truncate right of the last dot ${FILEPATH%%.*}=/home/w/wyoes/samples/ksh_samples-v1 # truncate right of the first dot ${FILEPATH%%/w*}=/home # truncate right of the first /w ${FILEPATH#/*/*/}=wyoes/samples/ksh_samples-v1.0.ksh # truncate left of the third slash ${FILEPATH##/*/}=ksh_samples-v1.0.ksh # truncate left of the last slash |
例如,系統管理員可能需要將一批 .jpg 檔案的副檔名全部修改成小寫字母。因為 UNIX 伺服器區分大小寫,而有些應用程式可能要求小寫副檔名,也有可能管理員只是想將副檔名標準化。手動或通過 GUI 介面修改大量檔案可能需要幾小時才能完成。下面的 shell 樣例指令碼顯示瞭解決這一問題辦法。該示例由兩個檔案組成。第一個是 setup_files.ksh,用來建立樣例目錄樹並使用一些檔案填充樹。它還建立了需要修改副檔名的檔案列表。第二個指令碼 fix_extension.ksh 讀取該檔案列表,修改檔案的副檔名。作為 mv 命令的一部分,% 字串操作符用來截斷檔名最後一個圓點 “.” 右側的字元(截斷副檔名)。在執行之後,兩個指令碼還使用 find 命令顯示成果。
Shell 指令碼示例:修改副檔名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
$ cat setup_files.ksh mkdir /tmp/mv_demo [ ! -d /tmp/mv_demo ] && exit cd /tmp/mv_demo mkdir tmp JPG 'pictures 1' touch a.JPG b.jpg c.Jpg d.jPg M.jpG P.jpg JPG_file.JPG JPG.file2.jPg file1.JPG.Jpg 'tmp/ pic 2.Jpg' 10.JPG.bak 'pictures 1/photo.JPG' JPG/readme.txt JPG/sos.JPG find . -type f|grep -i "\.jpg$" |sort| tee file_list.txt $ ./setup_files.ksh ./JPG.file2.jPg ./JPG/sos.JPG ./JPG_file.JPG ./M.jpG ./P.jpg ./a.JPG ./b.jpg ./c.Jpg ./d.jPg ./file1.JPG.Jpg ./pictures 1/photo.JPG ./tmp/pic 2.Jpg $ cd /tmp/mv_demo $ cat /tmp/fix_extension.ksh while read f ; do mv "${f}" "${f%.*}.jpg" done < file_list.txt find . -type f|grep -i "\.jpg$" |sort $ /tmp/fix_extension.ksh ./JPG.file2.jpg ./JPG/sos.jpg ./JPG_file.jpg ./M.jpg ./P.jpg ./a.jpg ./b.jpg ./c.jpg ./d.jpg ./file1.JPG.jpg ./pictures 1/photo.jpg ./tmp/pic 2.jpg |
本著建立有用可重用工具的精神,應該將修改副檔名的示例做得更通用。想到的一些改進有:傳遞進要修改的檔名稱流,例如通過管道傳遞。可 新增選項標誌來指定要修改的副檔名(例如 .mp3 或 .mov),並指定如何格式化副檔名(例如大寫、小寫,或大小寫混合)。可能性只受程式設計師的想象力和時間的限制。
結束語
UNIX 提供了各種以本機方式進行文字解析的工具,在很多情況下,不需要依賴那些系統上可能沒有安裝的特殊直譯器。本文只是寬泛地介紹了各種命令,並未對其用途進 行深入探討。本文中只對命令進行了部分演示,各個系統上實現的標誌或行為可能會各有所異。UNIX 提供了更多命令和方式來實現這些相同的任務,“有不止一種的方式來完成任務”。