通用線程 -- sed 實例 2

sed 是十分強大和小巧的文本流編輯器。在本文章系列的第二篇中,Daniel Robbins 爲您演示如何使用 sed 來執行字符串替換、創建更大的 sed 腳本以及如何使用 sed 的附加、插入和更改行命令。

sed 是很有用(但常被遺忘)的 UNIX 流編輯器。在以批處理方式編輯文件或以有效方式創建 shell 腳本來修改現有文件方面,它是十分理想的工具。本文是前一篇介紹 sed 文章的續篇。

替換!
讓我們看一下 sed 最有用的命令之一,替換命令。使用該命令,可以將特定字符串或匹配的規則表達式用另一個字符串替換。下面是該命令最基本用法的示例:

 $ sed -e 's/foo/bar/' myfile.txt 

上面的命令將 myfile.txt 中每行第一次出現的 'foo'(如果有的話)用字符串 'bar' 替換,然後將該文件內容輸出到標準輸出。請注意,我說的是每行第一次出現,儘管這通常不是您想要的。在進行字符串替換時,通常想執行全局替換。也就是說,要替換每行中的所有出現,如下所示:

$ sed -e 's/foo/bar/g' myfile.txt 

在最後一個斜槓之後附加的 'g' 選項告訴 sed 執行全局替換。

關於 's///' 替換命令,還有其它幾件要了解的事。首先,它是一個命令,並且只是一個命令,在所有上例中都沒有指定地址。這意味着,'s///' 還可以與地址一起使用來控制要將命令應用到哪些行,如下所示:

 $ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt 

上例將導致用短語 'entrapment' 替換所有出現的短語 'enchantment',但是隻在第一到第十行(包括這兩行)上這樣做。

 $ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt 

該例將用 'mountains' 替換 'hills',但是,只從空行開始,到以三個字符 'END' 開始的行結束(包括這兩行)的文本塊上這樣做。

關於 's///' 命令的另一個妙處是 '/' 分隔符有許多替換選項。如果正在執行字符串替換,並且規則表達式或替換字符串中有許多斜槓,則可以通過在 's' 之後指定一個不同的字符來更改分隔符。例如,下例將把所有出現的 /usr/local 替換成 /usr:

 $ sed -e 's:/usr/local:/usr:g' mylist.txt 

在該例中,使用冒號作爲分隔符。如果需要在規則表達式中指定分隔符字符,可以在它前面加入反斜槓。

規則表達式混亂
目前爲止,我們只執行了簡單的字符串替換。雖然這很方便,但是我們還可以匹配規則表達式。例如,以下 sed 命令將匹配從 '<' 開始、到 '>' 結束、並且在其中包含任意數量字符的短語。下例將刪除該短語(用空字符串替換):

 $ sed -e 's/<.*>//g' myfile.html 

這是要從文件除去 HTML 標記的第一個很好的 sed 腳本嘗試,但是由於規則表達式的特有規則,它不會很好地工作。原因何在?當 sed 試圖在行中匹配規則表達式時,它要在行中查找最長的匹配。在我的中,這不成問題,因爲我們使用的是 'd' 和 'p' 命令,這些命令總要刪除或打印整行。但是,在使用 's///' 命令時,確實有很大不同,因爲規則表達式匹配的整個部分將被目標字符串替換,或者,在本例中,被刪除。這意味着,上例將把下行:

 <b>This</b> is what <b>I</b> meant. 

變成:

 meant. 

我們要的不是這個,而是:

 This is what I meant. 

幸運的是,有一種簡便方法來糾正該問題。我們不輸入“'<' 字符後面跟有一些字符並以 '>' 字符結束”的規則表達式,而只需輸入一個“'<' 字符後面跟有任意數量非 '>' 字符並以 '>' 字符結束”的規則表達式。這將與最短、而不是最長的可能性匹配。新命令如下:

 $ sed -e 's/<[^>]*>//g' myfile.html 

在上例中,'[^>]' 指定“非 '>'”字符,其後的 '*' 完成該表達式以表示“零或多個非 '>' 字符”。對幾個 html 文件測試該命令,將它們管道輸出到 "more",然後仔細查看其結果。

更多字符匹配
'[ ]' 規則表達式語法還有一些附加選項。要指定字符範圍,只要字符不在第一個或最後一個位置,就可以使用 '-',如下所示:

 '[a-x]*' 

這將匹配零或多個全部爲 'a'、'b'、'c'...'v'、'w'、'x' 的字符。另外,可以使用 '[:space:]' 字符類來匹配空格。以下是可用字符類的相當完整的列表:

字符類 描述
[:alnum:] 字母數字 [a-z A-Z 0-9]
[:alpha:] 字母 [a-z A-Z]
[:blank:] 空格或製表鍵
[:cntrl:] 任何控制字符
[:digit:] 數字 [0-9]
[:graph:] 任何可視字符(無空格)
[:lower:] 小寫 [a-z]
[:print:] 非控制字符
[:punct:] 標點字符
[:space:] 空格
[:upper:] 大寫 [A-Z]
[:xdigit:] 十六進制數字 [0-9 a-f A-F]

儘可能使用字符類是很有利的,因爲它們可以更好地適應非英語 locale(包括某些必需的重音字符等等).

高級替換功能
我們已經看到如何執行簡單甚至有些複雜的直接替換,但是 sed 還可以做更多的事。實際上可以引用匹配規則表達式的部分或全部,並使用這些部分來構造替換字符串。作爲示例,假設您正在回覆一條消息。下例將在每一行前面加上短語 "ralph said: ":

 $ sed -e 's/.*/ralph said: &/' origmsg.txt 

輸出如下:

 ralph said: Hiya Jim, ralph said: ralph said: 
 I sure like this sed stuff! ralph said: 

該例的替換字符串中使用了 '&' 字符,該字符告訴 sed 插入整個匹配的規則表達式。因此,可以將與 '.*' 匹配的任何內容(行中的零或多個字符的最大組或整行)插入到替換字符串中的任何位置,甚至多次插入。這非常好,但 sed 甚至更強大。

那些極好的帶反斜槓的圓括號
's///' 命令甚至比 '&' 更好,它允許我們在規則表達式中定義區域,然後可以在替換字符串中引用這些特定區域。作爲示例,假設有一個包含以下文本的文件:

 foo bar oni eeny meeny miny larry curly moe jimmy the weasel 

現在假設要編寫一個 sed 腳本,該腳本將把 "eeny meeny miny" 替換成 "Victor eeny-meeny Von miny" 等等。要這樣做,首先要編寫一個由空格分隔並與三個字符串匹配的規則表達式。

 '.* .* .*' 

現在,將在其中每個感興趣的區域兩邊插入帶反斜槓的圓括號來定義區域:

 '/(.*/) /(.*/) /(.*/)' 

除了要定義三個可在替換字符串中引用的邏輯區域以外,該規則表達式的工作原理將與第一個規則表達式相同。下面是最終腳本:

 $ sed -e 's//(.*/) /(.*/) /(.*/)/Victor /1-/2 Von /3/' myfile.txt 

如您所見,通過輸入 '/x'(其中,x 是從 1 開始的區域號)來引用每個由圓括號定界的區域。輸入如下:

 Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel 

隨着對 sed 越來越熟悉,您可以花最小力氣來進行相當強大的文本處理。您可能想如何使用熟悉的腳本語言來處理這種問題 -- 能用一行代碼輕易實現這樣的解決方案嗎?

組合使用
在開始創建更復雜的 sed 腳本時,需要有輸入多個命令的能力。有幾種方法這樣做。首先,可以在命令之間使用分號。例如,以下命令系列使用 '=' 命令和 'p' 命令,'=' 命令告訴 sed 打印行號,'p' 命令明確告訴 sed 打印該行(因爲處於 '-n' 模式)。

 $ sed -n -e '=;p' myfile.txt 

無論什麼時候指定了兩個或更多命令,都按順序將每個命令應用到文件的每一行。在上例中,首先將 '=' 命令應用到第 1 行,然後應用 'p' 命令。接着,sed 繼續處理第 2 行,並重復該過程。雖然分號很方便,但是在某些場合下,它不能正常工作。另一種替換方法是使用兩個 -e 選項來指定兩個不同的命令:

 $ sed -n -e '=' -e 'p' myfile.txt 

然而,在使用更爲複雜的附加和插入命令時,甚至多個 '-e' 選項也不能幫我們的忙。對於複雜的多行腳本,最好的方法是將命令放入一個單獨的文件中。然後,用 -f 選項引用該腳本文件:

 $ sed -n -f mycommands.sed myfile.txt 

這種方法雖然可能不太方便,但總是管用。

一個地址的多個命令
有時,可能要指定應用到一個地址的多個命令。這在執行許多 's///' 以變換源文件中的字和語法時特別方便。要對一個地址執行多個命令,可在文件中輸入 sed 命令,然後使用 '{ }' 字符將這些命令分組,如下所示:

 1,20{ 	s/[Ll]inux/GNU//Linux/g 	s/samba/Samba/g 	s/posix/POSIX/g } 

上例將把三個替換命令應用到第 1 行到第 20 行(包括這兩行)。還可以使用規則表達式地址或者二者的組合:

 1,/^END/{         s/[Ll]inux/GNU//Linux/g         s/samba/Samba/g         s/posix/POSIX/g 	p } 

該例將把 '{ }' 之間的所有命令應用到從第 1 行開始,到以字母 "END" 開始的行結束(如果在源文件中沒發現 "END",則到文件結束)的所有行。

附加、插入和更改行
既然在單獨的文件中編寫 sed 腳本,我們可以利用附加、插入和更改行命令。這些命令將在當前行之後插入一行,在當前行之前插入一行,或者替換模式空間中的當前行。它們也可以用來將多行插入到輸出。插入行命令用法如下:

 i/ This line will be inserted before each line 

如果不爲該命令指定地址,那麼它將應用到每一行,併產生如下的輸出:

 This line will be inserted before each line line 1 here 
 This line will be inserted before each line line 2 here 
 This line will be inserted before each line line 3 here 
 This line will be inserted before each line line 4 here 

如果要在當前行之前插入多行,可以通過在前一行之後附加一個反斜槓來添加附加行,如下所示:

 i/ insert this line/ and this one/ and this one/ and, uh, this one too. 

附加命令的用法與之類似,但是它將把一行或多行插入到模式空間中的當前行之後。其用法如下:

 a/ insert this line after each line.  Thanks! :) 

另一方面,“更改行”命令將實際替換模式空間中的當前行,其用法如下:

 c/ You're history, original line! Muhahaha! 

因爲附加、插入和更改行命令需要在多行輸入,所以將把它們輸入到一個文本 sed 腳本中,然後通過使用 '-f' 選項告訴 sed 執行它們。使用其它方法將命令傳遞給 sed 會出現問題。

下一篇
在下一篇、也是本 sed 系列的最後一篇文章中,我將爲您演示許多使用 sed 來完成不同類型任務的極佳實例。我將不僅爲您顯示腳本做些什麼,還顯示爲什麼那樣做。完成之後,您將掌握更多有關如何在不同項目中使用 sed 的極佳知識。到時候見!

參考資料

閱讀 developerWorks 上 Daniel 的其它 sed 文章:通用線程:sed 實例,和。 查看 Eric Pement 極佳的 。 可以在 找到 sed 3.02 資源。 將在 找到很好的新的 sed 3.02.80。 另外,Eric Pement 還有一些方便的 ,任何有抱負的 sed 高手都應該看一下。 如果想看好的老式書籍,O'Reilly 的 將是極佳選擇。 可能想閱讀 (大概 1978!)。 閱讀 Felix von Leitner 短小的 。 在 中複習,發現和修改這個免費 dW 獨家教程文本中的模式。

關於作者
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的總裁兼 CEO,Gentoo Linux(用於 PC 的高級 Linux)和 Portage 系統(Linux 的下一代端口系統)的創始人。他還是 Macmillan 書籍 Caldera OpenLinux UnleashedSuSE Linux UnleashedSamba Unleashed 的作者。Daniel 自小學二年級起就與計算機結下不解之緣,那時他首先接觸的是 Logo 程序語言,並沉溺於 Pac-Man 遊戲中。這也許就是他至今仍擔任 SONY Electronic Publishing/Psygnosis 的首席圖形設計師的原因所在。Daniel 喜歡與妻子 Mary 和新出生的女兒 Hadassah 一起共度時光。可通過 [email protected] 與 Daniel Robbins 聯繫。


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