Linux下文本的高效處理

1 引言

所謂的文本處理是指對文本進行查找、替換、刪除、排序等操作, linux在文本處理方面提供了大量優秀的工具, 使得在linux下進行文本處理極其的方便. 

我們平常的工作中, 經常會用到文本處理, 比如日誌分析, 比如文本抽取, 等等, 所以掌握好文本處理, 將會對我們的工作起到極大的作用. 

下面我就來逐個介紹下這些強大的工具, 對於我覺得大家可能比較熟知的工具及用法, 我會略過, 或者粗講下.

2 關於輸入

Linux哲學中, 爲了更好的組合各種命令達到更加強大的功能, 大多數文本處理命令的輸入既可以是文件, 也可以是標準輸入, 如果沒有指定輸入文件, 則默認從標準輸入讀數據. 輸出都是標準輸出, 方面傳給管道線的下一個命令, 想要輸出到文件的話, 重定向下即可. 下面介紹的這些命令, 如無特殊說明, 則都可以從文件或者標準輸入讀入數據.

3 文本輸出

3.1 整個輸出

  • echo 
    輸入爲命令行參數
    

    非常常用的命令, 主要用作輸出字符串. 如果只是爲了向管道線的下一個命令傳輸入的話, 可以使用Here String:

    echo xxx | md5sum
    md5sum <<< xxx
    

    後者速度上應該會快一點, 不需要經過管道.

    • -n 

      不輸出換行符(默認輸出換行符)
    • -e 

      解釋轉義符, 常用的轉義符 \t\n; 這個命令還有個最常用的用途就是輸出ANSI顏色:

      echo -e '\033[1;31mHello, \033[0m\033[1;33mworld!\033[0m'
      

      Hello, world!

      這個工具可以更方便的輸出ANSI顏色.

    • -E 

      不解釋轉義符

  • printf 
    輸入爲命令行參數
    

    更強大的輸出你想要的文本的命令, 類似C裏面的printf

    printf '\033[1;31m%s, \033[0m\033[1;33m%d\033[0m and \u4e2d\u6587!\n' "Hello" 34
    

    Hello, 34 and 中文!

    不過此命令較echo來說, 使用率會低很多, 大多數情況下echo就能搞定了.

  • yes [STRING] 
    輸入爲命令行參數
    

    不停的輸出字符串STRING, 默認是y. 這個命令用處比較少, 但會有用, 比如測試tail命令.

  • cat (concatenate) 

    此命令最大的用途應該就是顯示特殊字符了, 如果你告訴我你經常用它給管道線的下一個命令傳輸入的話, 那麼就太浪費了:

    cat file | grep xxx
    grep xxx file
    

    前者相比後者多啓動了一個進程, 還經過了管道. file很大的話, 性能差距很容易就看出來了.

    • -E 

      在行尾顯示$, 有時候行尾是空格或者TAB, 看不清行尾是哪裏, 加了這個選項就知道了
    • -T 

      輸出的時候用^I代替TAB符號, 這樣很容易知道那一坨空白到底誰是TAB了
    • -v 

      用^和M-表示法輸出不可打印字符

      taoshanwen@taoshanwen-laptop ~$ echo -e '\r' | cat -v
      ^M
      
    • -e 

      相當於-vE
    • -t 

      相當於-vT
    • -A 

      相當於-vET, 一般這個選項用的最多

    • -n 

      顯示行號, 應該有同學感興趣
    • -b 

      只對非空行顯示行號, 有人感興趣嗎?

    • -s 

      對連續的空行只輸出一個空行, 見過好多同學有這樣的需求

  • tac 

    看名字知道了, 它和cat是反的, 倒着輸出, 先輸出最後一行, 接着是倒數第二行, 最後輸出的是第一行

    • -s, –separator=STRING 

      設定分隔符, 代替\n分割文本

  • rev (reverse) 

    這個命令和cat也是反的, 不過它不像tac那樣, 它輸出行的順序和cat也是一樣的, 不過輸出每行的時候, 先輸出最後一個字符, 接着是倒數第二個字符, 最後纔是第一個字符, 下面這個命令可以把輸入全部倒過來:

    tac | rev
    
  • nl (number lines) 

    更強大的行號顯示工具, 可以控制行號的格式, 寬度. 沒有特殊的需求, 用cat -n就夠了.

3.2 部分輸出

  • head 

    只顯示文本的開頭幾行, 比如head -2只顯示前面2行
  • tail 

    這個命令相對head來說, 最常用的用途就是不停的打印文件的最新內容了(tail -f)

    • -n, -K, –lines=K 

      顯示尾部K行, -n +K顯示第K行到文本尾部的所有內容
    • -f, –follow[={name|descriptor}] 

      這個選項表示如果文件尾部有新數據追加進來, 也會顯示出來. 這個選項可以根據文件名(name)和文件描述符(descriptor)來監視文件是否有更新. 默認是descriptor.
    • –retry 

      當文件不可訪問時, 進行重試, 這個選項和–follow=name組合起來比較有用
    • -F 

      相當於–follow=name –retry

4 文本搜索

4.1 grep (Global Regular Expression Print)

非常常用的命令, 打印文本中匹配模式的行, 下面的選項最好都能掌握.

grep [OPTIONS] PATTERN [FILE...]
grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]
  • -E, –extended-regexp 

    使用擴展正則表達式(ERE), 默認的是基礎正則表達式(BRE), BRE中元字符?, +, {, |, (, )失去特殊意義(你是否遇到grep "a|b"是否不能打印出含有a或者b的行?), 想要表達特殊意義的話, 需要用轉義字符進行轉義(\), \?, \+等
  • -F, –fixed-strings 

    把pattern當作一個固定的字符串, 不進行正則解析. 當你要搜索非正則的固定字符串時(還可能含有正則元字符), 這個選項會非常有用, 而且由於不需要解析正則, 速度會快些.
  • -P, –perl-regexp 

    把pattern解析爲perl的正則(PCRE), 由於perl的正則強大而簡潔, 所以可以多嘗試着使用這個選項.

  • -i, –ignore-case 

    搜索pattern的時候, 忽略大小寫. 如果沒有這個選項, 可以使用PCRE, 比如:

    grep -P "(?i)AB"
    
  • -v, –invert-match 

    顯示不能匹配pattern的行
  • -e pattern 

    如果你想要指定多個搜索pattern, 或者你想要搜索的pattern由減號(-)開頭(如果直接grep pattern的話, 會被解析爲grep的選項), 就可以用這個選項了.

  • -f FILE, –file=FILE 

    從文件中獲取pattern, 每行一個pattern

  • -x, –line-regexp 

    pattern必須要匹配整行, 這個選項等價於 "^pattern$"
  • -w, –word-regexp 

    pattern必須要匹配整個單詞, 這個選項等價於 "\bpattern\b"

  • -c, –count 

    不打印匹配的行, 只打印匹配的行數, 等價於grep pattern | wc -l
  • –color[=WHEN], –colour[=WHEN] 

    用顏色高亮出匹配的:

    (require 'coding-settings)
       ("C-x U"   revert-buffer-with-coding-system-no-confirm-sb)))
       ("C-x M-C" set-buffer-file-coding-system)))
      (set-buffer-file-coding-system 'unix))
      (set-buffer-file-coding-system 'dos))
    
  • -m NUM, –max-count=NUM 

    當發現NUM個匹配行後, 停止掃描剩下的文本.
  • -q, –quiet, –silent 

    不打印任何信息, 發現匹配即退出, 並返回0, 否則返回1. 我們經常只是想查看整個文本里面是否有匹配, 這時候這個選項就非常有用了, 速度會快很多.
  • -o, –only-matching 

    只顯示匹配pattern的字符串, 匹配行的其餘部分不顯示
  • -n, –line-number 

    在匹配行前面打印行號

    有時候想看看匹配行周圍都是啥, 下面這幾個選項就非常有用了:

  • -A NUM, –after-context=NUM 

    打印匹配行的後面NUM行
  • -B NUM, –before-context=NUM 

    打印匹配行的前面NUM行
  • -C NUM, -NUM, –context=NUM 

    打印匹配行的周圍NUM行

  • -a, –text 

    有時候文件中含有一些非可打印字符, grep可能會把它識別成二進制文件, 這時候grep只會打印出是否匹配pattern的信息, 並不會打印匹配的每行, 這個選項會強制grep把該文件當文本文件處理

  • -R, -r, –recursive 

    遞歸處理文件夾下的所有文件

  • -l, –files-with-matches 

    不打印匹配的行, 只打印匹配的文件
  • -L, –files-without-match 

    和-l相反, 不打印匹配的行, 只打印不匹配的文件
  • -h, –no-filename 

    搜索多個文件時, 會在每行前面輸出文件名, 如果你不喜歡, 使用此選項吧.

4.2 fgrep

grep -F

4.3 egrep

grep -E

4.4 rgrep

grep -r

4.5 agrep (approximate grep)

grep的模糊匹配版本

4.6 zgrep

對壓縮文件進行grep, 接受的選項和grep完全一樣

4.7 sgrep (structured grep)

對結構化的文本, 如SGML、XML、HTML進行搜索、抽取, 功能非常強大

4.8 nrgrep (Nondeterministic Reverse grep)

類似agrep

5 文本摘要

5.1 wc (word count)

最主要的用途就是統計行數

  • -l, –lines 

    最常用的選項, 統計行數
  • -L, –max-line-length 

    輸出文本最長行的長度
  • -w, –words 

    輸出單詞數
  • -m, –chars 

    輸出字符數
  • -c, –bytes 

    輸出字節數

5.2 md5sum

打印文本的md5, 主要用作文件校驗, 防止文件傳輸時發生錯誤或者被篡改. -c選項檢查md5是否正確

6 排序去重

6.1 sort

非常常用的命令, 啥序都能排

  • -r, –reverse 

    逆序排序, 默認是按從小到大排, -r後就從大到小了

  • -c, –check, –check=diagnose-first 

    檢查輸入文件是否是有序的, 不是的話, 會打印哪行開始不是有序的
  • -C, –check=quiet, –check=silent 

    類似-c, 但是不打印錯誤信息, 只返回錯誤碼1

  • -k, –key=POS1[,POS2] 

    這個應該是sort最nb的地方了, 可以精確控制要排序的對象. POS具備這樣的形式:

    F[.C][OPTS]
    

    其中, F是字段號, C是字符號, OPTS是排序選項, 可以每個字段排序的規則不一樣. F, C都是從1開始

    sort -t ' ' -k1,1d -k2.2,2n <<-EOF
    bb 113
    aa 224
    cc 323
    dd 444
    cc 513
    EOF
    
    aa 224
    bb 113
    cc 513
    cc 323
    dd 444
    
  • -u, –unique 

    對輸出結果進行去重, 只輸出重複的記錄中的第一條記錄
  • -m, –merge 

    對有序的輸入文件進行歸併, 這個選項使得你能夠在多核機器上優化大數據集的排序
  • -s, –stable 

    使得sort成爲穩定排序
  • -T, –temporary-directory=DIR 

    設定指定的臨時文件夾, 存放中間數據. 當你排序非常大的文件時, 而且/tmp所在的分區空間不夠時, 就會用到該選項了

  • -n, –numeric-sort 

    把輸入當整數來排序, 可以有負數, 但是不能含有加號(+)的正數, 這種輸入用-g搞定吧
  • -g, –general-numeric-sort 

    把輸入當作數值來排序, 可以有浮點數. 如果輸入是整數的話, 就用-n搞定吧, 人家性能高些.
  • -h, –human-numeric-sort 

    可以排序2K, 1G等帶單位的數字, 很爽啊, 想排序某文件夾下所有文件和文件的大小嗎:

    du -sh * | sort -h
    
  • -M, –month-sort 

    按月份進行排序, `JAN' < `FEB' < … < `DEC'
  • -d, –dictionary-order 

    按字典序排序, 忽略字母、數字、空白字符外的所有字符
  • -V, –version-sort 

    你開發的軟件有很多版了沒? 排下吧, 根據版本號

  • -t, –field-separator=SEP 

    設置字段分隔符, 默認爲空白字符. 可惜的是, 這個字段分隔符只能爲單個字符
  • -b, –ignore-leading-blanks 

    忽略前導空白字符
  • -f, –ignore-case 

    忽略大小寫
  • -i, –ignore-nonprinting 

    忽略不可打印字符

  • -R, –random-sort 

    隨機排序, 我想你會用到它的, 反正我用過幾次. 不過排序結果不完全隨機, 因爲sort會先對每行進行hash, 然後對hash值進行排序, 所以相同的行一定會排到一塊. 不過也許, 這正是你想要的. 如果你想更亂或者更加強大的功能的話, 看這裏

  • 陷阱 

    你是否經常sort一箇中文文件卻得不到正確結果? 那就對了, 你肯定沒設置好語言環境(locale), 試試LC_ALL=C sort吧. sort會根據本地語言環境對輸入文本進行排序. LC_ALL=C表示會根據字節值來排序. 或許你說我怎麼見到的都是LANG=C sort啊, 來, 我們看看bash info上關於LANG和LC_ALL的解釋:

    LANG   Used to determine the locale category for any category not specifically selected with a variable starting with LC_.
    LC_ALL This variable overrides the value of LANG and any other LC_ variable specifying a locale category.
    LC_COLLATE
           This variable determines the collation order used when sorting the results of pathname expansion, and  determines  the
           behavior  of  range  expressions,  equivalence  classes, and collating sequences within pathname expansion and pattern
           matching.
    
    • LANG 

      如果你沒有用LC_來設定某個分類的locale, 將會使用LANG來決定這個分類的locale
    • LC_ALL 

      該變量會覆蓋LANG和LC_
    • LC_COLLATE
      該變量設置排序時的locale

    所以, sort時, 設置LC_ALL是最保險的做法.

6.2 tsort (topological sort)

拓樸排序, 該命令可能會用的比較少

tsort <<EOF
a b c
d
e f
b c d e
EOF

輸出:

a
b
c
d
e
f

6.3 uniq

也是非常常用的一個命令. 這個命令主要用來對有序序列進行去重, 所以它常和sort聯合起來使用, 但是sort -u本身就有去重的功能, 所以當你僅僅只是爲了去重時, sort -u就可以幫你搞定了(當輸入文本巨大時, 可以用hash來去重提高性能, 比如awk的關聯數組), 所以呢, 當年需要對重複的數據進行統計時, 會用到uniq. 當然其實uniq相比sort -u而言, 對重複數據有更加強大的處理

  • -c, –count 

    在每行文本前面輸出重複次數
  • -d, –repeated 

    只顯示重複的行, 重複的行只顯示一行
  • -D, –all-repeated[=delimit-method] 

    顯示所有重複的行, 注意該選項與選項-d的區別
  • -u, –unique 

    只打印不重複的行
  • -i, –ignore-case 

    比較的時候不區分大小寫
  • -f, –skip-fields=N 

    不比較前面N個字段, 字段分隔符爲空白字符
  • -s, –skip-chars=N 

    不比較前面N個字符
  • -w, –check-chars=N 

    每行最多比較前面N個字符

  • 實例演示 

    大家看了uniq上面幾個選項後, 是不是有uniq沒有太大用處的感覺? 這都是錯覺, 下面我給大家演示下uniq在集合運算(統計中有大量的應用)方面巧妙的應用.

    • 並集 
      sort A B | uniq
      
    • 交集 

      sort A B | uniq -d
    • 差集(A-B) 

      sort A B B | uniq -u

  • 缺陷 
    • 不能控制字段分隔符
    • 不能像sort -k那樣精確的控制要比較的對象

6.4 comm

逐行比較兩個有序文件, 分三列輸出文件1獨有的行、文件2獨有的行、文件12共有的行,

$ cat ab
ax
by
cz

$ cat ac
ax
bd
cz

$ comm ab ac
        ax
    bd
by
        cz
  • -1 

    不輸出第一列(文件1獨有的行)
  • -2 

    不輸出第二列(文件2獨有的行)
  • -3 

    不輸出第三列文件3獨有的行
  • –check-order 

    檢查輸入文件是否有序
  • –nocheck-order 

    不檢查輸入文件是否有序
  • –output-delimiter=STR 

    設定輸出分隔符, 默認爲TAB
  • 實例演示 
    • 交集 

      comm -12 <(sort A) <(sort B)
    • 差集(A-B) 

      comm -23 <(sort A) <(sort B)

6.5 shuf (shuffle)

如果sort -R產生的結果還不夠亂的話, 我想這個命令應該就是你需要的了. 該命令產生完全亂序的結果, 而且速度應該比sort -R快(shuf不用排序), 還有功能更強大

  • -e, –echo 

    對命令行參數亂序
  • -i, –input-range=LO-HI 

    對LO到HI之間的數字進行亂序, 比如shuf -i 12-100

7 操作字段

7.1 cut

挺常用的一個命令, 能非常方便的取某個字段

  • -f, –fields=LIST 

    選擇要輸出的字段
  • -c, –characters=LIST 

    選擇要輸出的字符
  • -b, –bytes=LIST 

    選擇要輸出的字節
  • -d, –delimiter=DELIM 

    設定字段分隔符, 默認是TAB. 可惜的是, 該分隔符也只能是單個字符.
  • –complement 

    取設定的選擇LIST的補集
  • -s, –only-delimited 

    忽略不包含分隔符的行, 默認操作是輸出整行
  • –output-delimiter=STRING 

    設定輸出分隔符
  • LIST 

    -f, -c, -b選項使用的列表, 可以有下面幾種形式:



    • 第N個字段/字節/字符
    • N- 

      從第N個到最後一個
    • M-N 

      從第M個到第N個
    • -M 

      從第一個到第N個

    列表可以有多個, 之間以逗號分割, 比如:

    cut -f1-3,4-7
    

7.2 paste

這個命令很有意思, 把兩個文件按行粘貼到一塊, 曾經我想自己寫個程序搞定這個需求, 後來發現linux下竟然已經有這玩意了(linux總能給你帶來驚喜)

$ cat num2
1
2
$ cat let3
a
b
c
$ paste num2 let3
1       a
2       b
        c
  • -d, –delimiters=LIST 

    paste兩個文件的時候, 默認是用TAB分割, 這個選項設定分隔符, 同爲可惜的是, 只能爲單個字符(主要是paste可以粘貼多個文件, 這個選項的第二個字符用來分割第二個和第三個文件)

  • -s, –serial 

    默認paste是豎着粘貼的, 加了這個選項後, 就橫着粘貼了:

    $ paste -s num2 let3
    1       2
    a       b       c
    

7.3 join

這是一個稍微高級點的命令, 它把輸入文件當成一個key/value對, 然後會把同一個key的所有value粘貼到一塊, 來個例子:

$ cat file1
a 1
b 2
c 3
$ cat file2
a 4
c 6
$ join file1 file2
a 1 4
c 3 6

join默認把第一額字段當作key, 字段之間以空格分割, 作爲key的字段必須有序.

  • -i, –ignore-case 

    比較字段時, 忽略大小寫
  • -t CHAR 

    使用CHAR作爲字段分隔符, 又是隻能爲單個字符(杯具…)

  • -1 FIELD 

    設定第一個文件的key爲第FIELD個字段
  • -2 FIELD 

    設定第二個文件的key爲第FIELD個字段
  • -j FIELD 

    -1 FIELD -2 FIELD

  • -a FILENUM 

    join默認只打印擁有相同key的行, 該選項會打印第FILENUM個文件中沒有匹配上的行
  • -v FILENUM 

    和-a選項有點類似, 該選項只打印第FILENUM個文件中沒有匹配上的行, 不會打印匹配上的行

  • –check-order 

    檢查輸入文件作爲key的字段是否有序
  • –nocheck-order 

    不檢查輸入文件作爲key的字段是否有序

  • -o FIELD-LIST 

    高級的控制輸出對象的選項, FIELD-LIST中的每個元素具有下面這樣的形式:



    • 表示做爲key的字段
    • M.N 

      M爲文件號, 取值爲0或者1, N爲字段號, M.N就是取第M個文件第N個字段

    每個元素之間以逗號或者空格分割

  • -e EMPTY 

    -o選項中, 可能文件M中沒有字段號N, 這時候輸出的時候用EMPTY代替.

8 操作字符

8.1 tr (translate)

主要對文本中的字符進行替換、刪除.

  • 該命令只支持標準輸入, 不支持從文件輸入.
  • tr僅支持單字節字符.
tr [OPTION]... SET1 [SET2]

字符集合可以由一系列的字符構成, 也可以具有以下形式:

  • CHAR1-CHAR2 

    從CHAR1到CHAR2的所有字符
  • [CHAR*] 

    這種形式只能出現在SET2中, 表示拷貝CHAR直到SET2和SET1的長度相等
  • [CHAR*REPEAT] 

    REPEAT個CHAR
  • [:alnum:] 

    所有的字母和數字
  • [:alpha:] 

    所有的字母
  • [=CHAR=] 

    和CHAR屬於同一個字符類中的所有字符

當提供2個字符集合時, 表示把SET1中的字符替換成SET2中的對應的字符, 比如:

tr a A < file # 把文件file中的小寫a都變成大寫A
tr '[:lower:]' '[:upper:]' < file # 把文件file全部大寫
  • -d, –delete 

    刪除出現在集合1中的所有字符. 下面的命令把文件file中所有的行連成一行:

    tr -d "\r\n" < file
    
  • -s, –squeeze-repeats 

    把SET1中連續的字符都替換成1個字符, 當SET2也提供時, 首先執行刪除連續字符操作, 然後才執行替換操作

  • -c, -C, –complement 

    使用SET1的補集

  • -t, –truncate-set1 

    首先把SET1中的字符截斷到和SET2長度相等

  • 陷阱 

    經常見到有同學對會含有中文的文件用上面的方法進行大小寫轉換:

    # 終端編碼爲GB18030編碼
    $ tr '[:upper:]' '[:lower:]' <<< 琄
    琸
    

    爲什麼琄會變成琸呢? 

    上面我們說到, tr是按字節來處理的, 而GB18030編碼第二個字節編碼範圍爲0×40-0×7E和0×80-0×FE, 這樣, 第二個字節就可能出現ASCII碼, 我們來看下上面2個漢字的GB18030編碼值:

    $ od -c  <<< 琄
    0000000 254   K  \n
    0000003
    $ od -t x1  <<< 琄
    0000000 ac 4b 0a
    0000003
    
    $ od -c  <<< 琸
    0000000 254   k  \n
    0000003
    $ od -t x1  <<< 琸
    0000000 ac 6b 0a
    0000003
    

    看來確實如此, 琄的第二個字節是字符大K, 琸的第二個字節是字符小k.

    看來, 如果文本里含有多字節字符, 使用tr的時候得小心咯.

8.2 expand

每個編輯器對TAB的顯示設置不一樣, 有的顯示爲8個字符, 有的顯示爲4個字符, 這樣就造成了在A編輯器下排版很漂亮, 到了B編輯器下變得一團糟, 所以編碼的時候最好使用空白字符代替TAB(Emacs中這樣設置: (setq-default indent-tabs-mode nil), :) ), expand命令也可以幫你把TAB轉換成空格

  • -i, –initial 

    不轉換非空白字符後的TAB
  • -t, –tabs=NUMBER 

    設置一個TAB轉換成幾個空格, 默認是8

8.3 unexpand

Makefile縮進的時候, 必須是TAB, 所以有時候又需要把空格變成TAB, 就靠unexpand了

  • -a, –all 

    轉換所有的空格, 默認情況下只轉換開頭的空格
  • –first-only 

    只轉換開頭的空格
  • -t, –tabs=N 

    幾個空格轉換成一個TAB, 默認是8

8.4 colrm (COLumn ReMove)

colrm [start [stop]]

該命令只支持標準輸入, 不支持從文件輸入

刪除每行從start到stop之間的字符, 如果stop沒有指定的話, 則刪除到末尾. 需要注意的是, TAB被認爲佔8列(不知道爲啥這樣搞)

9 文本切割

9.1 split

切割文本INPUT成文件PREFIXaa, PREFIXab … 默認每個文件1000行, PREFIX爲x

split [OPTION]... [INPUT [PREFIX]]
  • -l, –lines=NUMBER 

    按行切割, 每個輸出文件NUMBER行, 比較常用的切割方式
  • -b, –bytes=SIZE 

    按字節切分
  • -C, –line-bytes=SIZE 

    每個文件最多SIZE個自己, 但是每行都完整的保存到一個輸出文件中, 即不像-b那樣, 可能一個整行被拆分到多個文件中去
  • -d, –numeric-suffixes 

    使用數字做爲後綴名

9.2 csplit (context split)

根據模式切割文件, 簡單瞭解即可

csplit [OPTION]... FILE PATTERN...

10 文本編碼

10.1 iconv

經常會用到, 主要用來轉換編碼

  • –list, -l 

    列出可以識別的編碼
  • -c 

    轉換的時候, 忽視無效的字符, 如果沒有加這個選項, iconv碰到這個無效字符會直接報錯退出, 所以最好加上這個選項

10.2 enca

我們經常需要知道文件的編碼, 這個命令幫你搞定

11 文本格式化

打扮一下你的文本吧.

11.1 column

按列漂亮的輸出:

$ (printf "PERM LINKS OWNER GROUP SIZE DAY HH:MM NAME\n"; ls -l | sed 1d) | column -t
PERM        LINKS  OWNER       GROUP       SIZE   DAY         HH:MM  NAME
drwxr-xr-x  3      taoshanwen  taoshanwen  4096   2012-04-03  22:54  ai
drwxr-xr-x  26     taoshanwen  taoshanwen  4096   2012-04-15  11:59  algorithm
drwxr-xr-x  2      taoshanwen  taoshanwen  4096   2012-04-09  13:35  arch
drwxr-xr-x  5      taoshanwen  taoshanwen  4096   2012-04-03  22:47  c-c++
drwxr-xr-x  6      taoshanwen  taoshanwen  4096   2012-04-14  20:33  CIP
drwxr-xr-x  5      taoshanwen  taoshanwen  4096   2012-04-03  22:47  computer-chess
drwxr-xr-x  2      taoshanwen  taoshanwen  4096   2012-04-15  00:23  computer-go
drwxr-xr-x  3      taoshanwen  taoshanwen  4096   2012-04-10  16:25  database
drwxr-xr-x  3      taoshanwen  taoshanwen  4096   2012-04-15  00:57  distributed
drwxr-xr-x  5      taoshanwen  taoshanwen  4096   2012-04-03  22:47  genetic-prog
drwxr-xr-x  3      taoshanwen  taoshanwen  4096   2012-04-03  22:47  infosec
drwxr-xr-x  2      taoshanwen  taoshanwen  4096   2011-03-19  20:40  iphone
drwxr-xr-x  20     taoshanwen  taoshanwen  4096   2012-04-15  00:38  java
drwxr-xr-x  94     taoshanwen  taoshanwen  16384  2012-04-17  20:01  linux
drwxr-xr-x  7      taoshanwen  taoshanwen  4096   2012-04-10  19:29  math
drwxr-xr-x  2      taoshanwen  taoshanwen  4096   2012-04-17  15:37  mysql
drwxr-xr-x  2      taoshanwen  taoshanwen  4096   2011-10-19  17:04  nosql
drwxr-xr-x  11     taoshanwen  taoshanwen  4096   2012-04-16  12:54  other
drwxr-xr-x  2      taoshanwen  taoshanwen  4096   2012-04-07  14:03  perl
drwxr-xr-x  3      taoshanwen  taoshanwen  4096   2012-04-15  00:18  python
drwxr-xr-x  6      taoshanwen  taoshanwen  4096   2012-04-03  22:50  ruby
drwxr-xr-x  52     taoshanwen  taoshanwen  4096   2012-04-15  00:59  search-engine
drwxr-xr-x  9      taoshanwen  taoshanwen  4096   2012-04-15  00:23  software-engineering
drwxr-xr-x  5      taoshanwen  taoshanwen  4096   2010-10-11  22:56  svnroot
drwxr-xr-x  7      taoshanwen  taoshanwen  4096   2012-04-14  20:33  web
drwxr-xr-x  66     taoshanwen  taoshanwen  12288  2012-04-17  23:47  work

11.2 fold

將一個比較長的文本行輸出進行"折行".

11.3 fmt

將輸入按照指定寬度進行折行, 功能較fold強大些

12 微語言

下面介紹文本處理中兩個最強大的命令sed和awk, 它們已經具有一些程序設計語言的特徵了, 特別是awk, 所以, 我們的腳本中, 放眼望去, 皆是awk阿. 熟練掌握這兩個命令, 你的文本處理功力將會極大的提升阿.

12.1 sed (Stream EDitor)

sed是一個流編輯器, 類似ed(行編輯器, 通過各種命令編輯文件), 它提供了各種替換、刪除的命令, 使得這些編輯操作能自動化起來.

  • 工作流程 

    sed維護2快內存(也可以理解爲2個變量, 或者說是2個寄存器), 分別叫做 pattern space 和 hold space, sed對每行輸入執行下面的循環:

    1. 讀入輸入行, 去掉尾部的換行符, 存入pattern space
    2. 執行sed命令, 每條sed命令都可以有個地址與它關聯, 這個地址就類似於條件語句, 只有這個條件語句通過驗證時, 其對應的命令纔會執行
    3. 執行完所有的sed命令後, 如果沒有指定sed的-n選項, 將會打印pattern space的內容, 然後再輸出換行符. 最後繼續讀入下一行, 進行下一次的循環

    每次循環開始時, pattern space的內容會被清空, hold space則不會

  • 地址格式 

    sed地址可以具有以下的形式:

    • NUMBER 

      指定執行命令的行號, 只有在這行, 對應的命令纔會被執行, 行號從1開始, 另外, 如果沒有指定-i或者-s選項的話, 所有的輸入文件會被當成一個輸入流, 行號就會一直累加的
    • FIRST~STEP 

      在FIRST、FIRST+STEP、FIRST+2*STEP、、FIRST+3*STEP行執行對應的命令


    • 最後一行
    • REGEXP 

      在匹配上正則REGEXP的行執行對應的命令, 如果REGEXP中含有/, 需要用\轉義
    • \%REGEXP%
      在匹配上正則REGEXP的行執行對應的命令, %也可以是其他字符, 如果REGEXP中含有%, 需要用\轉義
    • /REGEXP/I, \%REGEXP%I
      忽略大小寫
    • /REGEXP/M, \%REGEXP%M
      可以匹配多行, M表示multi-line

    如果沒有指定地址的話, 表示所有行對執行命令. 還可以提供2個地址, 指定一個地址範圍, 這2個地址之間以逗號分割, 比如:

    ADDRESS1,ADDRESS2
    

    這樣, 第一次匹配上ADDRESS1的行與第一次匹配上ADDRESS2的行之間的所有行都會執行對應的命令. 

    GNU sed還支持下面幾種地址範圍:

    • ADDR1,+N 

      匹配ADDR1, 以及接下來的N行
    • ADDR1,~N 

      匹配ADDR1, 直到行號爲N倍數的行

    在地址或者地址範圍後加感嘆號(!), 表示取反.

  • 常用命令 

    由於sed默認會打印pattern space, 所以不加任何命令的話, 就和cat一樣, 打印所有的輸出:

    $ cat ab
    ab
    ab
    ac
    ad
    ae
    ac
    ab
    
    $ sed "" ab
    ab
    ab
    ac
    ad
    ae
    ac
    ab
    


    • 刪除pattern space, 立即進入下一輪循環. 

      ls輸出的時候, 第一行有個摘要, 如果不想顯示的話, 這樣:

      ls -l | sed 1d
      


    • 打印pattern space. 

      輸出文件ab第5到第10行:

      sed -n 5,10p ab
      
    • q [EXIT-CODE] 

      立即以返回碼EXIT-CODE(默認爲0)退出sed, 如果沒有加-n選項的話, 當前的模式空間也會打印出來. 

      如果文件很大的話, 下面的方法輸出文件ab第5到第10行會快很多:

      sed -n "5,10p; 10q" ab
      


    • 如果沒有加-n的話, 打印模式空間, 然後直接進入下一輪循環. 

      不打印第18行:

      sed -n "p; 18n"
      
    • { COMMANDS } 

      一組命令, 這組命令共用同一個地址. 

      打印第8行:

      sed -n "8 {p; q}"
      
    • s/REGEXP/REPLACEMENT/FLAGS 

      這個命令估計是大家用的最多的命令了. 前面幾個命令大家不知道的情況下, 可能通過其他命令解決了, 但是這個命令的功能除了awk, 其他的做不了, 而sed比awk更簡潔.
      這個命令主要是對pattern space進行替換, 對匹配REGEXP的部分用REPLACEMENT進行替換, 用來分割的/可以由其他字符組成, 比如s:REGEXP:REPLACEMENT:FLAGS. REPLACEMENT可以由原始的字符組成, 也可以由下面帶有特殊意義的串組成:



      • 匹配REGEXP的部分
      • \d 

        d爲1-9的數字, \d表示REGEXP中第d個括號匹配的部分, 比如REGEXP爲:

        a(..(..))
        

        pattern space爲abcde, 那麼\1爲bcde, \2爲de

      • \L 

        把REPLACEMENT中的字符變成小寫, 直到遇到\U和\E. 比如:

        $ cat ab
        AB
        AB
        ac
        ad
        ae
        ac
        AB
        
        $ sed -r 's/(AB)/\L\1YYY/' ab
        abyyy
        abyyy
        ac
        ad
        ae
        ac
        abyyy
        

        上例中, 本來\1應該是AB, 但是\L把它全變成小寫了. 而且後面的YYY也變成小寫了.

      • \l 

        把下一個字符變成小寫
      • \U 

        把REPLACEMENT中的字符變成大寫, 直到遇到\L和\E.
      • \u 

        把下一個字符變成大寫
      • \E 

        結束\L和\U的作用

      s命令後的FLAGS可以由下面幾種:



      • s命令默認只替換第一個匹配, g可以讓它全部替換
      • NUMBER 

        替換第NUMBER個匹配


      • 如果發生了替換, 打印模式空間. 

        搜索文件ab中xxx並替換成yyy打印出來:

        sed s/xxx/yyy/p ab
        
      • i, I 

        正則匹配忽略大小寫
    • y/SOURCE-CHARS/DEST-CHARS/ 

      類似tr命令, 用DEST-CHARS對應的字符替換出現在SOURCE-CHARS中的字符. 和s命令一樣, 分隔符/也可以是其他字符.



    • 打印行號. 下面的命令類似grep -n:

      sed  -n '/xxx/ {=; p}'
      
  • 高級命令 


    • 用pattern space替換hold space


    • 先在hold space追加換行符, 再往hold space追加pattern space


    • 用hold space替換pattern space


    • 先在pattern space追加換行符, 再往pattern space追加hold space


    • 交換pattern space和hold space的內容


    • 刪除模式空間的第一行. 如果模式空間中還有內容的話, 開始進入下一輪循環, 但不讀入輸入. 如果沒有內容的話, 讀入輸入並進行下一輪循環.


    • 追加換行符到pattern space, 並讀入下一行輸入追加到pattern space, 如果已經沒有任何輸入, 直接退出sed, 不再處理任何命令.


    • 大寫p, 打印pattern space第一行


    • 清空pattern space

  • 專家命令 
    • : LABEL 

      設定標籤, 類似C語言中設定一個標籤, 然後可以goto之
    • b [LABEL] 

      跳轉到標籤, 如果沒有提供標籤的話, 直接進入下一輪循環
    • t [LABEL] 

      這輪循環中, 如果s命令替換成功過, 則跳轉到標籤. 如果沒有提供標籤的話, 直接進入下一輪循環
    • T LABEL 

      和t LABEL相反, s命令替換失敗, 才跳轉到標籤

  • 命令選項 
    • -n, –quiet, –silent 

      禁止自動打印pattern space
    • -i[SUFFIX], –in-place[=SUFFIX] 

      原地編輯文件, 文本修改後會直接影響到輸入文件
    • -r, –regexp-extended 

      啓用擴展正則, 默認是基礎正則
    • -s, –separate 

      把每個文件當作單獨的輸入流, 而不是一個輸入流

  • 給我一個寄存器, 我可以幹好多事 

    各位看完上面所說的sed命令後, 是不是覺得sed只能做一些替換、刪除等操作, 爲什麼稱爲微語言呢, 我之所以把它歸到微語言這一類是有原因的, 它具有下面幾個語言的特徵:

    1. 有條件判斷能力, sed的地址就是一種條件判斷, 還有標籤命令也是條件判斷
    2. 有流程控制能力, 標籤命令就可以做到
    3. 有變量, 雖然很少, 只有2個, 但是仍然可以幹好多事了, 看下面的例子

    更多的例子參加sed info

    • tac 

      模擬tac

      sed -n '1!G; $p; h'
      
    • 爲數字字串增加逗號分隔符號, 將1234567變爲1,234,567 
      sed -r ':a; s/(.*[0-9])([0-9]{3})/\1,\2/; ta' <<< 124523536543652
      

12.2 awk (Alfred V. Aho, Peter J. Weinberger, Brian W. Kernighan.)

awk是文本處理的利器, 前面那些命令能幹的事它都能搞定.

  • 工作流程 

    awk的工作方式有點類似sed, sed是地址+命令, awk則是pattern+action, pattern是要匹配的模式, action是要執行的命令, pattern可以由下面幾種形式:

    • BEGIN 

      awk程序開始處理輸入時
    • END 

      awk程序結束處理輸入時
    • BEGINFILE 

      awk程序開始處理每個文件時
    • ENDFILE 

      awk程序結束處理每個文件時
    • regular expression
    • relational expression 

      關係表達式
    • pattern && pattern
    • pattern || pattern
    • pattern ? pattern : pattern
    • (pattern)
    • ! pattern
    • pattern1, pattern2

    action要以大括號括起來, 比如:

    awk '0{print} 1{print}' .emacs
    
  • 內置變量 
    • ARGC 

      awk輸入參數的個數, 不包括awk自己
    • ARGV 

      命令行參數
    • ARGIND 

      當前處理的文件在ARGV中的位置, ARGV[ARGIND]表示當前正在處理的文件, 可以通過這個變量來對不同的輸入文件做不同的處理
    • FNR (File Number Record) 

      當前文件的記錄總數
    • NR (Number Record) 

      目前處理的記錄總數
    • NF (Number of Field)

      當前記錄的字段數
  • 網絡編程 

    awk能開發網絡程序, 你相信嗎?

    $ cat test.awk
    #!/usr/bin/awk -f
    
    BEGIN {
        str = "GET /index.html HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n"
        print str |& "/inet/tcp/0/www.baidu.com/80"
        "/inet/tcp/0/www.baidu.com/80" |& getline
        print
    }
    
    $ awk -f test.awk
    HTTP/1.1 200 OK
    
  • 陷阱 
    • tolower/toupper 

      和tr一樣, 這2個函數也是對字節進行處理

    • 判斷元素是否存在 

      你是否這樣判斷某元素是否存在於某數組:

      if (a[e] != 2) { ... }
      

      如果輸入很大的話, 過會你就會發現你的awk佔了很多內存, 原因就是a[e]的時候, 如果awk發現a中沒有e, 就會把e插入到a中, 這樣一來內存自然越來越大, 正確的判斷方法是:

      if (!(e in a)) { ... }
      

      用過python的朋友可能會這樣寫:

      if (e not in a) { ... }
      

      很不幸, 沒有這樣的語法, 而且還不報錯, 我猜awk把e not連接成一個字符串了…

13 語言

  • a2p
  • s2p
  • perl
  • python

14 實例

14.1 我的正則會數學

  • multi-sort
發佈了55 篇原創文章 · 獲贊 16 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章