UNIX 系統上的文本操作簡介
簡介
UNIX 的基本哲學之一就是創建只做一件事并將這一件事做好的程序(或進程)。這一哲學要求認真考慮接口以及結合這些更小(有望更簡單)流程的方法,以產生有用的 結果。文本數據通常是在這些接口之間流動。多年以來,越來越高級的文本處理工具和語言已經開發出來。從語言上來講,早期專門處理文本的語言有 perl,后來又出現了 python 和 ruby。雖然這些語言以及其他語言都是非常強大的文本處理器,但這些工具并不是一直可行的,在生產環境下尤其如此。本文將介紹一些基本的 UNIX 文本處理命令,這些命令既可單獨使用也可結合使用,可用來解決需要更新的語言才能解決的問題。對許多人來說,與長篇大論的解釋相比,實例能夠提供更多的信 息。請注意,由于有多種可用的 UNIX 和 UNIX 類系統,因此不同實現之間的命令標志、程序行為和輸出結果會略有不同。
cat
命令是最基本的命令之一。這個命令用來創建、追加、顯示以及合并文本文件。
我們可以使用 cat
命令創建文件,方法是:使用 ‘>’ 將標準輸入 (stdin
) 重定向到文件。使用 ‘>’ 操作符會縮短指定輸出文件的內容。在此之后輸入的文本會重定向到 ‘>’ 操作符右側指定的文件。control-d 表示文件結束,將控制權返回給 shell。
使用 cat 創建文件的示例
$ cat > grocery.list apples bananas plums <ctrl-d> $
使用 ‘>>’ 操作符將標準輸入追加到現有文件。
使用 cat 追加文件的示例
$ cat >> grocery.list carrots <ctrl-d>
使用 cat
命令不加標志,可查看 grocery.list 文件的內容。請注意文件的內容如何包含來自重定向的輸入以及追加操作符的示例。
使用無標志 cat 的示例
$ cat grocery.list apples bananas plums carrots
可以使用 cat
命令對文件行進行編號。
使用 cat 計算行的示例:
$ cat -n grocery.list 1 apples 2 bananas 3 plums 4 carrots
nl
過濾器會從 stdin
或指定文件讀取行。輸出則會寫入 stdout
并重定向到文件,或傳到另一個進程中。nl
的行為是由不同命令行選項控制的。
在默認情況下,nl
會計算行數,與 cat -n
的功能類似。
nl 默認用法示例:
$nl grocery.list 1 apples 2 bananas 3 plums 4 carrots
使用 -b 標志指定要進行編號的行。此標志將參數作為 “類型”。該類型告訴 nl
需要給哪些行編號,使用 ‘a’ 給所有行編號,‘t’ 告訴 nl
不對空行和只有空格的行進行編號,‘n’ 指定不編號行。在示例中顯示針對模式的類型 ‘p’。nl
給正則表達式模式指定的行編號,在本用例中,是以字母 ‘a’ 或 ‘b’ 開始的行。
使用 nl 對符合正則表達式的行進行編號的示例
$ nl -b p^[ba]grocery.list 1 apples 2 bananas plums carrots
在默認情況下,nl
行號和文本之間使用制表符進行分隔。使用 -s 指定其他分隔符,例如 ‘=’ 號。
使用 nl 指定其他分隔符的示例
$nl -s= grocery.list 1=apples 2=bananas 3=plums 4=carrots
wc
(wordcount) 命令計算指定文件或來自 stdin
的行數、單詞數(由空格分隔)和字符數。
wc 用法示例
$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
命令在指定文件或 stdin
中搜索與給定表達式相匹配的模式。grep
的輸出由多個選項標志控制。
為了演示,這里新創建了一個文件,與 grocery.list 配合使用。
$cat grocery.list2 Apple Sauce wild rice black beans kidney beans dry apples
grep 基本用法示例
$ grep apple grocery.list grocery.list2 grocery.list:apples grocery.list2:dry apples
grep
擁有相當可觀的選項標志。下面的示例將演示其中幾個選項的用法。
要顯示文件名(處理多個文件的情況下)以及發現模式匹配的行數,在本用例中使用的模式是計算每個文件中出現單詞 ‘apple’ 的行數。
grep 示例:計算文件中匹配數
$ grep -c apple grocery.list grocery.list2 grocery.list:1 grocery.list2:1
在搜索多個文件時,可以使用 -h 選項取消在輸出中顯示文件名。
grep 示例:取消在輸出中顯示文件名
$ grep -h apple grocery.list grocery.list2 apples dry apples
在很多情況下,需要進行不區分大小寫的搜索。grep
命令的 -i 選項可以在搜索時忽略大小寫。
grep 示例:不區分大小寫
$ grep -i apple grocery.list grocery.list2 grocery.list:apples grocery.list2:Apple Sauce grocery.list2:dry apples
有些時候,只需要輸出文件名,不需要輸出模式匹配的行。grep
提供 -l 選項,用于只輸出包含匹配模式行的文件名。
grep 示例:只輸出文件名
$ grep -l carrot grocery.list grocery.list2 grocery.list
行號可以顯示在輸出中。使用 -n 選項來包含行號。
grep 示例:包含行號
$ grep -n carrot grocery.list grocery.list2 grocery.list:4:carrots
有時期望輸出與模式不匹配的行。這時就要使用 -v 選項。
grep 示例:輸出不匹配行
$ grep -v beans grocery.list2 Apple Sauce wild rice dry apples
有時,需要的模式是一個單詞,兩邊被空格或其他字符(例如連字符或括號)包圍。grep
的多數版本都提供了 -w 選項,能方便地編寫此類模式的搜索。
grep 示例:單詞匹配
$ grep -w apples grocery.list grocery.list2 grocery.list:apples grocery.list2:dry apples
在 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。
合并和拆分標準流的示例
$ make –f build_example.mk 2>&1 | tee build.log
另一個重定向示例,使用 cat 命令和一些流重定向制作文本文件副本。
使用重定向制作備份文件的示例
$ cat < grocery.list > grocery.list.bak
前面使用 nl
命令為在 stdout
中顯示的文件加行號。管道可用于將 stdout
流(來自 cat grocery.list)發送到另外一個進程,在本用例中,是 nl
命令。
簡單管道傳送到 nl 的示例
$ cat grocery.list | nl 1 apples 2 bananas 3 plums 4 carrots
先前顯示的另一個示例是針對模式執行不區分大小寫的搜索。這也可以使用重定向來實現(在這本用例中為來自 stdin
或使用管道,與上述簡單管道示例相似)。
grep 示例:通過 stdin 重定向和管道
$ 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
$ cat << EOF > oranges > mangos > pinapples > EOF oranges mangos pinapples
可將此輸出重定向到文件,在本示例中,分隔符 ‘EOF’ 改為 ‘!’。然后使用 tr
命令(稍后說明)將 here-doc 中的字母全部變成大寫。
示例:將基本 here-doc 重定向到文件
cat << ! > grocery.list3 oranges mangos pinapples ! $ cat grocery.list3 oranges mangos pinapples $tr [:lower:] [:upper:] << ! > onions > ! ONIONS
head
和 tail
命令用來查看文件的頂部 (head) 或底部 (tail)。要顯示文件頂部兩行和底部兩行,請分別使用這兩個命令加 -n 選項標志。相同地,-c 選項顯示文件中的前幾個或最后幾個字符。
示例:head 和 tail 命令的基本用法
$ 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
命令用來轉換來自 stdin
的字符,在 stdout
中顯示。tr
一般接受兩個字符集合,用第二個集合中的字符替換第一個集合中的字符。有許多預定義的字符類(集合)可供 tr
使用,還有其他命令可用。
這些預定義的字符類是:
- alnum:字母數字字符
- alpha:字母字符
- blank:空白字符
- cntrl:控制字符
- digit:數字字符
- graph:圖形字符
- lower:小寫字母字符
- print:可打印字符
- punct:標點字符
- space:空間字符
- upper:大寫字符
- xdigit:16 進制字符
tr
命令夠將字符串中的小寫字符轉換成大寫。
tr 示例:將字符串轉換成大寫
$ echo "Who is the standard text editor?" |tr [:lower:] [:upper:] WHO IS THE STANDARD TEXT EDITOR?
tr
可以用來從字符串中刪除指定字符。
tr 示例:從字符串中刪除指定字符
$ echo 'ed, of course!' |tr -d aeiou d, f crs!
使用 tr
將字符串中任何指定字符轉換成空格。在序列中遇到多個指定字符時,它們會轉換成一個空格。
-s 選項標志的行為在不同系統中表現不同。
tr 示例:將字符轉變成空格
$ echo 'The ed utility is the standard text editor.' |tr -s astu ' ' The ed ili y i he nd rd ex edi or.
-s 選項標志可以用來取消字符串中多余的空格。
$ 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 示例:消除回車符
$ tr -d '\r' < dosfile.txt > unixfile.txt
使用 colrm
,可以從流中剪切出文本列。在第一個示例中,使用 colrm
命令從管道的每行文本中剪切出第 4 列到行尾。然后,將同一個文件發送至 colrm
,以刪除第 4 列到第 5 列。
使用 colrm 刪除列的示例
$ cat grocery.list |colrm 4 app ban plu car $ cat grocery.list |colrm 4 5 apps banas plu carts
expand
命令將制表符變成空格,而 unexpand
將空格變成制表符。這兩個命令都接受 stdin
輸入以及命令行指定文件的輸入。使用 -t 選項可以設置一個或多個制表符停止位。
expand 和 unexpand 示例:
$ 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
為了演示這些命令,要新建兩個文件。
新建演示文件:
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 命令的示例
$ diff dummy_file1.dat dummy_file2.dat 1,2c1,2 < 011 IBM 174.99 < 012 INTC 22.69 --- > 011 IBM 174.99 > 012 INTC 22.78 4c4 < 014 VMW 102.92 --- > 014 vmw 102.92 $ diff -w dummy_file1.dat dummy_file2.dat 2c2 < 012 INTC 22.69 --- > 012 INTC 22.78 4c4 < 014 VMW 102.92 --- > 014 vmw 102.92 $ diff -i dummy_file1.dat dummy_file2.dat 1,2c1,2 < 011 IBM 174.99 < 012 INTC 22.69 --- > 011 IBM 174.99 > 012 INTC 22.78
comm
命令會對兩個文件進行比較,但比較的方式與 diff 差別很大。comm
產生三列輸出,僅出現在第 1 個文件(第 1 列)的行,僅出現在第 2 個文件(第 2 列)的行,兩個文件中都有的常見行(第 3 列)。可使用選項標志來取消輸出列。此命令可能在取消第 1 列和第 2 列時最有用,只顯示兩個文件中常見的行,如下所示。
comm 命令示例
$ 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 命令示例
$ cmp dummy_file1.dat dummy_file2.dat dummy_file1.dat dummy_file2.dat differ: char 5, line 1
使用 fold
命令可以將行拆分為指定的寬度。這個命令最初是用來對無法支持換行的定寬輸出設備進行文本格式化。-w 選項標志允許使用指定行寬,而不是只使用默認的 80 列。
使用 fold 示例
$ fold -w8 dummy_file1.dat 011 IBM 174.99 012 INTC 22.69 013 SAP 59.37 014 VMW 102.92
paste
命令用來合并文件,將每個文件的記錄逐一合并。利用重定向,可以通過將一個文件中的每個記錄與另一個文件的記錄合并,來新建文件。
新建演示文件:
cat << EOF > dummy1.txt IBM INTC SAP VMW EOF cat << EOF > dummy2.txt 174.99 22.69 59.37 102.92 EOF
paste 示例:來自多文件的行
$ 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:來自多文件的行
$ 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 示例:使用分隔符
$ 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
在 Shell 上進行算術計算的簡易方法是使用 bc
(“basic calculator” 或 “bench calculator”)。有些 shell 自帶了算術計算功能,有些則依靠 expr
對表達式進行運算。使用 bc
,計算可以在不同的 Shell 和 UNIX 系統間移植,只要注意不同廠商的擴展即可。
bc 示例:簡單計算
$ 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 示例:更多計算
$ 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%
bc
的手冊頁面中有詳細說明,并有相關示例。
split
命令的一大作途就是將大型數據文件分解成小的文件以方便處理。在本示例中,BigFile.dat 經 wc
命令統計有 165782 行。-l 選項標志規定了 split
為每個輸出文件生成的最大行數。split
支持為輸出文件名指定前綴,例如下面的示例指定 BigFile_ 為前綴。其他選項支持后綴控制,在 BSD 系統上的 -p 選項標志支持按正則表達式進行拆分,就像 csplit
(上下文拆分)命令一樣。更多信息請參閱手冊頁面。
split 示例:
$ 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
命令用來 "裁剪" 文件中以列為基礎小節或從 stdin
傳送而來的數據。它可按字節 (-b)、字符 (-c) 和列表指定的域 (-f) 進行修剪。使用逗號分隔列表和連字符指定域列表或字節/字符位置。如果只需要輸出一個位置或域,則直接指定位置或域即可。可以使用連字符指定一系列域,例 如 1-3 輸出 1-3 域(或位置),-2 從行開始前兩個域(或字節/字符)開始輸出,3- 則讓 cut
從域(或位置)3 開始輸出到行尾。多個域之間以逗號分隔。其他有用的標志有:-d 指定域分隔符,-s 取消沒有分隔符的行。
cut 示例
$ 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
命令通常用來惟一地列出輸入源(通常是文件或 stdin
)中的行。要正確操作,重復的行必須連續放置于輸入中。uniq
命令的輸入通常會進行排序,因此重復的行會進行合并。與 uniq
命令搭配使用的兩個常用標志是:-c 輸出每行出現的次數,-d 用來顯示重復行的一個實例。
uniq 示例
$ 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
要按指定順序對 stdin
或文件的內容排序,例如按字母順序或數字順序,則可以使用 sort
命令。在默認情況下,sort
的輸出寫在 stdout
中。LC_ALL、LC_COLLATE 和 LANG 等環境變量可以影響 sort
及其他命令的輸出。請注意,示例文件顯示了 2 個分離的重復記錄,一個重復是 IBM,另一個重復是空行。
sort 示例:默認行為
$ 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 示例:惟一排序
$ sort -u dummy_sort1.dat 011 IBM, 174.99 012 sap, 59.37 013 INTC, 22.69 014 VMW, 102.92
有時,希望將輸入倒序輸出。在默認情況下,sort
按從小到大(數字)和字符數據的字母順序排序。使用 -r 選項標志可以將默認排序倒過來。
sort 示例:倒序排序
$ sort -ru dummy_sort1.dat 014 VMW, 102.92 013 INTC, 22.69 012 sap, 59.37 011 IBM, 174.99
不同情況下,可能要求根據某種域或 “鍵” 對文件進行排序。幸運的是,sort
的 -k 選項標志支持按位置指定排序鍵。域之間默認以空格分隔。
sort 示例:按鍵排序
$ 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 示例:不區分大小寫排序
$ 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 示例:按數字排序
$ 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 示例:使用非默認分隔符對域排序
$ sort -k2 -t"," -un dummy_sort1.dat 013 INTC, 22.69 012 sap, 59.37 014 VMW, 102.92 011 IBM, 174.99
凡是熟悉數據庫查詢編寫的人,都認得 join
命令這個實用工具。與多數 UNIX 命令一樣,這個命令的輸出也顯示在 stdout
中。要將文件連接 (“join”) 在一起,請逐行比較來自兩個文件中指定的域。如果沒有指定域,join
則會從每一行的開始進行域區匹配。默認域分隔符是空格(有些系統使用一個空格,有的則使用相鄰多個空格)。找到域匹配后,會根據域匹配的兩行輸出一行結果。要得到合理的結果,每個文件都應該按照匹配的域進行排序。各個系統實現 join
的方式略有不同。
本示例使用 -t 指定域分隔符,并演示了在逗號分隔的第一個域(默認)上對兩個文件進行連接。數據庫操作人員將其看作是內部連接,只顯示匹配的行。
join 示例:使用非默認域分隔符
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 示例:指定域
$ 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 示例:左外連接
$ 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 示例:全外連接
$ 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
流編輯器 (stream editor) 是個有用的文本解析和操作實用工具,可以方便地進行文件或數據流的轉換。它一次一行地讀取文本,在文本行中應用指定的命令。默認輸出到 stdout
。sed
使用的命令可以執行多種操作,如刪除緩沖區的文本、將文本附加或插入到緩沖區、寫入到一個文件中,以及根據正則表達式轉換文本等。
sed
替換的基本示例顯示使用 -e 選項標志來指定表達式或編輯文本。在一個 sed
執行中可以指定多個表達式或編輯文本。請注意 sed
文本編輯的組件。文本編輯開始的 “s” 代表這是個替換命令。使用 “/” 作為分隔符,先指明要替換的 “IBM”。接下來,替換模式出現在兩個 “/” 分隔符之間。最后,“g” 指明在當前文本緩沖區中進行全局修改。本示例的第三個演示解釋了三個文本編輯的組合:用斜杠代替反斜杠,用下劃線代替空格,以及刪除冒號(請注意其中反斜 杠 “\” 字符的轉義表示方式)。
sed 示例:基本替換/多個編輯文本
$ 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 示例:篩選
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 示例:更多模式
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 示例:地址范圍
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 示例:分組模式
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
程序是個方便的文本操作工具,執行的工作包括文本的解析、篩選和簡易格式化。它的輸入來自 stdin
或文件,默認在 stdout
上顯示輸出。awk
有不同的發行版,并使用不同的名稱,例如 nawk
和 gawk
。不同版本和供應商提供的 awk
其行為也各不相同。awk
與本文介紹的其他命令不同,因為它是一個編程語言。該語言內置算術、字符串操作、流程控制以及文本格式化的函數。程序員也可以自己定義函數,創建用戶自定義函數庫或獨立腳本。因為 awk
包含的特性如此之多,所以這里只演示少量的示例。欲了解更多信息,請參閱 參考資料 小節或手冊頁面。
在本示例中,首先將 awk
作為篩選器,只輸出 Linux 系統上的完整文件系統。在默認情況下,awk
使用空白來識別各個列。該示例檢查了第 5 列,因為此列顯示的是磁盤已用空間百分比。如果磁盤利用率是 100%,則第一個示例會在 stdout
中輸出記錄。接下來的語句對第一個示例做了擴展,對消息進行格式化,可能要在電子郵件中發送,或者放在消息中寫入日志文件。接下來的示例顯示如何創建一個使用數值比較的匹配。
awk 示例:篩選器
$ 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 示例:域分隔符/字符串函數
$ 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 示例:程序/算術
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 可以是強大的編程語言。與 awk 一樣,Shell 提供了豐富的選項來執行字符串操作、算術功能、數組、流程控制,以及文件操作。下面的幾個示例將顯示如何從某一方提取字符串的各個部分。此操作并不改變字 符串的值,只是提取出需要的結果,通常用來對變量賦值。使用百分號 “%” 截斷模式的右側,并用 “#” 號截斷模式的左側。
Shell 腳本的示例:字符串提取
$ 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 腳本示例:修改文件擴展名
$ 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 提供了更多命令和方式來實現這些相同的任務,“有不止一種的方式來完成任務”。
文章出處:IBM developerWorks