Linux shell 腳本編程-高級篇 (五)

繼: Linux shell 腳本編程-高級篇 (四)

5. sed 進階


sed 編輯器的基礎命令能滿足大多數日常文本編輯需求。本節介紹 sed 編輯器提供的更多高級特性。這些功能未必會經常用到,但當需要時,知道這些功能
的存在以及如何使用它們是必要的。


5.1 多行命令
-----------------------------------------------------------------------------------------------------------------------------------------
在使用 sed 編輯器的基礎命令時,可能注意到了一個侷限。所有的 sed 編輯器命令都是針對單行數據執行操作的。在 sed 讀取數據流時,它會基於換行符
的位置將數據分成行。sed 根據定義好的腳本命令一次處理一行數據,然後移到下一行重複這個過程。

有時需要對跨多行的數據執行特定操作。例如,如果正在數據中查找短語 Linux System Administrators Group,它很有可能出現在兩行中,每行各包含其中
一部分短語。如果用普通的 sed 命令來處理文本,就不可能發現這種被分開的短語。

sed 編輯器包含了三個可用來處理多行文本的特殊命令:

    □ N:將數據流中的下一行加進來創建一個多行組(multiline group)來處理。
    □ D:刪除多行組中的一行。
    □ P:打印多行組中的一行。


5.1.1 next 命令
-----------------------------------------------------------------------------------------------------------------------------------------


■ 單行的 next 命令
-----------------------------------------------------------------------------------------------------------------------------------------
小寫的 n 命令會告訴 sed 編輯器移動到數據流中的下一文本行,而不用重新回到命令的最開始再執行一遍。通常 sed 在移動到數據流中的下一文本行之前,
會在當前行上執行完所有定義好的命令。單行 next 命令改變了這個流程。

下面的例子中,有個數據文件,共有 5行內容,其中的兩行是空的。目標是刪除首行之後的空白行,而留下最後一行之前的空白行。如果寫一個刪掉空白行的
sed 腳本,會刪掉兩個空白行:

    [devalone@devalone 21]$ cat data1.txt
    This is the header line

    This is a data line

    This is the last line
    [devalone@devalone 21]$ sed '/^$/d' data1.txt
    This is the header line
    This is a data line
    This is the last line

由於要刪除的行是空行,沒有任何能夠標示這種行的文本可供查找。解決辦法是用 n 命令。

在這個例子中,腳本要查找含有單詞 header 的那一行。找到之後,n 命令會讓 sed 編輯器移動到文本的下一行,也就是那個空行。

    [devalone@devalone 21]$ sed '/header/{n;d}' data1.txt
    This is the header line
    This is a data line

    This is the last line

這時,sed 編輯器會繼續執行命令列表,該命令列表使用 d 命令來刪除空白行。sed 編輯器執行完命令腳本後,會從數據流中讀取下一行文本,並從頭開始
執行命令腳本。因爲 sed 編輯器再也找不到包含單詞 header 的行了。所以也不會有其他行會被刪掉。


■ 合併文本行
-----------------------------------------------------------------------------------------------------------------------------------------
單行 next 命令會將數據流中的下一文本行移動到 sed 編輯器的工作空間(稱爲模式空間)。多行版本的 next 命令(用大寫 N )會將下一文本行添加到
模式空間中已有的文本後。這樣的作用是將數據流中的兩個文本行合併到同一個模式空間中。文本行仍然用換行符分隔,但 sed 編輯器現在會將兩行文本當
成一行來處理。

下面的例子演示了 N 命令的工作方式:

    [devalone@devalone 21]$ cat data2.txt
    This is the header line.
    This is the first data line.
    This is the second data line.
    This is the last line.
    [devalone@devalone 21]$
    
    [devalone@devalone 21]$ sed '/first/{N; s/\n/ /}' data2.txt
    This is the header line.
    This is the first data line. This is the second data line.
    This is the last line.

sed 編輯器腳本查找含有單詞 first 的那行文本。找到該行後,它會用 N 命令將下一行合併到那行,然後用替換命令 s 將換行符替換成空格。結果是,
文本文件中的兩行在 sed 編輯器的輸出中成了一行。

如果要在數據文件中查找一個可能會分散在兩行中的文本短語的話,這是個很實用的應用程序。

示例:
    [devalone@devalone 21]$ cat data3.txt
    On Tuesday, the Linux System
    Administrator's group meeting will be held.
    All System Administrators should attend.
    Thank you for your attendance.
    
    [devalone@devalone 21]$ sed 'N; s/System Administrator/Desktop User/' data3.txt
    On Tuesday, the Linux System
    Administrator's group meeting will be held.
    All Desktop Users should attend.
    Thank you for your attendance.

替換命令會在文本文件中查找特定的雙詞短語 System Administrator。如果短語在一行中的話,事情很好處理,替換命令可以直接替換文本。但如果短語
分散在兩行中的話,替換命令就沒法識別匹配的模式了。

這時N命令就可以派上用場了:

    [devalone@devalone 21]$ sed 'N; s/System.Administrator/Desktop User/' data3.txt
    On Tuesday, the Linux Desktop User's group meeting will be held.
    All Desktop Users should attend.
    Thank you for your attendance.

用 N 命令將發現第一個單詞的那行和下一行合併後,即使短語內出現了換行,仍然可以找到它。

注意,替換命令在 System 和 Administrator之間用了通配符模式(.)來匹配空格和換行符這兩種情況。但當它匹配了換行符時,它就從字符串中刪掉換行符,
導致兩行合併成一行。這可能不是我們想要的。

要解決這個問題,可以在 sed 編輯器腳本中用兩個替換命令:一個用來匹配短語出現在多行中的情況,一個用來匹配短語出現在單行中的情況:

    [devalone@devalone 21]$ sed 'N
    > s/System\nAdministrator/Desktop\nUser/
    > s/System Administrator/Desktop User/
    > ' data3.txt
    On Tuesday, the Linux Desktop
    User's group meeting will be held.
    All Desktop Users should attend.
    Thank you for your attendance.

第一個替換命令專門查找兩個單詞間的換行符,並將它放在了替換字符串中。這樣就能在第一個替換命令專門在兩個檢索詞之間尋找換行符,並將其納入替換
字符串。這樣就允許在新文本的同樣位置添加換行符了。

但這個腳本中仍有個小問題。這個腳本總是在執行 sed 編輯器命令前將下一行文本讀入到模式空間。當它到了最後一行文本時,就沒有下一行可讀了,所以
N 命令會叫 sed 編輯器停止。如果要匹配的文本正好在數據流的最後一行上,命令就不會發現要匹配的數據。

示例:
    [devalone@devalone 21]$ cat data4.txt
    On Tuesday, the Linux System
    Administrator's group meeting will be held.
    All System Administrators should attend.
    [devalone@devalone 21]$
    
    [devalone@devalone 21]$ sed 'N
    s/System\nAdministrator/Desktop\nUser/
    s/System Administrator/Desktop User/
    ' data4.txt
    On Tuesday, the Linux Desktop
    User's group meeting will be held.
    All System Administrators should attend.

由於 System Administrator 文本出現在了數據流中的最後一行,N 命令會錯過它,因爲沒有其他行可讀入到模式空間跟這行合併。

可以輕鬆地解決這個問題,將單行命令放到 N 命令前面,並將多行命令放到 N 命令後面,像這樣:

    [devalone@devalone 21]$ sed '
    > s/System Administrator/Desktop User/
    > N
    > s/System\nAdministrator/Desktop\nUser/
    > ' data4.txt
    On Tuesday, the Linux Desktop
    User's group meeting will be held.
    All Desktop Users should attend.

現在,查找單行中短語的替換命令在數據流的最後一行也能正常工作,多行替換命令則會負責短語出現在數據流中間的情況。


5.1.2 多行刪除命令
-----------------------------------------------------------------------------------------------------------------------------------------
sed 編輯器用單行刪除命令(d)來刪除模式空間中的當前行。但和 N 命令一起使用時,使用單行刪除命令就要小心了。

示例:
    [devalone@devalone 21]$ cat data4.txt
    On Tuesday, the Linux System
    Administrator's group meeting will be held.
    All System Administrators should attend.

    [devalone@devalone 21]$ sed 'N; /System\nAdministrator/d' data4.txt
    All System Administrators should attend.
    [devalone@devalone 21]$

刪除命令會在不同的行中查找單詞 System和 Administrator,然後在模式空間中將兩行都刪掉。這未必是我們想要的結果。

sed 編輯器提供了多行刪除命令 D,它只刪除模式空間中的第一行。該命令會刪除到換行符(含換行符)爲止的所有字符。

示例:
    [devalone@devalone 21]$ sed 'N; /System\nAdministrator/D' data4.txt
    Administrator's group meeting will be held.
    All System Administrators should attend.

文本的第二行被 N 命令加到了模式空間,但仍然完好。如果需要刪掉目標數據字符串所在行的前一文本行,它能派得上用場。

示例: 刪除數據流中出現在第一行前的空白行

    [devalone@devalone 21]$ cat data5.txt

    This is the header line.
    This is a data line.

    This is the last line.
    [devalone@devalone 21]$

    [devalone@devalone 21]$ sed '/^$/{N; /header/D}' data5.txt
    This is the header line.
    This is a data line.

    This is the last line.

sed 編輯器腳本會查找空白行,然後用 N 命令來將下一文本行添加到模式空間。如果新的模式空間內容含有單詞 header,則 D 命令會刪除模式空間中的
第一行。如果不結合使用 N 命令和 D 命令,就不可能在不刪除其他空白行的情況下只刪除第一個空白行。


5.1.3 多行打印命令
-----------------------------------------------------------------------------------------------------------------------------------------
多行打印命令(P)沿用了同樣的方法。它只打印多行模式空間中的第一行。這包括模式空間中直到換行符爲止的所有字符。當用 -n 選項來阻止腳本輸出時,
它和顯示文本的單行 p 命令的用法大同小異。

示例:
    [devalone@devalone 21]$ cat data3.txt
    On Tuesday, the Linux System
    Administrator's group meeting will be held.
    All System Administrators should attend.
    Thank you for your attendance.
    
    [devalone@devalone 21]$ sed -n 'N; /System\nAdministrator/P' data3.txt
    On Tuesday, the Linux System

當多行匹配出現時,P 命令只會打印模式空間中的第一行。多行 P 命令的強大之處在和 N 命令及 D 命令組合使用時才能顯現出來。

D 命令的獨特之處在於強制 sed 編輯器返回到腳本的起始處,對同一模式空間中的內容重新執行這些命令(它不會從數據流中讀取新的文本行)。在命令腳
本中加入 N 命令,就能單步掃過整個模式空間,將多行一起匹配。

接下來,使用 P 命令打印出第一行,然後用 D 命令刪除第一行並繞回到腳本的起始處。一旦返回,N 命令會讀取下一行文本並重新開始這個過程。這個循環
會一直繼續下去,直到數據流結束。


5.2 保持空間
-----------------------------------------------------------------------------------------------------------------------------------------
模式空間(pattern space)是一塊活躍的緩衝區,在 sed 編輯器執行命令時它會保存待檢查的文本。但它並不是 sed 編輯器保存文本的唯一空間。

sed 編輯器有另一塊稱作保持空間(hold space)的緩衝區域。在處理模式空間中的某些行時候,可以用保持空間來臨時保存一些行。有 5 條命令可用來操作
保持空間,如下表所示:

    sed 編輯器的保持空間命令
    +-------+---------------------------------------------------------------------
    | 命 令    | 描 述
    +-------+---------------------------------------------------------------------
    | h        | 將模式空間複製到保持空間
    +-------+---------------------------------------------------------------------
    | H        | 將模式空間附加到保持空間
    +-------+---------------------------------------------------------------------
    | g        | 將保持空間複製到模式空間
    +-------+---------------------------------------------------------------------
    | G        | 將保持空間附加到模式空間
    +-------+---------------------------------------------------------------------
    | x        | 交換模式空間和保持空間的內容
    +-------+---------------------------------------------------------------------

這些命令用來將文本從模式空間複製到保持空間。這可以清空模式空間來加載其他要處理的字符串。

通常,在使用 h 或 H 命令將字符串移動到保持空間後,最終還要用 g、G 或 x 命令將保存的字符串移回模式空間(否則就不用在一開始考慮保存它們了)。

由於有兩個緩衝區域,弄明白哪行文本在哪個緩衝區域有時會比較麻煩。

示例: 用 h 和 g 命令來將數據在 sed 編輯器緩衝空間之間移動

    [devalone@devalone 21]$ cat data2.txt
    This is the header line.
    This is the first data line.
    This is the second data line.
    This is the last line.
    [devalone@devalone 21]$
    
    [devalone@devalone 21]$ sed -n '/first/{h; p; n; p; g; p}' data2.txt
    This is the first data line.
    This is the second data line.
    This is the first data line.
    [devalone@devalone 21]$

分析:
    (1) sed 腳本在地址中用正則表達式來過濾出含有單詞first的行;
    (2) 當含有單詞 first 的行出現時,h 命令將該行放到保持空間;
    (3) p 命令打印模式空間也就是第一個數據行的內容;
    (4) n 命令提取數據流中的下一行(This is the second data line),並將它放到模式空間;
    (5) p 命令打印模式空間的內容,現在是第二個數據行;
    (6) g 命令將保持空間的內容(This is the first data line)放回模式空間,替換當前文本;
    (7) p 命令打印模式空間的當前內容,現在變回第一個數據行了。

通過使用保持空間來回移動文本行,可以強制輸出中第一個數據行出現在第二個數據行後面。

示例:
    [devalone@devalone 21]$ sed -n '/first/ {h; n; p; g; p }' data2.txt
    This is the second data line.
    This is the first data line.


5.3 排除命令
-----------------------------------------------------------------------------------------------------------------------------------------
感嘆號命令(!)用來排除(negate)命令,也就是讓原本會起作用的命令不起作用。

示例:
    [devalone@devalone 21]$ sed -n '/header/!p' data2.txt
    This is the first data line.
    This is the second data line.
    This is the last line.
    
    [devalone@devalone 21]$ sed -n '/header/p' data2.txt
    This is the header line.


普通 p 命令只打印 data2.txt 文件中包含單詞 header 的那行。加了感嘆號之後,情況就相反了:除了包含單詞 header 那一行外,文件中其他所有的行都
被打印出來了。

感嘆號在有些應用中用起來很方便。之前演示了一種情況:sed 編輯器無法處理數據流中最後一行文本,因爲之後再沒有其他行了。可以用感嘆號來解決這個
問題。

示例:
    [devalone@devalone 21]$ sed 'N
    > s/System\nAdministrator/Desktop\nUser/
    > s/System Administrator/Desktop User/
    > ' data4.txt
    On Tuesday, the Linux Desktop
    User's group meeting will be held.
    All System Administrators should attend.

    [devalone@devalone 21]$ sed '$!N
    s/System\nAdministrator/Desktop\nUser/
    s/System Administrator/Desktop User/
    ' data4.txt
    On Tuesday, the Linux Desktop
    User's group meeting will be held.
    All Desktop Users should attend.

這個例子演示瞭如何配合使用感嘆號與 N 命令以及與美元符特殊地址。美元符表示數據流中的最後一行文本,所以當 sed 編輯器到了最後一行時,它沒有
執行 N 命令,但它對所有其他行都執行了這個命令。

使用這種方法,可以反轉數據流中文本行的順序。要實現這個效果(先顯示最後一行,最後顯示第一行),需要利用保持空間做一些特別的鋪墊工作。

    (1) 在模式空間中放置一行;
    (2) 將模式空間中的行放到保持空間中;
    (3) 在模式空間中放入下一行;
    (4) 將保持空間附加到模式空間後;
    (5) 將模式空間中的所有內容都放到保持空間中;
    (6) 重複執行第(3)~(5)步,直到所有行都反序放到了保持空間中;
    (7) 提取並打印行。

在使用這種方法時,不想在處理時打印行。這意味着要使用 sed 的 -n 命令行選項。下一步是決定如何將保持空間文本附加到模式空間文本後面。這可以用
G 命令完成。唯一的問題是不想將保持空間附加到要處理的第一行文本後面。這可以用感嘆號命令輕鬆解決:

    1!G

下一步就是將新的模式空間(含有已反轉的行)放到保持空間。這也非常簡單,只要用 h 命令就行。

將模式空間中的整個數據流都反轉了之後,要做的就是打印結果。當到達數據流中的最後一行時,就知道已經得到了模式空間的整個數據流。打印結果要用
下面的命令:

    $p

示例:
    [devalone@devalone 21]$ cat data2.txt
    This is the header line.
    This is the first data line.
    This is the second data line.
    This is the last line.
    [devalone@devalone 21]$ sed -n '{1!G; h; $p}' data2.txt
    This is the last line.
    This is the second data line.
    This is the first data line.
    This is the header line.


5.4 改變流
-----------------------------------------------------------------------------------------------------------------------------------------
通常,sed 編輯器會從腳本的頂部開始,一直執行到腳本的結尾(D 命令是個例外,它會強制sed編輯器返回到腳本的頂部,而不讀取新的行)。sed 編輯器
提供了一個方法來改變命令腳本的執行流程,其結果與結構化編程類似。


5.4.1 分支
-----------------------------------------------------------------------------------------------------------------------------------------
sed 編輯器提供了一種方法,可以基於地址、地址模式或地址區間排除一整塊命令。這允許只對數據流中的特定行執行一組命令。

分支(branch)命令 b 的格式如下:

    [address]b [label]

address 參數決定了哪些行的數據會觸發分支命令。label 參數定義了要跳轉到的位置。如果沒有加 label 參數,跳轉命令會跳轉到腳本的結尾。

示例:
    [devalone@devalone 21]$ cat data2.txt
    This is the header line.
    This is the first data line.
    This is the second data line.
    This is the last line.
    [devalone@devalone 21]$ sed '{2,3b; s/This is/Is this/; s/line./test?/}' data2.txt
    Is this the header test?
    This is the first data line.
    This is the second data line.
    Is this the last test?

分支命令在數據流中的第 2行和第 3行處跳過了兩個替換命令。

要是不想直接跳到腳本的結尾,可以爲分支命令定義一個要跳轉到的標籤。標籤以冒號開始,最多可以是7個字符長度。

    :label2

要指定標籤,將它加到 b 命令後即可。使用標籤允許跳過地址匹配處的命令,但仍然執行腳本中的其他命令。

示例:
    [devalone@devalone 21]$ sed '{/first/b jump1; s/This is the/No jump on/
    > :jump1
    > s/This is the/Jump here on/}' data2.txt
    No jump on header line.
    Jump here on first data line.
    No jump on second data line.
    No jump on last line.

跳轉命令指定如果文本行中出現了 first,程序應該跳到標籤爲 jump1的腳本行。如果分支命令的模式沒有匹配,sed編輯器會繼續執行腳本中的命令,包括
分支標籤後的命令(因此,所有的替換命令都會在不匹配分支模式的行上執行)。

如果某行匹配了分支模式, sed 編輯器就會跳轉到帶有分支標籤的那行。因此,只有最後一個替換命令會執行。

下面例子演示了跳轉到 sed 腳本後面的標籤上。也可以跳轉到腳本中靠前面的標籤上,這樣就達到了循環的效果:

    [devalone@devalone 21]$ echo "This, is, a, test, to, remove, commas." | sed -n '{
    > :start
    > s/,//1p
    > b start
    > }'
    This is, a, test, to, remove, commas.
    This is a, test, to, remove, commas.
    This is a test, to, remove, commas.
    This is a test to, remove, commas.
    This is a test to remove, commas.
    This is a test to remove commas.

腳本的每次迭代都會刪除文本中的第一個逗號,並打印字符串。這個腳本有個問題:它永遠不會結束。這就形成了一個無窮循環,不停地查找逗號,直到使用
Ctrl+C 組合鍵發送一個信號,手動停止這個腳本。

要防止這個問題,可以爲分支命令指定一個地址模式來查找。如果沒有模式,跳轉就應該結束:

    [devalone@devalone 21]$ echo "This, is, a, test, to, remove, commas." | sed -n '{
    :start
    s/,//1p
    /,/b start
    }'
    This is, a, test, to, remove, commas.
    This is a, test, to, remove, commas.
    This is a test, to, remove, commas.
    This is a test to, remove, commas.
    This is a test to remove, commas.
    This is a test to remove commas.

現在分支命令只會在行中有逗號的情況下跳轉。在最後一個逗號被刪除後,分支命令不會再執行,腳本也就能正常停止了。


5.4.2 測試
-----------------------------------------------------------------------------------------------------------------------------------------
類似於分支命令,測試(test)命令(t)也可以用來改變 sed 編輯器腳本的執行流程。測試命令會根據替換命令的結果跳轉到某個標籤,而不是根據地址
進行跳轉。

如果替換命令成功匹配並替換了一個模式,測試命令就會跳轉到指定的標籤。如果替換命令未能匹配指定的模式,測試命令就不會跳轉。

測試命令使用與分支命令相同的格式:

    [address]t [label]

跟分支命令一樣,在沒有指定標籤的情況下,如果測試成功,sed 會跳轉到腳本的結尾。

測試命令提供了對數據流中的文本執行基本的 if-then 語句的一個低成本辦法。例如,如果已經做了一個替換,不需要再做另一個替換,可以用測試命令。

示例:
    [devalone@devalone 21]$ sed '{
    > s/first/matched/
    > t
    > s/This is the/No match on/
    > }' data2.txt
    No match on header line.
    This is the matched data line.
    No match on second data line.
    No match on last line.

第一個替換命令會查找模式文本 first。如果匹配了行中的模式,它就會替換文本,而且測試命令會跳過後面的替換命令。如果第一個替換命令未能匹配模式,
第二個替換命令就會被執行。

有了測試命令,就能結束之前用分支命令形成的無限循環。

示例:
    [devalone@devalone 21]$ echo "This, is, a, test, to, remove, commas. " | sed -n '{
    > :start
    > s/,//p
    > t start
    > }'
    This is, a, test, to, remove, commas.
    This is a, test, to, remove, commas.
    This is a test, to, remove, commas.
    This is a test to, remove, commas.
    This is a test to remove, commas.
    This is a test to remove commas.

當無需替換時,測試命令不會跳轉而是繼續執行剩下的腳本。

 

5.5 模式替代
-----------------------------------------------------------------------------------------------------------------------------------------
已經知道如何在 sed命令中使用模式來替代數據流中的文本。然而在使用通配符時,很難知道到底哪些文本會匹配模式。

舉個例子,假如想在行中匹配的單詞兩邊上放上引號。如果只是要匹配模式中的一個單詞,那就非常簡單:

    [devalone@devalone 21]$ echo "The cat sleeps in his hat." | sed 's/cat/"cat"/'
    The "cat" sleeps in his hat.

但如果在模式中用通配符(.)來匹配多個單詞呢?

    [devalone@devalone 21]$ echo "The cat sleeps in his hat." | sed 's/.at/".at"/g'
    The ".at" sleeps in his ".at".

模式字符串用點號通配符來匹配at前面的一個字母。遺憾的是,用於替代的字符串無法匹配已匹配單詞中的通配符字符。


5.5.1 & 符號
-----------------------------------------------------------------------------------------------------------------------------------------
sed 編輯器提供了一個解決辦法。& 符號可以用來代表替換命令中的匹配的模式。不管模式匹配的是什麼樣的文本,都可以在替代模式中使用 & 符號來使用
這段文本。這樣就可以操作模式所匹配到的任何單詞了。

示例:
    [devalone@devalone 21]$ echo "The cat sleeps in his hat." | sed 's/.at/"&"/g'
    The "cat" sleeps in his "hat".

當模式匹配了單詞c at,"cat" 就會出現在了替換後的單詞裏。當它匹配了單詞 hat,"hat" 就出現在了替換後的單詞中。

5.5.2 替代單獨的單詞
-----------------------------------------------------------------------------------------------------------------------------------------
& 符號會提取匹配替換命令中指定模式的整個字符串。有時只想提取這個字符串的一部分。

sed 編輯器用圓括號來定義替換模式中的子模式。可以在替代模式中使用特殊字符引用每個子模式。替代字符由反斜線和數字組成。數字表明子模式的位置。
sed 編輯器會給第一個子模式分配字符 \1,給第二個子模式分配字符 \2,依此類推。


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    當在替換命令中使用圓括號時,必須用轉義字符將它們標示爲分組字符而不是普通的圓括號。這跟轉義其他特殊字符正好相反。

示例:
    [devalone@devalone 21]$ echo "The System Administrator manual" | sed '
    > s/\(System\) Administrator/\1 User/'
    The System User manual

這個替換命令用一對圓括號將單詞 System 括起來,將其標示爲一個子模式。然後它在替代模式中使用 \1 來提取第一個匹配的子模式。這沒什麼特別的,
但在處理通配符模式時卻特別有用。

如果需要用一個單詞來替換一個短語,而這個單詞剛好是該短語的子字符串,但那個子字符串碰巧使用了通配符,這時使用子模式會方便很多。

示例:
    [devalone@devalone 21]$ echo "That furry cat is pretty" | sed 's/furry \(.at\)/\1/'
    That cat is pretty

    [devalone@devalone 21]$ echo "That furry hat is pretty" | sed 's/furry \(.at\)/\1/'
    That hat is pretty

在這種情況下,不能用 & 符號,因爲它會替換整個匹配的模式。子模式提供了方案,允許將模式中的某部分作爲替代模式。

需要在兩個或多個子模式間插入文本時,這個特性尤其有用。

示例: 使用子模式在大數字中插入逗號

    [devalone@devalone 21]$ echo "1234567" | sed '{
    > :start
    > s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
    > t start
    > }'
    1,234,567
    [devalone@devalone 21]$

這個腳本將匹配模式分成了兩部分。

    .*[0-9]
    [0-9]{3}

這個模式會查找兩個子模式。第一個子模式是以數字結尾的任意長度的字符。第二個子模式是若干組三位數字。如果這個模式在文本中找到了,替代文本會
在兩個子模式之間加一個逗號,每個子模式都會通過其位置來標示。這個腳本使用測試命令來遍歷這個數字,直到放置好所有的逗號。


5.6 在腳本中使用 sed
-----------------------------------------------------------------------------------------------------------------------------------------


5.6.1 使用包裝腳本
-----------------------------------------------------------------------------------------------------------------------------------------
實現 sed 編輯器腳本的過程很煩瑣,尤其是腳本很長的話。可以將 sed 命令放到 shell 包裝腳本(wrapper)中,不用每次使用時都重新鍵入整個腳本。
包裝腳本充當 sed 編輯器腳本和命令行之間的中間人角色。

在 shell 腳本中,可以將普通的 shell 變量及參數和 sed 編輯器腳本一起使用。

示例:
    [devalone@devalone 21]$ cat reverse.sh
    #!/bin/bash
    # shell wrapper for sed editor script
    # to reverse text file lines.

    sed -n '{ 1!G; h; $p}' $1

腳本用 sed 編輯器腳本來反轉數據流中的文本行。它使用 shell 參數 $1 從命令行中提取第一個參數,作爲要進行反轉的文件名。

運行:
    [devalone@devalone 21]$ ./reverse.sh data2.txt
    This is the last line.
    This is the second data line.
    This is the first data line.
    This is the header line.

現在能在任何文件上輕鬆使用這個 sed 編輯器腳本,再不用每次都在命令行上重新輸入了。


5.6.2 重定向sed 的輸出
-----------------------------------------------------------------------------------------------------------------------------------------
默認情況下,sed 編輯器會將腳本的結果輸出到 STDOUT 上。可以在 shell 腳本中使用各種標準方法對 sed 編輯器的輸出進行重定向。

可以在腳本中用 $() 將 sed 編輯器命令的輸出重定向到一個變量中,以備後用。

示例: 使用 sed 腳本來向數值計算結果添加逗號

    [devalone@devalone 21]$ cat fact.sh
    #!/bin/bash
    # Add commas to number in factorial answer
    #
    factorial=1
    counter=1
    number=$1
    #
    while [ $counter -le $number ]
    do
            factorial=$[ $factorial * $counter ]
            counter=$[ $counter + 1 ]
    done
    #
    result=$(echo $factorial | sed '{
    :start
    s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
    t start
    }')
    #
    echo "The result is $result"

運行:
    [devalone@devalone 21]$ ./fact.sh 20
    The result is 2,432,902,008,176,640,000


在使用普通的階乘計算腳本後,腳本的結果會被作爲 sed 編輯器腳本的輸入,它會給結果加上逗號。然後 echo 語句使用這個值產生最終結果。


5.7 創建 sed 實用工具
-----------------------------------------------------------------------------------------------------------------------------------------


5.7.1 加倍行間距
-----------------------------------------------------------------------------------------------------------------------------------------
示例: 向文本文件的行間插入空白行

    [devalone@devalone 21]$ sed 'G' data2.txt
    This is the header line.

    This is the first data line.

    This is the second data line.

    This is the last line.

    [devalone@devalone 21]$
    
這個技巧的關鍵在於保持空間的默認值。G 命令會簡單地將保持空間內容附加到模式空間內容後。當啓動 sed 編輯器時,保持空間只有一個空行。將它附加
到已有行後面,就在已有行後面創建了一個空白行。

這個腳本在數據流的最後一行後面也加了一個空白行,使得文件的末尾也產生了一個空白行。如果不想要這個空白行,可以用排除符號(!)和尾行符號 ($)
來確保腳本不會將空白行加到數據流的最後一行後面。

示例:
    [devalone@devalone 21]$ sed '$!G' data2.txt
    This is the header line.

    This is the first data line.

    This is the second data line.

    This is the last line.
    [devalone@devalone 21]$

只要該行不是最後一行,G 命令就會附加保持空間內容。當 sed 編輯器到了最後一行時,它會跳過 G 命令。


5.7.2 對可能含有空白行的文件加倍行間距
-----------------------------------------------------------------------------------------------------------------------------------------
如果文本文件已經有一些空白行,但想給所有行加倍行間距要怎麼辦呢?如果用前面的腳本,有些區域會有太多的空白行,因爲每個已有的空白行也會被加倍。

示例:
    [devalone@devalone 21]$ cat data6.txt
    This is line one.
    This is line two.

    This is line three.
    This is line four.
    [devalone@devalone 21]$ sed '$!G' data6.txt
    This is line one.

    This is line two.

 

    This is line three.

    This is line four.

現在,在原來空白行的位置有了三個空白行。

這個問題的解決辦法是,首先刪除數據流中的所有空白行,然後用 G 命令在所有行後插入新的空白行。要刪除已有的空白行,需要將 d 命令和一個匹配
空白行的模式一起使用:

    /^$/d

這個模式使用了行首符號(^)和行尾符號($)。將這個模式加到腳本中會生成想要的結果。

示例:
    [devalone@devalone 21]$ sed '/^$/d; $!G' data6.txt
    This is line one.

    This is line two.

    This is line three.

    This is line four.


5.7.3 給文件中的行編號
-----------------------------------------------------------------------------------------------------------------------------------------
之前演示瞭如何用等號來顯示數據流中行的行號:

    [devalone@devalone 21]$ sed '=' data2.txt
    1
    This is the header line.
    2
    This is the first data line.
    3
    This is the second data line.
    4
    This is the last line.

行號是在數據流中實際行的上方。比較好的解決辦法是將行號和文本放在同一行。

N 命令用來合併行,在 sed 腳本中使用這個命令應該不難。這個工具的技巧在於不能將兩個命令放到同一個腳本中。

在獲得了等號命令的輸出之後,可以通過管道將輸出傳給另一個 sed 編輯器腳本,它會使用 N 命令來合併這兩行。還需要用替換命令將換行符更換成空格
或製表符。最終的解決辦法看起來如下:

    [devalone@devalone 21]$ sed '=' data2.txt | sed 'N; s/\n/ /'
    1 This is the header line.
    2 This is the first data line.
    3 This is the second data line.
    4 This is the last line.

有些 bash shell 命令也可以添加行號,但它們會另外加入一些東西(有可能是不需要的間隔)。

示例:

    [devalone@devalone 21]$ nl data2.txt
         1  This is the header line.
         2  This is the first data line.
         3  This is the second data line.
         4  This is the last line.
    [devalone@devalone 21]$ cat -n data2.txt
         1  This is the header line.
         2  This is the first data line.
         3  This is the second data line.
         4  This is the last line.


5.7.4 打印末尾行
-----------------------------------------------------------------------------------------------------------------------------------------
如果只需處理一個長輸出(比如日誌文件)中的末尾幾行,要怎麼辦呢?

美元符代表數據流中最後一行,所以只顯示最後一行很容易:

    [devalone@devalone 21]$ sed -n '$p' data2.txt
    This is the last line.

如何用美元符來顯示數據流末尾的若干行呢?答案是創建滾動窗口。

滾動窗口是檢驗模式空間中文本行塊的常用方法,它使用 N 命令將這些塊合併起來。N 命令將下一行文本附加到模式空間中已有文本行後面。一旦在模式
空間有了一個10行的文本塊,可以用美元符來檢查是否已經處於數據流的尾部。如果不在,就繼續向模式空間增加行,同時刪除原來的行(D 命令會刪除模
式空間的第一行)。

通過循環 N 命令和 D 命令,在向模式空間的文本行塊增加新行的同時也刪除了舊行。分支命令非常適合這個循環。要結束循環,只要識別出最後一行並用
q 命令退出就可以了。

示例:
    [devalone@devalone 21]$ cat data7.txt
    This is line 1.
    This is line 2.
    This is line 3.
    This is line 4.
    This is line 5.
    This is line 6.
    This is line 7.
    This is line 8.
    This is line 9.
    This is line 10.
    This is line 11.
    This is line 12.
    This is line 13.
    This is line 14.
    This is line 15.
    [devalone@devalone 21]$ sed '{
    > :start
    > $q; N; 11, $D
    > b start
    > }' data7.txt
    This is line 6.
    This is line 7.
    This is line 8.
    This is line 9.
    This is line 10.
    This is line 11.
    This is line 12.
    This is line 13.
    This is line 14.
    This is line 15.

這個腳本會首先檢查這行是不是數據流中最後一行。如果是,退出(quit)命令會停止循環。N 命令會將下一行附加到模式空間中當前行之後。如果當前行
在第 10 行後面,11,$D 命令會刪除模式空間中的第一行。這就會在模式空間中創建出滑動窗口效果。因此,這個 sed 程序腳本只會顯示出 data7.txt文件
最後10行。


5.7.5 刪除行
-----------------------------------------------------------------------------------------------------------------------------------------
另一個有用的 sed 編輯器工具是刪除數據流中不需要的空白行。刪除數據流中的所有空白行很容易,但要選擇性地刪除空白行則需要一點創造力。


■ 刪除連續的空白行
-----------------------------------------------------------------------------------------------------------------------------------------
刪除連續空白行的最簡單辦法是用地址區間來檢查數據流。sed 編輯器會對所有匹配指定地址區間的行執行該命令。

刪除連續空白行的關鍵在於創建包含一個非空白行和一個空白行的地址區間。如果 sed 編輯器遇到了這個區間,它不會刪除行。但對於不匹配這個區間的行
(兩個或更多的空白行),它會刪除這些行。

下面是完成這個操作的腳本:

    /./,/^$/!d

區間是 /./ 到 /^$/。區間的開始地址會匹配任何含有至少一個字符的行。區間的結束地址會匹配一個空行。在這個區間內的行不會被刪除。

示例:
    [devalone@devalone 21]$ cat data8.txt
    This is line one.

 

    This is line two.

    This is line three.

 


    This is line four.
    [devalone@devalone 21]$ sed '/./,/^$/!d' data8.txt
    This is line one.

    This is line two.

    This is line three.

    This is line four.

無論文件的數據行之間出現了多少空白行,在輸出中只會在行間保留一個空白行。


■ 刪除開頭的空白行
-----------------------------------------------------------------------------------------------------------------------------------------
刪除數據流頂部的空白行不難。下面是完成這個功能的腳本:

    /./,$!d

這個腳本用地址區間來決定哪些行要刪掉。這個區間從含有字符的行開始,一直到數據流結束。在這個區間內的任何行都不會從輸出中刪除。這意味着含有
字符的第一行之前的任何行都會刪除。

示例:
    [devalone@devalone 21]$ cat data9.txt

 

    This is line one.


    This is line two.
    [devalone@devalone 21]$ sed '/./,$!d' data9.txt
    This is line one.


    This is line two.

測試文件在數據行之前有兩個空白行。這個腳本成功地刪除了開頭的兩個空白行,保留了數據中的空白行。


■ 刪除結尾的空白行
-----------------------------------------------------------------------------------------------------------------------------------------
刪除結尾的空白行並不像刪除開頭的空白行那麼容易。就跟打印數據流的結尾一樣,刪除數據流結尾的空白行也需要花點心思,利用循環來實現。

命令:
    sed '{
    :start
    /^\n*$/{$d; N; b start }
    }'

注意,在正常腳本的花括號裏還有花括號。這允許在整個命令腳本中將一些命令分組。該命令組會被應用在指定的地址模式上。

地址模式能夠匹配只含有一個換行符的行。如果找到了這樣的行,而且還是最後一行,刪除命令會刪掉它。如果不是最後一行,N 命令會將下一行附加到它
後面,分支命令會跳到循環起始位置重新開始。

示例:
    [devalone@devalone 21]$ cat data10.txt
    This is the first line
    This is the second line

 


    [devalone@devalone 21]$ sed '{
    > :start
    > /^\n*$/{$d; N; b start }
    > }' data10.txt
    This is the first line
    This is the second line

這個腳本成功刪除了文本文件結尾的空白行。


5.7.6 刪除 HTML 標籤
-----------------------------------------------------------------------------------------------------------------------------------------
標準的 HTML Web 頁面包含一些不同類型的 HTML 標籤,標明瞭正確顯示頁面信息所需要的格式化功能。下面是個 HTML 文件的例子:

    [devalone@devalone 21]$ cat data11.txt
    <html>
    <head>
    <title>This is the page title</title>
    </head>
    <body>
    <p>
    This is the <b>first</b> line in the Web page.
    This should provide some <i>useful</i>
    information to use in our sed script.
    </body>
    </html>


HTML 標籤由小於號和大於號來識別。大多數 HTML 標籤都是成對出現的:一個起始標籤(比如<b>用來加粗),以及另一個結束標籤(比如</b>用來結束加粗)。

但如果不夠小心的話,刪除HTML標籤可能會帶來問題。乍一看,可能認爲刪除 HTML 標籤的辦法就是查找以小於號(<)開頭、大於號(>)結尾且其中有數據
的文本字符串:

    s/<.*>//g

很遺憾,這個命令會出現一些意料之外的結果:

    [devalone@devalone 21]$ sed 's/<.*>//g' data11.txt

 

 


    This is the  line in the Web page.
    This should provide some
    information to use in our sed script.


    [devalone@devalone 21]$

注意,標題文本以及加粗和傾斜的文本都不見了。sed 編輯器將這個腳本忠實地理解爲小於號和大於號之間的任何文本,且包括其他的小於號和大於號。
每次文本出現在 HTML 標籤中(比如<b>first</b>),這個 sed 腳本都會刪掉整個文本。

這個問題的解決辦法是讓 sed 編輯器忽略掉任何嵌入到原始標籤中的大於號。要這麼做的話,可以創建一個字符組來排除大於號。腳本改爲:

    s/<[^>]*>//g
    
這個腳本現在能夠正常工作了,它會顯示要在 Web 頁面 HTML 代碼裏看到的數據:

    [devalone@devalone 21]$ sed 's/<[^>]*>//g' data11.txt


    This is the page title

 

    This is the first line in the Web page.
    This should provide some useful
    information to use in our sed script.


    [devalone@devalone 21]$

要想看起來更清晰一些,可以加一條刪除命令來刪除多餘的空白行:

    [devalone@devalone 21]$ sed 's/<[^>]*>//g; /^$/d' data11.txt
    This is the page title
    This is the first line in the Web page.
    This should provide some useful
    information to use in our sed script.

現在緊湊多了,只有想要看的數據。

 

系列目錄:

Linux shell 腳本編程-高級篇 (一)

Linux shell 腳本編程-高級篇 (二)

Linux shell 腳本編程-高級篇 (三)

Linux shell 腳本編程-高級篇 (四)

Linux shell 腳本編程-高級篇 (五)

Linux shell 腳本編程-高級篇 (六)

Linux shell 腳本編程-高級篇 (七)

 

 

-----------------------------------------------------------------------------------------------------------------------------------------
參考:

    《Linux 命令行與 shell 腳本編程大全》 第 3 版 —— 2016.8(美)Richard Blum  Cristine Bresnahan

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章