歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> UNIX系統上的文本操作簡介

UNIX系統上的文本操作簡介

日期:2017/2/27 16:06:01   编辑:Linux教程

簡介


UNIX 的基本哲學之一就是創建只做一件事並將這一件事做好的程序(或進程)。這一哲學要求認真考慮接口以及結合這些更小(有望更簡單)流程的方法,以產生有用的結果。文本數據通常是在這些接口之間流動。多年以來,越來越高級的文本處理工具和語言已經開發出來。從語言上來講,早期專門處理文本的語言有 perl,後來又出現了 python 和 ruby。雖然這些語言以及其他語言都是非常強大的文本處理器,但這些工具並不是一直可行的,在生產環境下尤其如此。本文將介紹一些基本的 UNIX 文本處理命令,這些命令既可單獨使用也可結合使用,可用來解決需要更新的語言才能解決的問題。對許多人來說,與長篇大論的解釋相比,實例能夠提供更多的信息。請注意,由於有多種可用的 UNIX 和 UNIX 類系統,因此不同實現之間的命令標志、程序行為和輸出結果會略有不同。

使用 cat


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

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

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

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

流、管道、重定向、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。

合並和拆分標准流的示例

$ 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 命令用來查看文件的頂部 (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


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,可以從流中剪切出文本列。在第一個示例中,使用 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


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

使用 comm、cmp 和 diff


為了演示這些命令,要新建兩個文件。

新建演示文件:
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


使用 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


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

使用 bc


在 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


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


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


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

使用 sort


要按指定順序對 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


凡是熟悉數據庫查詢編寫的人,都認得 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


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


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 的字符串操作


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 提供了更多命令和方式來實現這些相同的任務,“有不止一種的方式來完成任務”。
Copyright © Linux教程網 All Rights Reserved