3. sed

sed意為流編輯器(Stream Editor),在Shell腳本和Makefile中作為過濾器使用非常普遍,也就是把前一個程序的輸出引入sed的輸入,經過一系列編輯命令轉換為另一種格式輸出。sedvi都源於早期UNIX的ed工具,所以很多sed命令和vi的末行命令是相同的。

sed命令行的基本格式為

sed option 'script' file1 file2 ...
sed option -f scriptfile file1 file2 ...

sed處理的檔案既可以由標準輸入重定向得到,也可以當命令行參數傳入,命令行參數可以一次傳入多個檔案,sed會依次處理。sed的編輯命令可以直接當命令行參數傳入,也可以寫成一個腳本檔案然後用-f參數指定,編輯命令的格式為

/pattern/action

其中pattern是正則表達式,action是編輯操作。sed程序一行一行讀出待處理檔案,如果某一行與pattern匹配,則執行相應的action,如果一條命令沒有pattern而只有action,這個action將作用於待處理檔案的每一行。

表 32.5. 常用的sed命令

/pattern/p打印匹配pattern的行
/pattern/d刪除匹配pattern的行
/pattern/s/pattern1/pattern2/查找符合pattern的行,將該行第一個匹配pattern1的字元串替換為pattern2
/pattern/s/pattern1/pattern2/g查找符合pattern的行,將該行所有匹配pattern1的字元串替換為pattern2

使用p命令需要注意,sed是把待處理檔案的內容連同處理結果一起輸出到標準輸出的,因此p命令表示除了把檔案內容打印出來之外還額外打印一遍匹配pattern的行。比如一個檔案testfile的內容是

123
abc
456

打印其中包含abc的行

$ sed '/abc/p' testfile
123
abc
abc
456

要想只輸出處理結果,應加上-n選項,這種用法相當於grep命令

$ sed -n '/abc/p' testfile
abc

使用d命令就不需要-n參數了,比如刪除含有abc的行

$ sed '/abc/d' testfile
123
456

注意,sed命令不會修改原檔案,刪除命令只表示某些行不打印輸出,而不是從原檔案中刪去。

使用查找替換命令時,可以把匹配pattern1的字元串複製到pattern2中,比如:

$ sed 's/bc/-&-/' testfile
123
a-bc-
456

pattern2中的&表示原檔案的當前行中與pattern1相匹配的字元串,再比如:

$ sed 's/\([0-9]\)\([0-9]\)/-\1-~\2~/' testfile
-1-~2~3
abc
-4-~5~6

pattern2中的\1表示與pattern1的第一個()括號相匹配的內容,\2表示與pattern1的第二個()括號相匹配的內容。sed預設使用Basic正則表達式規範,如果指定了-r選項則使用Extended規範,那麼()括號就不必轉義了。

如果testfile的內容是

<html><head><title>Hello World</title>
<body>Welcome to the world of regexp!</body></html>

現在要去掉所有的HTML標籤,使輸出結果為

Hello World
Welcome to the world of regexp!

怎麼做呢?如果用下面的命令

$ sed 's/<.*>//g' testfile


結果是兩個空行,把所有字元都過濾掉了。這是因為,正則表達式中的數量限定符會匹配儘可能長的字元串,這稱為貪心的(Greedy)[39]。比如sed在處理第一行時,<.*>匹配的並不是<html><head>這樣的標籤,而是

<html><head><title>Hello World</title>

這樣一整行,因為這一行開頭是<,中間是若干個任意字元,末尾是>。那麼這條命令怎麼改才對呢?留給讀者思考。



[39] 有些正則表達式規範支持Non-greedy的數量限定符,匹配儘可能短的字元串,例如在Python中*?*一樣表示0個或任意多個,但前者是Non-greedy的。