Sed&awk筆記之sed篇

http://blog.csdn.net/a81895898/article/details/8482387正則表達式

Sed是什麼

《sed and awk》一書中(1.2 A Stream Editor)是這樣解釋的:shell

Sed is a "non-interactive" stream-oriented editor. It is stream-oriented because, like many UNIX
programs, input flows through the program and is directed to standard output.express

Sed本質上是一個編輯器,可是它是非交互式的,這點與VIM不一樣;同時它又是面向字符流的,輸入的字符流通過Sed的處理後輸出。這兩個特性使得Sed成爲命令行下面很是有用的一個處理工具。編程

若是對sed的歷史有興趣,能夠看書中2.1節"Awk, by Sed and Grep, out of Ed",這裏就很少介紹。vim

基本概念

sed命令的語法以下所示:緩存

sed [options] script filename

同大多數Linux命令同樣,sed也是從stdin中讀取輸入,而且將輸出寫到stdout,可是當filename被指定時,則會從指定的文件中獲取輸入,輸出能夠重定向到文件中,可是須要注意的是,該文件絕對不能與輸入的文件相同。bash

options是指sed的命令行參數,這一塊並非重點,參數也很少。app

script是指須要對輸入執行的一個或者多個操做指令(instruction),sed會依次讀取輸入文件的每一行到緩存中並應用script中指定的操做指令,所以而帶來的變化並不會影響最初的文件(注:若是使用sed時指定-i參數則會影響最初的文件)。若是操做指令不少,爲了避免影響可讀性,能夠將其寫到文件中,並經過-f參數指定scriptfile:編程語言

sed -f scriptfile filename

這裏有一個建議,在命令行中指定的操做指令最好用單引號引發來,這樣能夠避免shell對特殊字符的處理(如空格、$等,關於這一點能夠參考簡潔的Bash編程技巧續篇 - 9. 引號之間的區別)。這個建議一樣適用grep/awk等命令,固然若是有時候確實不適合使用單引號時,記得對特殊字符轉義。編輯器

不管是將操做指令經過命令行指定,仍是寫入到文件中做爲一個sed腳本,必須包含至少一個指令,不然用sed就沒有意義了。通常會同時指定多個操做指令,這時候指令之間的順序就顯得很是重要。而你的腦海中必須有這麼一個概念,即每一個指令應用後,當前輸入的行會變成什麼樣子。要作到這一點首先必需要了解sed的工做原理,要作到「知其然,且知其因此然」。

每條操做指令由pattern和procedure兩部分組成,pattern通常是用'/'分隔的正則表達式(在sed中也有多是行號,具體參見Sed命令地址匹配問題總結),而procedure則是一連串編輯命令(action)。

sed的處理流程,簡化後是這樣的:

  1. 讀入新的一行內容到緩存空間;
  2. 從指定的操做指令中取出第一條指令,判斷是否匹配pattern;
  3. 若是不匹配,則忽略後續的編輯命令,回到第2步繼續取出下一條指令;
  4. 若是匹配,則針對緩存的行執行後續的編輯命令;完成後,回到第2步繼續取出下一條指令;
  5. 當全部指令都應用以後,輸出緩存行的內容;回到第1步繼續讀入下一行內容;
  6. 當全部行都處理完以後,結束;

具體流程見下圖:
simple sed process flow chart

簡單例子

概念若是脫離實際的例子就會顯得很是枯燥無趣,這本書在這一點上處理得很好,都是配上實際的例子來說解的。不過有點遺憾的是,書中的例子大可能是與troff macro有關的,咱們很難有條件來實際測試例子,在筆記中我會盡可能使用更加淺顯易懂的例子來講明。

下面是摘自書中的一個例子,假設有個文件list:

John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK
Terry Kalkas, 402 Lans Road, Beaver Falls PA
Eric Adams, 20 Post Road, Sudbury MA
Hubert Sims, 328A Brook Road, Roanoke VA
Amy Wilde, 334 Bayshore Pkwy, Mountain View CA
Sal Carpenter, 73 6th Street, Boston MA

這個例子中用到的知識點都會在後文中介紹,你能夠先知道有這個東西就行。

假如,如今打算將MA替換成Massachusetts,可使用替換命令s:

$ sed -e 's/MA/Massachusetts/' list John Daggett, 341 King Road, Plymouth Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury Massachusetts Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston Massachusetts

這裏面的-e選項是可選的,這個參數只是在命令行中同時指定多個操做指令時才須要用到,如:

$ sed -e 's/ MA/, Massachusetts/' -e 's/ PA/, Pennsylvania/' list John Daggett, 341 King Road, Plymouth, Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls, Pennsylvania Eric Adams, 20 Post Road, Sudbury, Massachusetts Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston, Massachusetts

即便在多個操做指令的狀況下,-e參數也不是必需的,我通常不會加-e參數,好比上面的例子能夠換成下面的寫法:

$ sed 's/ MA/, Massachusetts/;s/ PA/, Pennsylvania/' list 

操做指令之間能夠用逗號分隔,這點和shell命令能夠用逗號分隔是同樣的。

這裏提早說一點使用s替換命令須要當心的地方,不要忘記結尾的斜槓,若是你遇到下面的錯誤說明你犯了這個錯誤:

$ sed -e 's/ MA/, Massachusetts' list sed: -e expression #1, char 21: unterminated `s' command

假如這時,我只想輸出替換命令影響的行,而不想輸出文件的全部內容,須要怎麼辦?這個時候能夠利用替換命令s的p選項,它會將替換後的內容打印到標準輸出。可是僅僅這樣是不夠的,不然輸出的結果並不是咱們所想要的:

$ sed -e 's/ MA/, Massachusetts/p' list John Daggett, 341 King Road, Plymouth, Massachusetts John Daggett, 341 King Road, Plymouth, Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury, Massachusetts Eric Adams, 20 Post Road, Sudbury, Massachusetts Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston, Massachusetts Sal Carpenter, 73 6th Street, Boston, Massachusetts

從上面能夠看出,文件的全部行都被打印到標準輸出,而且被替換命令修改的行輸出了兩遍。形成這個問題的緣由是,sed命令應用完全部指定以後默認會將當前行打印輸出,因此就有了上面的結果。解決方法是在使用sed命令是指定-n參數,該參數會抑制sed默認的輸出:

$ sed -n 's/ MA/, Massachusetts/p' list John Daggett, 341 King Road, Plymouth, Massachusetts Eric Adams, 20 Post Road, Sudbury, Massachusetts Sal Carpenter, 73 6th Street, Boston, Massachusetts

正則表達式

書中的第3章的內容都是介紹正則表達式,這是由於sed的不少地方都須要用到正則表達式,好比操做指令的pattern部分以及替換命令中用到的匹配部分。關於正則這部分能夠看Linux/Unix工具與正則表達式的POSIX規範,文章中已經講得很是清楚了,我自已在忘記一些內容的時候也是去查閱這篇文章。

 

sed並不是是將一個編輯命令分別應用到每一行,而後再取下一個編輯命令。偏偏相反,sed是以行的方式來處理的。另一方面,每一行都是被讀入到一塊緩存空間,該空間名爲模式空間(pattern space),這是一個很重要的概念,在後文中會屢次被說起。所以sed操做的都是最初行的拷貝,同時後續的編輯命令都是應用到前面的命令編輯後輸出的結果,因此編輯命令之間的順序就顯得格外重要。

簡單例子

讓咱們來看一個很是簡單的例子,將一段文本中的pig替換成cow,而且將cow替換成horse:

$ sed 's/pig/cow/;s/cow/hores/' input

初看起來好像沒有問題,可是其實是錯誤的,緣由是第一個替換命令將全部的pig都替換成cow,緊接着的替換命令是基於前一個結果處理的,將全部的cow都替換成horse,形成的結果是所有的pig/cow都被替換成了horse,這樣違背了咱們的初衷。

在這種狀況下,只須要調換下兩個編輯命令的順序:

$ sed 's/cow/hores/;s/pig/cow/' input

模式空間的轉換

sed只會緩存一行的內容在模式空間,這樣的好處是sed能夠處理大文件而不會有任何問題,不像一些編輯器由於要一次性載入文件的一大塊內容到緩存中而致使內存不足。下面用一個簡單的例子來說解模式空間的轉換過程,以下圖所示:

模式空間的轉換

如今要把一段文本中的Unix System與UNIX System都要統一替換成The UNIX Operating System,所以咱們用兩句替換命令來完成這個目的:

s/Unix /UNIX / s/UNIX System/UNIX Operating System/

對應上圖,過程以下:

  1. 首先一行內容The Unix System被讀入模式空間;
  2. 應用第一條替換命令將Unix替換成UNIX;
  3. 如今模式空間的內容變成The UNIX System;
  4. 應用第二條替換命令將UNIX System替換成UNIX Operating System;
  5. 如今模式空間的內容變成The UNIX Operating System;
  6. 全部編輯命令執行完畢,默認輸出模式空間中的行;

地址匹配

地址匹配在sed中是很是重要的一塊內容,由於它限制sed的編輯命令到底應用在哪些行上。默認狀況下,sed是全局匹配的,即對全部輸入行都應用指定的編輯命令,這是由於sed依次讀入每一行,每一行都會成爲當前行並被處理,因此s/CA/California/g會將全部輸入行的CA替換成California。這一點跟vi/vim是不同的,衆所周知,vim的替換命令默認是替換當前行的內容,除非你指定%s纔會做全局替換。

能夠經過指定地址來限制sed的處理範圍,例如只想將替換包含Sebastopol的行:

Sebastopol/s/CA/California/

/Sebastopol/是一個正則表達式匹配包含Sebastopol的行,所以像行「San Francisco, CA」則不會被替換。

sed命令中能夠包含0個、1個或者2個地址(地址對),地址能夠爲正則表達式(如/Sebastopol/),行號或者特殊的行符號(如$表示最後一行):

  • 若是沒有指定地址,默認將編輯命令應用到全部行;
  • 若是指定一個地址,只將編輯命令應用到匹配該地址的行;
  • 若是指定一個地址對(addr1,addr2),則將編輯命令應用到地址對中的全部行(包括起始和結束);
  • 若是地址後面有一個感嘆號(!),則將編輯命令應用到不匹配該地址的全部行;

爲了方便理解上述內容,咱們以刪除命令(d)爲例,默認不指定地址將會刪除全部行:

$ sed 'd' list $ 

指定地址則刪除匹配的行,如刪除第一行:

$ sed '1d' list Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

或者刪除最後一行,$符號在這裏表示最後一行,這點要下正則表達式中的含義區別開來:

$ sed '$d' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA

這裏經過指定行號刪除,行號是sed命令內部維護的一個計數變量,該變量只有一個,而且在多個文件輸入的狀況下也不會被重置。

前面都是以行號來指定地址,也能夠經過正則表達式來指定地址,如刪除包含MA的行:

$ sed '/MA/d' list Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA

經過指定地址對能夠刪除該範圍內的全部行,例如刪除第3行到最後一行:

$ sed '2,$d' list John Daggett, 341 King Road, Plymouth MA

使用正則匹配,刪除從包含Alice的行開始到包含Hubert的行結束的全部行:

$ sed '/Alice/,/Hubert/d' list John Daggett, 341 King Road, Plymouth MA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

固然,行號和地址對是能夠混用的:

$ sed '2,/Hubert/d' list John Daggett, 341 King Road, Plymouth MA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

若是在地址後面指定感嘆號(!),則會將命令應用到不匹配該地址的行:

$ sed '2,/Hubert/!d' list Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA

以上介紹的都是最基本的地址匹配形式,GNU Sed(也是咱們用的sed版本)基於此添加了幾個擴展的形式,具體能夠看man手冊,或者能夠看我以前寫的Sed命令地址匹配問題總結一文。

上面說的內容都是對匹配的地址執行單個命令,若是要執行多個編輯命令要怎麼辦?sed中能夠用{}來組合命令,就比如編程語言中的語句塊,例如:

$ sed -n '1,4{s/ MA/, Massachusetts/;s/ PA/, Pennsylvania/;p}' list John Daggett, 341 King Road, Plymouth, Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls, Pennsylvania

 

在開始以前,首先回顧上一篇的重點內容:地址匹配。上一篇中介紹過,地址能夠指定0個,1個或者2個。地址的形式能夠爲斜槓分隔的正則表達式(例如/test/),行號(例如3,5)或者特殊符號(例如$)。若是沒有指定地址,說明sed應用的編輯命令是全局的;若是是1個地址,編輯命令只是應用到匹配的那一行;若是是一對地址,編輯命令則應用到該地址對匹配的行範圍。關於地址匹配的內容具體能夠看Sed命令地址匹配問題總結

書中說,對於sed編輯命令的語法有兩種約定,分別是

[address]command               # 第一種
[line-address]command          # 第二種

第一種[address]是指能夠爲任意地址包括地址對,第二種[line-address]是指只能爲單個地址。文中指出,例如i(後方插入命令)、a(前方追加命令)以及=(打印行號命令)等都屬於第二種命令,可是實際上我在ArchLinux上測試,指定地址對也沒有報錯。不過爲了兼容性,建議按做者所說的作。

如下是要介紹的所有基礎命令:

名稱 命令 語法 說明
替換 s [address]s/pattern/replacement/flags 替換匹配的內容
刪除 d [address]d 刪除匹配的行
插入 i [line-address]i\

text
在匹配行的前方插入文本
追加 a [line-address]a\

text
在匹配行的後方插入文本
行替換 c [address]c\

text
將匹配的行替換成文本text
打印行 p [address]p 打印在模式空間中的行
打印行號 = [address]= 打印當前行行號
打印行 l [address]l 打印在模式空間中的行,同時顯示控制字符
轉換字符 y [address]y/SET1/SET2/ 將SET1中出現的字符替換成SET2中對應位置的字符
讀取下一行 n [address]n 將下一行的內容讀取到模式空間
讀文件 r [line-address]r file 將指定的文件讀取到匹配行以後
寫文件 w [address]w file 將匹配地址的全部行輸出到指定的文件中
退出 q [line-address]q 讀取到匹配的行以後即退出

替換命令: s

替換命令的語法是:

[address]s/pattern/replacement/flags

其中[address]是指地址,pattern是替換命令的匹配表達式,replacement則是對應的替換內容,flags是指替換的標誌位,它能夠包含如下一個或者多個值:

  • n: 一個數字(取值範圍1-512),代表僅替換前n個被pattern匹配的內容;
  • g: 表示全局替換,替換全部被pattern匹配的內容;
  • p: 僅當行被pattern匹配時,打印模式空間的內容;
  • w file:僅當行被pattern匹配時,將模式空間的內容輸出到文件file中;

若是flags爲空,則默認替換第一次匹配:

$ echo "column1 column2 column3 column4" | sed 's/ /;/' column1;column2 column3 column4

若是flags中包含g,則表示全局匹配:

$ echo "column1 column2 column3 column4" | sed 's/ /;/g' column1;column2;column3;column4

若是flags中明確指定替換第n次的匹配,例如n=2:

$ echo "column1 column2 column3 column4" | sed 's/ /;/2' column1 column2;column3 column4

當替換命令的pattern與地址部分是同樣的時候,好比/regexp/s/regexp/replacement/能夠省略替換命令中的pattern部分,這在單個編輯命令的狀況下沒多大用處,可是在組合命令的場景下仍是能省很多功夫的,例以下面是摘自文中的一個段落:

The substitute command is applied to the lines matching the address. If no
address is specified, it is applied to all lines that match the pattern, a
regular expression. If a regular expression is supplied as an address, and no
pattern is specified, the substitute command matches what is matched by the
address.  This can be useful when the substitute command is one of multiple
commands applied at the same address. For an example, see the section "Checking
Out Reference Pages" later in this chapter.

如今要在substitute command後面增長("s"),同時在被修改的行前面增長+號,如下是使用的sed命令:

$ sed '/substitute command/{s//&("s")/;s/^/+ /}' paragraph.txt 

這裏咱們用到了組合命令,而且地址匹配的部分和第一個替換命令的匹配部分是同樣的,因此後者咱們省略了,在replacement部分用到了&這個元字符,它表明以前匹配的內容,這點咱們在後面介紹。執行後的結果爲:

+ The substitute command("s") is applied to the lines matching the address. If no
address is specified, it is applied to all lines that match the pattern, a
regular expression. If a regular expression is supplied as an address, and no
+ pattern is specified, the substitute command("s") matches what is matched by the
+ address.  This can be useful when the substitute command("s") is one of multiple
commands applied at the same address. For an example, see the section "Checking
Out Reference Pages" later in this chapter.

替換命令的一個技巧是中間的分隔符是能夠更改的(這個技巧咱們在簡潔的Bash編程技巧續篇 - 5. 你知道sed的這個特性嗎?中也曾經介紹過),這個技巧在有些地方很是有用,好比路徑替換,下面是採用默認的分隔符和使用感嘆號做爲分隔符的對比:

$ find /usr/local -maxdepth 2 -type d | sed 's/\/usr\/local\/man/\/usr\/share\/man/' $ find /usr/local -maxdepth 2 -type d | sed 's!/usr/local/man!/usr/share/man!'

替換命令中還有一個很重要的部分——replacement(替換內容),即將匹配的部分替換成replacement。在replacemnt部分中也有幾個特殊的元字符,它們分別是:

  • &: 被pattern匹配的內容;
  • \num: 被pattern匹配的第num個分組(正則表達式中的概念,\(..\)括起來的部分稱爲分組;
  • \: 轉義符號,用來轉義&,\, 回車等符號

&元字符咱們已經在上面的例子中介紹過,這裏舉個例子介紹下\num的用法。在Linux系統中,默認都開了幾個tty可讓你切換(ctrl+alt+1, ctrl+alt+2 ...),例如CentOS 6.0+的系統默認是6個:

[root@localhost ~]# grep -B 1 ACTIVE_CONSOLES /etc/sysconfig/init  # What ttys should gettys be started on? ACTIVE_CONSOLES=/dev/tty[1-6]

能夠經過修改上面的配置來減小開機啓動的時候建立的tty個數,好比咱們只想要2個:

[root@localhost ~]# sed -r -i 's!(ACTIVE_CONSOLES=/dev/tty\[1-)6]!\12]!' /etc/sysconfig/init  ACTIVE_CONSOLES=/dev/tty[1-2]

其中-i參數表示直接修改原文件,-r參數是指使用擴展的正則表達式(ERE),擴展的正則表達式中分組的括號不須要用反斜槓轉義"(ACTIVE_CONSOLES=/dev/tty\[1-)",這裏[是有特殊含義的(表示字符組),因此須要轉義。在替換的內容中使用\1來引用這個匹配的分組內容,1表明分組的編號,表示第一個分組。

刪除命令: d

刪除命令的語法是:

[address]d

刪除命令能夠用於刪除多行內容,例如1,3d會刪除1到3行。刪除命令會將模式空間中的內容所有刪除,而且致使後續命令不會執行而且讀入新行,由於當前模式空間的內容已經爲空。咱們能夠試試:

$ sed '2,${d;=}' list John Daggett, 341 King Road, Plymouth MA

以上命令嘗試在刪除第2行到最後一行以後,打印當前行號(=),可是事實上並無執行該命令。

插入行/追加行/替換行命令: i/a/c

這三個命令的語法以下所示:

# Append 追加
[line-address]a\
text
# Insert 插入
line-address]i\
text
# Change 行替換 
[address]c\
text

以上三個命令,行替換命令(c)容許地址爲多個地址,其他兩個都只容許單個地址(注:在ArchLinux上測試代表,追加和插入命令都容許多個地址,sed版本爲GNU sed version 4.2.1

追加命令是指在匹配的行後面插入文本text;相反地,插入命令是指匹配的行前面插入文本text;最後,行替換命令會將匹配的行替換成文本text。文本text並無被添加到模式空間,而是直接輸出到屏幕,所以後續的命令也不會應用到添加的文本上。注意,即便使用-n參數也沒法抑制添加的文本的輸出。

咱們用實際的例子來簡單介紹下這三個命令的用法,依然用最初的文本list:

$ cat list
John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

如今,咱們要在第2行後面添加'------':

$ sed '2a\ -------------------------------------- ' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA -------------------------------------- Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

或者能夠在第3行以前插入:

$ sed '3i\ -------------------------------------- ' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA -------------------------------------- Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

咱們來測試下文本是否確實沒有添加到模式空間,由於模式空間中的內容默認是會打印到屏幕的:

$ sed -n '2a\ -------------------------------------- ' list --------------------------------------

經過-n參數來抑制輸出後發現插入的內容依然被輸出,因此能夠斷定插入的內容沒有被添加到模式空間。

使用行替換命令將第2行到最後一行的內容所有替換成'----':

$ sed '2,$c\ -------------------------------------- ' list John Daggett, 341 King Road, Plymouth MA --------------------------------------

打印命令: p/l/=

這裏純粹的打印命令應該是指p,可是由於後二者(l和=)和p差很少,而且相對都比較簡單,因此這裏放到一塊兒介紹。

這三個命令的語法是:

[address]p
[address]=
[address]l

p命令用於打印模式空間的內容,例如打印list文件的第一行:

$ sed -n '1p' list John Daggett, 341 King Road, Plymouth MA

l命令相似p命令,不過會顯示控制字符,這個命令和vim的list命令類似,例如:

$ echo "column1 column2 column3^M" | sed -n 'l' column1\tcolumn2\tcolumn3\r$

=命令顯示當前行行號,例如:

$ sed '=' list 1 John Daggett, 341 King Road, Plymouth MA 2 Alice Ford, 22 East Broadway, Richmond VA 3 Orville Thomas, 11345 Oak Bridge Road, Tulsa OK 4 Terry Kalkas, 402 Lans Road, Beaver Falls PA 5 Eric Adams, 20 Post Road, Sudbury MA 6 Hubert Sims, 328A Brook Road, Roanoke VA 7 Amy Wilde, 334 Bayshore Pkwy, Mountain View CA 8 Sal Carpenter, 73 6th Street, Boston MA

轉換命令: y

轉換命令的語法是:

[address]y/SET1/SET2/

它的做用是在匹配的行上,將SET1中出現的字符替換成SET2中對應位置的字符,例如1,3y/abc/xyz/會將1到3行中出現的a替換成x,b替換成y,c替換成z。是否是以爲這個功能很熟悉,其實這一點和tr命令是同樣的。能夠經過y命令將小寫字符替換成大寫字符,不過命令比較長:

$ echo "hello, world" | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' HELLO, WORLD

使用tr命令來轉換成大寫:

$ echo "hello, world" | tr a-z A-Z HELLO, WORLD

取下一行命令: n

取下一行命令的語法爲:

[address]n

n命令爲將下一行的內容提早讀入,而且將以前讀入的行(在模式空間中的行)輸出到屏幕,而後後續的命令會應用到新讀入的行上。所以n命令也會同d命令同樣改變sed的控制流程。

書中給出了一個例子來介紹n的用法,假設有這麼一個文本:

$ cat text
.H1 "On Egypt" Napoleon, pointing to the Pyramids, said to his troops: "Soldiers, forty centuries have their eyes upon you."

如今要將.H1後面的空行刪除:

$ sed '/.H1/{n;/^$/d}' text .H1 "On Egypt" Napoleon, pointing to the Pyramids, said to his troops: "Soldiers, forty centuries have their eyes upon you."

讀寫文件命令: r/w

讀寫文件命令的語法是:

[line-address]r file
[address]w file

讀命令將指定的文件讀取到匹配行以後,而且輸出到屏幕,這點相似追加命令(a)。咱們以書中的例子來說解讀文件命令。假設有一個文件text:

$ cat text
For service, contact any of the following companies: [Company-list] Thank you.

同時咱們有一個包含公司名稱列表的文件company.list:

$ cat company.list Allied Mayflower United

如今咱們要將company.list的內容讀取到[Company-list]以後:

$ sed '/^\[Company-list]/r company.list' text For service, contact any of the following companies: [Company-list] Allied Mayflower United Thank you.

更好的處理應當是用公司名稱命令替換[Company-list],所以咱們還須要刪除[Company-list]這一行:

$ sed '/^\[Company-list]/r company.list;/^\[Company-list]/d;' text For service, contact any of the following companies: [Company-list] Thank you.

可是結果咱們非但沒有刪除[Company-list],並且company.list的內容也不見了?這是怎麼回事呢?

下面是我猜想的過程,讀文件的命令會將r空格後面的全部內容都當成文件名,即company.list;/^\[Company-list]/d;,而後讀取命令的時候發現該文件不存在,可是sed命令讀取不存在的文件是不會報錯的。因此什麼事都沒幹成。

咱們用-e選項將兩個命令分開就正常了:

$ sed -e '/^\[Company-list]/r company.list' -e '/^\[Company-list]/d;' text For service, contact any of the following companies: Allied Mayflower United Thank you.

寫命令將匹配地址的全部行輸出到指定的文件中。假設有一個文件內容以下,前半部分是人名,後半部分是區域名稱:

$ cat text 
Adams, Henrietta Northeast Banks, Freda South Dennis, Jim Midwest Garvey, Bill Northeast Jeffries, Jane West Madison, Sylvia Midwest Sommes, Tom South

如今咱們要將不一樣區域的人名字寫到不一樣的文件中:

$ sed '/Northeast$/w region.northeast > /South$/w region.south > /Midwest$/w region.midwest > /West$/w region.west' text Adams, Henrietta Northeast Banks, Freda South Dennis, Jim Midwest Garvey, Bill Northeast Jeffries, Jane West Madison, Sylvia Midwest Sommes, Tom South

查看輸出的文件:

$ cat region. region.midwest region.northeast region.south region.west $ cat region.midwest Dennis, Jim Midwest Madison, Sylvia Midwest

咱們也能夠試試將全部的w命令放到一塊兒用分號分隔,發現會出下面的錯誤,它把w空格後面的部分看成文件名,這樣就驗證了上面的猜想:

$ sed '/Northeast$/w region.northeast;/south$/w region.south;' text sed: couldn't open file region.northeast;/south$/w region.south;: No such file or directory

退出命令: q

退出命令的語法是

[line-address]q

當sed讀取到匹配的行以後即退出,不會再讀入新的行,而且將當前模式空間的內容輸出到屏幕。例如打印前3行內容:

$ sed '3q' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK

打印前3行也能夠用p命令:

$ sed -n '3p' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK

可是對於大文件來講,前者比後者效率更高,由於前者讀取到第N行以後就退出了。後者雖然打印了前N行,可是後續的行仍是要繼續讀入,只不會不做處理。

到此爲止,sed基礎命令的部分就介紹完了。下面一篇的內容會在最近幾天更新上來,主要會介紹sed高級命令。不過通常狀況下,會使用基礎命令已經差很少了,高級命令不必定須要去了解。

下面是我整理的一份思惟導圖(附.xmind文件下載地址):
sed基礎命令

上一篇中介紹的基礎命令都是面向行的,通常狀況下,這種處理並無什麼問題,可是當匹配的內容是錯開在兩行時就會有問題,最明顯的例子就是某些英文單詞會被分紅兩行。

幸運地是,sed容許將多行內容讀取到模式空間,這樣你就能夠匹配跨越多行的內容。本篇筆記主要介紹這些命令,它們可以建立多行模式空間而且處理之。其中,N/D/P這三個多行命令分別對應於小寫的n/d/p命令,後者咱們在上一篇已經介紹。它們的功能是相似的,區別在於命令影響的內容不一樣。例如D命令與d命令一樣是刪除模式空間的內容,只不過d命令會刪除模式空間中全部的內容,而D命令僅會刪除模式空間中的第一行。

讀下一行:N

N命令將下一行的內容讀取到當前模式空間,可是下n命令不同的地方是N命令並無直接輸出當前模式空間中的行,而是把下一行追加到當前模式空間,兩行之間用回車符\n鏈接,以下圖所示:

模式空間包含多行以後,正則表達式的^/$符號的意思就變了,^是匹配模式空間的最開始而非行首,$是匹配模式空間的最後位置而非行尾。

書中給的第一例子,是替換如下文本中的"Owner and Operator Guide"爲"Installation Guide",正如咱們在本篇開頭說的Owner and Operator Guide跨越在兩行:

Consult Section 3.1 in the Owner and Operator
Guide for a description of the tape drives
available on your system.

咱們用N命令試下手:

$ sed '/Operator$/{N;s/Owner and Operator\nGuide/Installation Guide/}' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system.

不過這個例子有兩個侷限:

  • 咱們知道Owner and Operator Guide分割的位置;
  • 執行替換命令後,先後兩行拼接在一塊兒,致使這行過長;

第二點,能夠這樣解決:

$ sed '/Operator$/{N;s/Owner and Operator\nGuide /Installation Guide\n/}' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system.

如今去掉第1個前提,咱們引入更加複雜的測試文本,這段文本中Owner and Operator Guide有位於一行的,也有跨越多行的狀況:

$ cat text
Consult Section 3.1 in the Owner and Operator Guide for a description of the tape drives available on your system. Look in the Owner and Operator Guide shipped with your system. Two manuals are provided including the Owner and Operator Guide and the User Guide. The Owner and Operator Guide is shipped with your system.

相應地修改下以前執行的命令,以下所示:

$ sed 's/Owner and Operator Guide/Installation Guide/ /Owner/{ N s/ *\n/ / s/Owner and Operator Guide */Installation Guide\ / }' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system. Look in the Installation Guide shipped with your system. Two manuals are provided including the Installation Guide and the User Guide. The Installation Guide is shipped with your system.

這裏咱們首先將在單行出現的Owner and Operator Guide替換爲Installation Guide,而後再尋找匹配Owner的行,匹配後讀取下一行的內容到模式空間,而且將中間的換行符替換成空格,最後再替換Owner and Operator Guide。

解釋起來很簡單,可是中間仍是有些門道的。好比你可能以爲這裏最前面的s/Owner and Operator Guide/Installation Guide/命令是多餘的,假設你刪除這一句:

$ sed '/Owner/{ > N > s/ *\n/ / > s/Owner and Operator Guide */Installation Guide\ > / > }' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system. Look in the Installation Guide shipped with your system. Two manuals are provided including the Installation Guide and the User Guide. The Owner and Operator Guide is shipped with your system.

最明顯的問題是最後一行沒有被替換,緣由是當最後一行的被讀入到模式空間後,匹配Owner,執行N命令讀入下一行,可是由於當前已是最後一行,因此N讀取替換,告訴sed能夠退出了,sed也不會繼續執行接下來的替換命令(注:書中說最後一行不會被打印輸出,我這邊測試是會輸出的)。因此這裏須要在使用N的時候加一個判斷,即當前行爲最後一行時,不讀取下一行的內容:$!N,這一點在不少場合都是有用的,更改後從新執行:

$ sed '/Owner/{ > $!N > s/ *\n/ / > s/Owner and Operator Guide */Installation Guide\ > / > }' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system. Look in the Installation Guide shipped with your system. Two manuals are provided including the Installation Guide and the User Guide. The Installation Guide is shipped with your system.

上面只是爲了用例子說明N的用法,可能解決方案未必是最好的。

刪除行:D

該命令刪除模式空間中第一行的內容,而它對應的小d命令刪除模式空間的全部內容。D不會致使讀入新行,相反它會回到最初的編輯命令,重要應用在模式空間剩餘的內容上。

後面半句開始比較難以理解,用書中的一個例子來解釋下。如今咱們有一個文本文件,內容以下所示,行之間有空行:

$ cat text
This line is followed by 1 blank line. This line is followed by 2 blank lines. This line is followed by 3 blank lines. This line is followed by 4 blank lines. This is the end.

如今咱們要刪除多餘的空行,將多個空行縮減成一行。假如咱們使用d命令來刪除,很簡單的邏輯:

$ sed '/^$/{N;/^\n$/d}' text This line is followed by 1 blank line. This line is followed by 2 blank lines. This line is followed by 3 blank lines. This line is followed by 4 blank lines. This is the end.

咱們會發現一個奇怪的結果,奇數個數的相連空行已經被合併成一行,可是偶數個數的卻所有被刪除了。形成這樣的緣由須要從新翻譯下上面的命令,當匹配一個空行是,將下一行也讀取到模式空間,而後若下一行也是空行,則模式空間中的內容應該是\n,所以匹配^\n$,從而執行d命令會將模式空間中的內容清空,結果就是相連的兩個空行都被刪除。這樣就能夠理解爲何相連奇數個空行的狀況下是正常的,而偶數個數就有問題了。

這種狀況下,咱們就應該用D命令來處理,這樣作就獲得預期的結果了:

$ sed '/^$/{N;/^\n$/D}' text This line is followed by 1 blank line. This line is followed by 2 blank lines. This line is followed by 3 blank lines. This line is followed by 4 blank lines. This is the end.

D命令只會刪除模式空間的第一行,並且刪除後會從新在模式空間的內容上執行編輯命令,相似造成一個循環,前提是相連的都是空行。當匹配一個空行時,N讀取下一行內容,此時匹配^\n$致使模式空間中的第一行被刪除。如今模式空間中的內容是空的,從新執行編輯命令,此時匹配/^$/。繼續讀取下一行,當下一行依然爲空行時,重複以前的動做,不然輸出當前模式空間的內容。形成的結果是連續多個空行,只有最後一個空行是保留輸出的,其他的都被刪除了。這樣的結果纔是咱們最初但願獲得的。

打印行:P

P命令與p命令同樣是打印模式空間的內容,不一樣的是前者僅打印模式空間的第一行內容,然後者是打印全部的內容。由於編輯命令所有執行完以後,sed默認會輸出模式空間的內容,因此通常狀況下,p和P命令都是與-n選項一塊兒使用的。可是有一種狀況是例外的,即編輯命令的執行流程被改變的狀況,例如N,D等。不少狀況下,P命令都是用在N命令以後,D命令以前的。這三個命令合起來,能夠造成一人輸入輸出的循環,而且每次只打印一行:讀入一行後,N繼續讀下一行,P命令打印第一行,D命令刪除第一行,執行流程回到最開始重複該過程。

$ echo -e "line1\nline2\nline3" | sed '$!N;P;D'

不過多行命令用起來要格外當心,你要用本身的大腦去演算一遍執行的過程,要否則很容易出錯,好比:

$ echo -e "line1\nline2\nline3" | sed -n 'N;1P'

你可能指望打印第一行的內容,事實上並無輸出。緣由是當N繼續讀入第二行後,當前行號已是2了,咱們在第二篇筆記中曾經說過,行號只是sed在內部維護的一個計數變量而已,每當讀入新的一行,行號就加一:

$ echo -e "line1\nline2\nline3" | sed -n '$!N;=' 2 3

咱們依然用替換Unix System爲Unix Operating System做爲例子,介紹N/P/D三個命令是如何配合使用的。

示例文本以下所示,爲了舉例方便,這段文本僅有三行內容,恰好能夠演示一個循環的處理過程:

$ cat text
The UNIX System and UNIX ...

執行的命令:

$ sed '/UNIX$/{ > N > s/\nSystem/ Operating &/ > P > D > }' text The UNIX Operating System and UNIX ...

執行過程以下圖所示:
NDP Loop

以上三個命令的用法與以前介紹的基礎命令是大相徑庭的,有些同窗可能都沒有接觸過。在下一篇中,我會介紹更多高級的命令,同時爲引入一個新的概念:保持空間(Hold Space)。

保持空間

保持空間用於保存模式空間的內容,模式空間的內容能夠複製到保持空間,一樣地保持空間的內容能夠複製回模式空間。sed提供了幾組命令用來完成複製的工做,其它命令沒法匹配也不能修改模式空間的內容。

操做保持空間的命令以下所示:

名稱 命令 說明
保存(Hold) h/H 將模式空間的內容複製或者追加到保持空間
取回(Get) g/G 將保持空間的內容複製或者追加到模式空間
交換(Exchange) x 交換模式空間和保持空間的內容

這幾組命令提供了保存、取回以及交換三個動做,交換命令比較容易理解,保存命令和取回命令都有大寫和小寫兩種形式,這兩種形式的區別是小寫的是將會覆蓋目的空間的內容,而大寫的是將內容追加到目的空間,追加的內容和原有的內容是以\n分隔。

基本使用

咱們隨便試試這幾個命令,假設有以下測試文本:

$ cat text
1 2 11 22 111 222

1. 首先,僅使用h/H或者g/G命令:

使用h命令:

$ sed 'h' text 1 2 11 22 111 222

使用G命令:

$ sed 'G' text 1 2 11 22 111 222

前者返回的結果正常,由於複製到保持空間的內容並無取回;後者每一行的後面都多了一個空行,緣由是每行都會從保持空間取回一行,追加(大寫的G)到模式空間的內容以後,以\n分隔。

2. 使用x命令交換空間

$ sed 'x' text 1 2 11 22 111

命令執行後,發現前面多了一個空行而且最後一行不見了。我在前面一直強調sed命令用好,要有用大腦回顧命令執行過程的能力:

* 當讀入第一行的時候,模式空間中的內容是第一行的內容,而保持空間是空的,這個時候交換兩個空間,致使模式空間爲空,保持空間爲第一行的內容,所以輸出爲空行;
* 當讀入下一行以後,模式空間爲第2行的內容,保持空間爲第一行的內容,交換後輸出第1行的內容;
* 依次讀入每一行,輸出上一行的內容;
* 直到最後一行被讀入到模式空間,交換後輸出倒數第二行的內容,而最後一行的內容並無輸出,此時命令執行結束。

深刻使用

上面的例子簡單地介紹了保持空間命令的基本使用方法,這些命令單個使用可能效果不大,可是組合起來的效果是很是好的。

1.第一個例子: 使用逗號拼接行

$ sed 'H;$!d;${x;s/^\n//;s/\n/,/g}' text 1,11,2,11,22,111,222

上面的命令執行過程是這樣的,使用H將每一行都追加到保持空間,這裏利用d命令打斷常規的命令執行流程,讓sed繼續讀入新的一行,直接到將最後一行都放到保持空間。這個時候使用x命令將保持空間的內容交換到模式空間,模式空間的內容如今是這樣的:\n1\n11\n2\n11\n22\n111\n222。替換的步驟分紅兩個,首先去掉首個回車符,而後把剩餘的回車符替換成逗號。

其實上面的過程能夠包裝成一個經常使用的函數:

$ function join_lines() > { > sed 'H;$!d;${x;s/^\n//;s/\n/,/g}' $1 > } $ join_lines text 1,11,2,11,22,111,222

進一步,咱們可讓分隔符能夠經過參數設置,另一方面刪除文件名的參數,而改爲常見的過濾器命令形式(即經過管道傳遞輸入):

$ function join_lines() > { > local delim=${1:-,} > sed 'H;$!d;${x;s/^\n//;s/\n/'$delim'/g}' > } $ cat text | join_lines ';' 1;11;2;11;22;111;222

可是若是咱們要用&做爲符號,就會出現問題:

$ cat text | join_lines '&' 1 11 2 11 22 111 222

上面並無&做爲分隔符,這是由於&是元字符,表示匹配的部分,這裏恰好是回車符\n。所以咱們須要對分隔符進行轉義:

$ function join_lines() > { > local delim=${1:-,} > sed 'H;$!d;${x;s/^\n//;s/\n/\'$delim'/g}' > } $ cat text | join_lines '&' 1&11&2&11&22&111&222

2.第二個例子:將語句中的特定單詞轉換成大寫

如今有這樣的文本,有許多相似這樣的find the Match statement語句,其中Match是語句的名稱,可是這個單詞的大小寫不統一,有些地方是小寫的,有些地方是首字符大寫,如今咱們要作的是把這個單詞統一轉換成大寫。

容易聯想到的是Sed&awk筆記之sed篇:基礎命令中介紹的y命令,利用y命令確實能夠作到小寫轉換成大寫,轉換的命令是這樣的:

y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

可是y命令是會把模式空間的全部內容都轉換,這樣不能知足咱們的需求。可是咱們能夠利用保持空間保存當前行,而後處理模式空間中的內容:

/the .* statement/{ h s/.*the \(.*\) statement.*/\1/ y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ G s/\(.*\)\n\(.*the \).*\( statement.*\)/\2\1\3/ }

老規矩一條一條過上面的命令,爲了方便說明,每一個命令解釋後我都會給出當前模式空間和保持空間的內容。

首先找到須要處理的行(假設當前行爲find the Match statement)。

Pattern space: find the Match statement

將當前行保存到保持空間:

Pattern space: find the Match statement
Hold space: find the Match statement

而後利用替換命令獲取須要處理的單詞:

Pattern space: Match
Hold space: find the Match statement

而後經過轉換命令將其轉換成大寫:

Pattern space: MATCH
Hold space: find the Match statement

如今再利用G命令將保持空間的內容追加到模式空間最後:

Pattern space: MATCH\nfind the Match statement
Hold space: find the Match statement

最後再次利用替換命令處理下:

Pattern space: find the MATCH statement
Hold space: find the Match statement

流程控制

通常狀況下,sed是將編輯命令從上到下依次應用到讀入的行上,可是像d/n/D/N命令都可以在必定程度上改變默認的執行流程,甚至利用N/D/P三個命令能夠造成一個強大的循環處理流程。除此以外,其實sed還提供了分支命令(b)和測試(test)兩個命令來控制流程,這兩個命令能夠跳轉到指定的標籤(label)位置繼續執行命令。標籤是以冒號開頭的標記,以下例中的:top標籤:

:top command1 command2 /pattern/b top command3

當執行到/pattern/b top時,若是匹配pattern,則跳轉到:top標籤所在的位置,繼續執行下一個命令command1。

若是沒有指定標籤,則將控制轉移到腳本的結尾處。也許這只是一個默認的行爲,可是有時候若是用得好也是很是有用的,例如:

/pattern/b command 1 command 2 command 3

當執行到/pattern/b時,若是匹配pattern,則跳轉到最後。這種狀況下匹配pattern的行能夠避開執行後續的命令,被排除在外。

下一個例子中,咱們利用分支命令的跳轉效果達到相似if語句的效果:

command1
/pattern/b end command2 :end command3

當執行到/pattern/b end時,若是匹配pattern,則跳轉到:end標籤所在的位置,跳過command2而不執行。

進一步地,利用兩個分支命令能夠達到if..else的分支效果:

command1
/pattern/b dothree command2 b :dothree command3

這個例子中,當執行到/pattern/b dothree時,若匹配pattern則中轉到:dothree標籤,此時執行command3;若不匹配,則執行command2,而且跳轉到最後。

上面的例子都是用到了分支命令,分支命令的跳轉是無條件的。而與之相對的是測試命令,測試命令的跳轉是有條件的,當且僅當當前行發生成功的替換時才跳轉。

爲了說明測試命令的用法,咱們用它來實現前文定義過的join_lines函數:

$ sed ':a;$!N;s/\n/,/;ta' text 1,11,2,11,22,111,222

在最前面咱們添加了一個標籤:a,而後在再最後面利用測試命令跳轉到該標籤。可能,你會以爲這裏也能夠用分支命令,可是事實上分支命令會致使死循環,由於在它裏他沒有結束的條件。

可是測試命令就不一樣了,這一點直到最後才體現出來。當最後一行被N命令讀入以後,回車替換成逗號,此時ta繼續跳轉到最開頭,由於全部行都已經被讀入,因此$!不匹配,同時模式空間中的回車已經所有被替換成逗號,因此替換也不會發生。以前咱們說過,當且僅噹噹前行發生成功的替換時測試命令才跳轉。因此此時跳轉不會發生,退出sed命令。

咱們能夠利用sedsed這個工具來驗證上面的過程,sedsed能夠用來調試sed腳本。

$ ./sedsed -d -e ':a;$!N;s/\n/,/;ta' text PATT:1$ HOLD:$ COMM::a COMM:$ !N PATT:1\n11$ HOLD:$ COMM:s/\n/,/ PATT:1,11$ HOLD:$ COMM:t a COMM:$ !N PATT:1,11\n2$ HOLD:$ ... ... COMM:$ !N PATT:1,11,2,11,22,111,222\n1111$ HOLD:$ COMM:s/\n/,/ PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:t a COMM:$ !N PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:s/\n/,/ PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:t a PATT:1,11,2,11,22,111,222,1111$ HOLD:$ 1,11,2,11,22,111,222,1111

看第27行替換命令發生的時候,此時模式空間的內容爲PATT:1,11,2,11,22,111,222,1111$,所以替換失敗,ta命令不會發生跳轉,腳本執行退出。

而若是在這裏把測試命令換成分支命令,整個執行過程就會陷入死循環了:

COMM:b a COMM:$ !N PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:s/\n/,/ PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:b a COMM:$ !N PATT:1,11,2,11,22,111,222,1111$

高級命令總結

到此爲止,全部高級命令的用法就已經介紹完了。最後一段內容因爲時間的關係,寫得比較倉促。高級命令的用法比起基礎命令相對複雜一點,並且容易出錯,須要十分當心,若是不肯定能夠用上面介紹的sedsed工具來調式下,並且便於加深各類命令行爲的理解。

相信你們確定用過grep這個命令,它能夠找出匹配某個正則表達式的行,例如查看包含"the word"的行:

$ grep "the word" filename

可是grep是針對單行做匹配的,因此若是一個短句跨越了兩行就沒法匹配。這就給咱們出了一個題目,如何用sed模仿grep的行爲,而且支持跨行短句的匹配呢?

當單詞僅出如今一行時很簡單,用普通的正則就能夠匹配,這裏咱們用到分支命令,當匹配時則跳轉到最後:

/the word/b

當單詞跨越兩行,容易想到用N命令將下一行讀取到模式空間再作處理。例如咱們一樣要查找短句"the word",如今的文本是這樣的:

$ cat text
we want to find the phrase the
word, but it appears across two lines.

當用N玲讀入下一行時,模式空間的內容以下所示:

Pattern space: we want to find the phrase the\nword, but it appears across two lines.

所以,須要將回車符號刪除,而後再做匹配。

$!N
s/ *\n/ /
/the word/b

但是這裏會有一個問題,若是短句剛好在讀入的第二行的話,雖然匹配,可是會打印出模式空間中的全部內容,這樣不符合grep的行爲,只顯示包含短句的那一行。因此這裏要再加一個處理,將模式空間的第一行內容刪除,再在第二行中匹配。可是在此以前,首先要保存模式空間的內容,不然可沒有後悔藥可吃。

h命令能夠將模式空間的內容保存到保持空間,而後利用替換命令將模式空間的第一行內容清除,而後再做匹配處理:

$!N
h
s/.*\n//
/the word/b

若是不匹配,則短句可能確實是跨越兩行,這時候咱們首先用g命令將以前保存的內容從保持空間取回到模式空間,替換掉回車後再進行匹配:

g
s/ *\n/ /
/the word/b

這裏若是匹配則直接跳轉到最後,而且輸出的內容是替換後的內容。

可是咱們要輸出的是匹配的原文,而原文如今在保持空間還有一份拷貝,所以當匹配時,須要將原文從保持空間取回:

g
s/ *\n/ /
/the word/{
g
b
}

一樣地,咱們要考慮不匹配的狀況,不匹配的時候也要將會原文從保持空間取回,而且將模式空間的第一行刪除,繼續處理下一行:

g
s/ *\n/ /
/the word/{
g
b
}
g
D

將全部的sed腳本合在一塊兒,假設咱們將如下內容保存到phrase.sed文件中:

/the word/b
$!N
h
s/.*\n//
/the word/b
g
s/ *\n//
/the word/{
g
b
}
g
D

接下來,咱們用一段文原本測試下以上的腳本是否正確:

$ cat text
We will use phrase.sed to print any line which contains the word. Or if the word appears across two lines, like below: It will print this line, because the word appears across two lines. You can run sed -f phrase.sed text to test this.

執行命令以下所示:

$ sed -f phrase.sed text the word. Or if the word appears across two lines, like It will print this line, because the word appears across two lines.

上面的命令中的"the word"其實能夠是一個變量,這樣咱們就能夠將這個功能寫成一個腳本或者函數,用在更多地方:

$ cat phrase.sh #! /bin/sh # phrase -- search for words across lines # $1 = search string; remaining args = filenames search=$1 for file in ${@:2}; do sed "/$search/b \$!N h s/.*\n// /$search/b g s/ *\n/ / /$search/{ g b } g D" $file done $ chmod +x phrase.sh $ ./phrase.sh 'the word' text the word. Or if the word appears across two lines, like It will print this line, because the word appears across two lines.

這只是一個開頭,或者你也能夠在此基礎上擴展更多的功能。sed的命令從單個看來並沒是很複雜,可是要用得好就有點難度了,因此須要多多實踐,多多思考,這一點跟正則表達式的學習是同樣的。若是以爲沒有現成的學習環境,sed1line是一個不錯的開始。

相關文章
相關標籤/搜索