Linux sed命令高階用法精講

2020-07-16 10:04:50
《Linux sed用法詳解》一節給大家介紹了如何用 sed 命令的基本功能處理文字中的資料,所涵蓋的知識點,可以滿足日常大多數文字編輯需求。本節將介紹 sed 提供的一些高階功能,這些功能雖不常用,但知道這些功能的存在以及用法也是有必要的。

sed 多行命令

在學習 sed 命令的基礎功能時,你可能注意到了一個局限,即所有的 sed 命令都只是針對單行資料執行操作,在 sed 命令讀取緩衝區中的文字資料時,它會基於換行符的位置,將資料分成行,sed 會根據定義好的指令碼命令一次處理一行資料。

但是,有時我們需要對跨多行的資料執行特定操作。比如說,在文字中查詢一串字串"http://c.biancheng.net",它很有可能出現在兩行中,每行各包含其中一部分。這時,如果用普通的 sed 編輯器命令來處理文字,就不可能發現這種被分開的情況。

幸運的是,sed 命令的設計人員已經考慮到了這種情況,並設計了對應的解決方案。sed 包含了三個可用來處理多行文字的特殊命令,分別是:
  1. Next 命令(N):將資料流中的下一行加進來建立一個多行組來處理。
  2. Delete(D):刪除多行組中的一行。
  3. Print(P):列印多行組中的一行。

注意,以上命令的縮寫,都為大寫。

N 多行操作命令

N 命令會將下一行文字內容新增到緩衝區已有資料之後(之間用換行符分隔),從而使前後兩個文字行同時位於緩衝區中,sed 命令會將這兩行資料當成一行來處理。

下面這個例子演示的 N 命令的功能:

[[email protected] ~]# 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.
[[email protected] ~]# 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 的輸出中成了一行。

如果要在資料檔案中查詢一個可能會分散在兩行中的文字短語,如何實現呢?這裡給大家一個範例:

[[email protected] ~]# 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.
[[email protected] ~]# 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 指令碼中用兩個替換命令,一個用來匹配短語出現在多行中的情況,一個用來匹配短語出現在單行中的情況,比如:

[[email protected] ~]# sed 'N
> s/SystemnAdministrator/DesktopnUser/
> 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 程式停止,這就導致,如果要匹配的文字正好在最後一行中,sed 命令將不會發現要匹配的資料。

解決這個 bug 的方法是,將單行命令放到 N 命令前面,將多行命令放到 N 命令後面,像這樣:

[[email protected] ~]# sed '
> s/SystemnAdministrator/DesktopnUser/
> 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.

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

D 多行刪除命令

sed 不僅提供了單行刪除命令(d),也提供了多行刪除命令 D,其作用是只刪除緩衝區中的第一行,也就是說,D 命令將緩衝區中第一個換行符(包括換行符)之前的內容刪除掉。

比如說:

[[email protected] ~]# cat data4.txt
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
[[email protected] ~]# sed 'N ; /SystemnAdministrator/D' data4.txt
Administrator's group meeting will be held.
All System Administrators should attend.

文字的第二行被 N 命令加到了緩衝區,因此 sed 命令第一次匹配就是成功,而 D 命令會將緩衝區中第一個換行符之前(也就是第一行)的資料刪除,所以,得到了如上所示的結果。

下面的例子中,它會刪除資料流中出現在第一行前的空白行:

[[email protected] ~]# cat data5.txt

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

This is the last line.
[[email protected] ~]# 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 命令會刪除緩衝區中的第一行。

P 多行列印命令

同 d 和 D 之間的區別一樣,P(大寫)命令和單行列印命令 p(小寫)不同,對於具有多行資料的緩衝區來說,它只會列印緩衝區中的第一行,也就是首個換行符之前的所有內容。

例如,test.txt 檔案中的內容如下:

[[email protected] ~]# cat test.txt
aaa
bbb
ccc
ddd
eee
fff


表 1 中是對 test.txt 檔案中的內容分別用 p 命令和 P 命令後,產生的輸出資訊的對比。

表 1 P 命令和 p 命令的對比
P(大寫)命令 p(小寫)命令

[[email protected] ~]# sed '/.*/N;P'
aaa
aaa
bbb
ccc
ccc
ddd
eee
eee
fff


 

[[email protected] ~]# sed '/.*/N;p'
aaa
bbb
aaa
bbb
ccc
ddd
ccc
ddd
eee
fff
eee
fff


第一個 sed 命令,每次都使用 N 將下一行內容追加到緩衝區內容的後面(用換行符間隔),也就是說,第一次時緩衝區中的內容為 aaanbbb,但 P(大寫) 命令的作用的列印換行符之前的內容,也就是 aaa,之後則是 sed 在自動輸出功能輸出 aaa 和 bbb(sed 命令會自動將 n 輸出為換行),依次類推,就輸出了所看到的結果。

第二個 sed 命令,使用的是 p (小寫)單行列印命令,它會將緩衝區中的所有內容全部列印出來(n 會自動輸出為換行),因此,出現了看到的結果。

sed 保持空間

前面我們一直說,sed 命令處理的是緩衝區中的內容,其實這裡的緩衝區,應稱為模式空間。值得一提的是,模式空間並不是 sed 命令儲存檔案的唯一空間。sed 還有另一塊稱為保持空間的緩衝區域,它可以用來臨時儲存一些資料。

表 2 列出了 5 條可用來操作保持空間的命令。

表 2 sed 保持空間命令
命令 功能
h 將模式空間中的內容複製到保持空間
H 將模式空間中的內容附加到保持空間
g 將保持空間中的內容複製到模式空間
G 將保持空間中的內容附加到模式空間
x 交換模式空間和保持空間中的內容

通常,在使用 h 或 H 命令將字串移動到保持空間後,最終還要用 g、G 或 x 命令將儲存的字串移回模式空間。保持空間最直接的作用是,一旦我們將模式空間中所有的檔案複製到保持空間中,就可以清空模式空間來載入其他要處理的文字內容。

由於有兩個緩衝區域,下面的例子中演示了如何用 h 和 g 命令來將資料在 sed 緩衝區之間移動。

[[email protected] ~]# 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.
[[email protected] ~]# 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.

這個例子的執行過程是這樣的:
  • sed指令碼命令用正規表示式過濾出含有單詞first的行;
  • 當含有單詞 first 的行出現時,h 命令將該行放到保持空間;
  • p 命令列印模式空間也就是第一個資料行的內容;
  • n 命令提取資料流中的下一行(This is the second data line),並將它放到模式空間;
  • p 命令列印模式空間的內容,現在是第二個資料行;
  • g 命令將保持空間的內容(This is the first data line)放回模式空間,替換當前文字;
  • p 命令列印模式空間的當前內容,現在變回第一個資料行了。

sed改變指定流程

b 分支命令

通常,sed 程式的執行過程會從第一個指令碼命令開始,一直執行到最後一個指令碼命令(D 命令是個例外,它會強制 sed 返回到指令碼的頂部,而不讀取新的行)。sed 提供了 b 分支命令來改變命令指令碼的執行流程,其結果與結構化程式設計類似。

b 分支命令基本格式為:

[address]b [label]

其中,address 引數決定了哪些行的資料會觸發分支命令,label 引數定義了要跳轉到的位置。

需要注意的是,如果沒有加 label 引數,跳轉命令會跳轉到指令碼的結尾,比如:

[[email protected] ~]# 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.
[[email protected] ~]# 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?

可以看到,因為 b 命令未指定 label 引數,因此資料流中的第2行和第3行並沒有執行那兩個替換命令。

如果我們不想直接跳到指令碼的結尾,可以為 b 命令指定一個標籤(也就是格式中的 label,最多為 7 個字元長度)。在使用此該標籤時,要以冒號開始(比如 :label2),並將其放到要跳過的指令碼命令之後。這樣,當 sed 命令匹配並處理該行文字時,會跳過標籤之前所有的指令碼命令,但會執行標籤之後的指令碼命令。

比如說:

[[email protected] ~]# 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 會繼續執行所有的指令碼命令。

b 分支命令除了可以向後跳轉,還可以向前跳轉,例如:

[[email protected] ~]# 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.

在這個例子中,當緩衝區中的行內容中有逗號時,指令碼命令就會一直迴圈執行,每次疊代都會刪除文字中的第一個逗號,並列印字串,直至內容中沒有逗號。

t 測試命令

類似於 b 分支命令,t 命令也可以用來改變 sed 指令碼的執行流程。t 測試命令會根據 s 替換命令的結果,如果匹配並替換成功,則指令碼的執行會跳轉到指定的標籤;反之,t 命令無效。

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

[address]t [label]

跟分支命令一樣,在沒有指定標籤的情況下,如果 s 命令替換成功,sed 會跳轉到指令碼的結尾(相當於不執行任何指令碼命令)。例如:

[[email protected] ~]# 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,如果匹配並替換成功,命令會直接跳過後面的替換命令;反之,如果第一個替換命令未能匹配成功,第二個替換命令就會被執行。

再舉個例子:

[[email protected] ~]#  echo "This, is, a, test, to, remove, commas. " | sed -n '{
> :start
> s/,//1p
> 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.