類Unix流編輯器sed線上極速入門 第一部分

helloxchen發表於2010-10-21
類Unix流編輯器sed線上極速入門 第一部分

本文章系列包括三部分,為您演示如何使用功能十分強大(但常被遺忘)的 UNIX 流編輯器 sed。sed 是用批處理方式編輯檔案或以十分有效的方式建立 shell 指令碼以修改現有檔案的理想工具。

挑選編輯器

在 UNIX 世界中有很多文字編輯器可供我們選擇。思考一下 -- vi、emacs 和 jed 以及很多其它工具都會浮現在腦海中。我們都有自己已逐漸瞭解並且喜愛的編輯器(以及我們喜愛的組合鍵)。有了可信賴的編輯器,我們可以輕鬆處理任何數量與 UNIX 有關的管理或程式設計任務。

雖然互動式編輯器很棒,但卻有其限制。儘管其互動式特性可以成為強項,但也有其不足之處。考慮一下需要對一組檔案執行類似更改的情形。您可能會本能地執行自己所喜愛的編輯器,然後手工執行一組煩瑣、重複和耗時的編輯任務。然而,有一種更好的方法。

進入 sed

如果可以使編輯檔案的過程自動化,以便用“批處理”方式編輯檔案,甚至編寫可以對現有檔案進行復雜更改的指令碼,那將太好了。幸運的是,對於這種情況,有一種更好的方法 -- 這種更好的方法稱為 "sed"。

sed 是一種幾乎包括在所有 UNIX 平臺(包括 Linux)的輕量級流編輯器。sed 有許多很好的特性。首先,它相當小巧,通常要比您所喜愛的指令碼語言小很多倍。其次,因為 sed 是一種 流編輯器,所以,它可以對從如管道這樣的標準輸入接收的資料進行編輯。因此,無需將要編輯的資料儲存在磁碟上的檔案中。因為可以輕易將資料管道輸出到 sed,所以,將 sed 用作強大的 shell 指令碼中長而複雜的管道很容易。試一下用您所喜愛的編輯器去那樣做。

GNU sed

對 Linux 使用者來說幸運的是,最好的 sed 版本之一恰好是 GNU sed,其當前版本是 3.02。每一個 Linux 發行版都有(或至少應該有)GNU sed。GNU sed 之所以流行不僅因為可以自由分發其原始碼,還因為它恰巧有許多對 POSIX sed 標準便利、省時的擴充套件。另外,GNU 沒有 sed 早期專門版本的很多限制,如行長度限制 -- GNU 可以輕鬆處理任意長度的行。

最新的 GNU sed

在研究這篇文章之時我注意到:幾個線上 sed 愛好者提到 GNU sed 3.02a。奇怪的是,在 ftp.gnu.org上找不到 sed 3.02a,所以,我只得在別處尋找。我在 alpha.gnu.org的 /pub/sed 中找到了它。於是我高興地將其下載、編譯然後安裝,而幾分鐘後我發現最新的 sed 版本卻是 3.02.80 -- 可在 alpha.gnu.org上 3.02a 原始碼旁邊找到其原始碼。安裝完 GNU sed 3.02.80 之後,我就完全準備好了。

alpha.gnu.org
alpha.gnu.org是新的和實驗性 GNU 原始碼的網站。然而,您還會在那裡發現許多優秀、穩定的原始碼。出於某種原因,不是許多 GNU 開發人員忘記將穩定的原始碼移至 ftp.gnu.org,就是它們的 "beta" 期間格外長(2 年!)。例如,sed 3.02a 已有兩年,甚至 3.02.80 也有一年,但它們仍不能(在 2000 年 8 月寫本文章時)在 ftp.gnu.org 上獲得。


正確的 sed

在本系列中,將使用 GNU sed 3.02.80。在即將出現的本系列後續部分中,某些(但非常少)最高階的示例將不能在 GNU sed 3.02 或 3.02a 中使用。如果您使用的不是 GNU sed,那麼結果可能會不同。現在為什麼不花些時間安裝 GNU sed 3.02.80 呢?那樣,不僅可以為本系列的餘下部分作好準備,而且還可以使用可能是目前最好的 sed。

sed 示例

sed 透過對輸入資料執行任意數量使用者指定的編輯操作(“命令”)來工作。sed 是基於行的,因此按順序對每一行執行命令。然後,sed 將其結果寫入標準輸出 (stdout),它不修改任何輸入檔案。

讓我們看一些示例。頭幾個會有些奇怪,因為我要用它們演示 sed 如何工作,而不是執行任何有用的任務。然而,如果您是 sed 新手,那麼理解它們是十分重要的。下面是第一個示例:
  1. $ sed -e 'd' /etc/services
複製程式碼
如果輸入該命令,將得不到任何輸出。那麼,發生了什麼?在該例中,用一個編輯命令 'd' 呼叫 sed。sed 開啟 /etc/services 檔案,將一行讀入其模式緩衝區,執行編輯命令(“刪除行”),然後列印模式緩衝區(緩衝區已為空)。然後,它對後面的每一行重複這些步驟。這不會產生輸出,因為 "d" 命令除去了模式緩衝區中的每一行!

在該例中,還有幾件事要注意。首先,根本沒有修改 /etc/services。這還是因為 sed 只讀取在命令列指定的檔案,將其用作輸入 -- 它不試圖修改該檔案。第二件要注意的事是 sed 是面向行的。'd' 命令不是簡單地告訴 sed 一下子刪除所有輸入資料。相反,sed 逐行將 /etc/services 的每一行讀入其稱為模式緩衝區的內部緩衝區。一旦將一行讀入模式緩衝區,它就執行 'd' 命令,然後列印模式緩衝區的內容(在本例中沒有內容)。我將在後面為您演示如何使用地址範圍來控制將命令應用到哪些行 -- 但是,如果不使用地址,命令將應用到 所有行。

第三件要注意的事是括起 'd' 命令的單引號的用法。養成使用單引號來括起 sed 命令的習慣是個好注意,這樣可以禁用 shell 擴充套件。

另一個 sed 示例

下面是使用 sed 從輸出流除去 /etc/services 檔案第一行的示例:
  1. $ sed -e '1d' /etc/services | more
複製程式碼
如您所見,除了前面有 '1' 之外,該命令與第一個 'd' 命令十分類似。如果您猜到 '1' 指的是第一行,那您就猜對了。與第一個示例中只使用 'd' 不同的是,這一次使用的 'd' 前面有一個可選的數字地址。透過使用地址,可以告訴 sed 只對某一或某些特定行進行編輯。

地址範圍

現在,讓我們看一下如何指定地址 範圍。在本例中,sed 將刪除輸出的第 1 到 10 行:
  1. $ sed -e '1,10d' /etc/services | more
複製程式碼
當用逗號將兩個地址分開時,sed 將把後面的命令應用到從第一個地址開始、到第二個地址結束的範圍。在本例中,將 'd' 命令應用到第 1 到 10 行(包括這兩行)。所有其它行都被忽略。

帶規則表示式的地址

現在演示一個更有用的示例。假設要檢視 /etc/services 檔案的內容,但是對檢視其中包括的註釋部分不感興趣。如您所知,可以透過以 '#' 字元開頭的行在 /etc/services 檔案中放置註釋。為了避免註釋,我們希望 sed 刪除以 '#' 開始的行。以下是具體做法:
  1. $ sed -e '/^#/d' /etc/services | more
複製程式碼
試一下該例,看看發生了什麼。您將注意到,sed 成功完成了預期任務。現在,讓我們分析發生的情況。

要理解 '/^#/d' 命令,首先需要對其剖析。首先,讓我們除去 'd' -- 這是我們前面所使用的同一個刪除行命令。新增加的是 '/^#/' 部分,它是一種新的 規則表示式地址。規則表示式地址總是由斜槓括起。它們指定一種 模式,緊跟在規則表示式地址之後的命令將僅適用於正好與該特定模式匹配的行。

因此,'/^#/' 是一個規則表示式。但是,它做些什麼呢?很明顯,現在該複習規則表示式了。

規則表示式複習

可以使用規則表示式來表示可能會在文字中發現的模式。您在 shell 命令列中用過 '*' 字元嗎?這種用法與規則表示式類似,但並不相同。下面是可以在規則表示式中使用的特殊字元:
字元 描述
^ 與行首匹配
$ 與行末尾匹配
w 與任一個字元匹配
* 將與 前一個字元的零或多個出現匹配
[ ] 與 [ ] 之內的所有字元匹配

感受規則表示式的最好方法可能是看幾個示例。所有這些示例都將被 sed 作為合法地址接受,這些地址出現在命令的左邊。下面是幾個示例:
規則表示式 描述
/./ 將與包含至少一個字元的任何行匹配
/../ 將與包含至少兩個字元的任何行匹配
/^#/ 將與以 '#' 開始的任何行匹配
/^$/ 將與所有空行匹配
/}^/ 將與以 '}'(無空格)結束的任何行匹配
/} *^/ 將與以 '}' 後面跟有 零或多個空格結束的任何行匹配
/[abc]/ 將與包含小寫 'a'、'b' 或 'c' 的任何行匹配
/^[abc]/ 將與以 'a'、'b' 或 'c' 開始的任何行匹配

在這些示例中,鼓勵您嘗試幾個。花一些時間熟悉規則表示式,然後嘗試幾個自己建立的規則表示式。可以如下使用 regexp:
  1. $ sed -e '/regexp/d' /path/to/my/test/file | more
複製程式碼
這將導致 sed 刪除任何匹配的行。然而,透過告訴 sed 列印regexp 匹配並刪除不匹配的內容,而不是與之相反的方法,會更有利於熟悉規則表示式。可以用以下命令這樣做:
  1. $ sed -n -e '/regexp/p' /path/to/my/test/file | more
複製程式碼
請注意新的 '-n' 選項,該選項告訴 sed 除非明確要求列印模式空間,否則不這樣做。您還會注意到,我們用 'p' 命令替換了 'd' 命令,如您所猜想的那樣,這明確要求 sed 列印模式空間。就這樣,將只列印匹配部分。

有關地址的更多內容

目前為止,我們已經看到了行地址、行範圍地址和 regexp 地址。但是,還有更多的可能。我們可以指定兩個用逗號分開的規則表示式,sed 將與所有從匹配第一個規則表示式的第一行開始,到匹配第二個規則表示式的行結束(包括該行)的所有行匹配。例如,以下命令將列印從包含 "BEGIN" 的行開始,並且以包含 "END" 的行結束的文字塊:
  1. $ sed -n -e '/BEGIN/,/END/p' /my/test/file | more
複製程式碼
如果沒發現 "BEGIN",那麼將不列印資料。如果發現了 "BEGIN",但是在這之後的所有行中都沒發現 "END",那麼將列印所有後續行。發生這種情況是因為 sed 面向流的特性 -- 它不知道是否會出現 "END"。

C 原始碼示例

如果只要列印 C 原始檔中的 main() 函式,可輸入:
  1. $ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more
複製程式碼
該命令有兩個規則表示式 '/main[[:space:]]*(/' 和 '/^}/',以及一個命令 'p'。第一個規則表示式將與後面依次跟有任意數量的空格或製表鍵以及開始圓括號的字串 "main" 匹配。這應該與一般 ANSI C main() 宣告的開始匹配。

在這個特別的規則表示式中,出現了 '[[:space:]]' 字元類。這只是一個特殊的關鍵字,它告訴 sed 與 TAB 或空格匹配。如果願意的話,可以不輸入 '[[:space:]]',而輸入 '[',然後是空格字母,然後是 -V,然後再輸入製表鍵字母和 ']' -- Control-V 告訴 bash 要插入“真正”的製表鍵,而不是執行命令擴充套件。使用 '[[:space:]]' 命令類(特別是在指令碼中)會更清楚。

好,現在看一下第二個 regexp。'/^}' 將與任何出現在新行行首的 '}' 字元匹配。如果程式碼的格式很好,那麼這將與 main() 函式的結束花括號匹配。如果格式不好,則不會正確匹配 -- 這是執行模式匹配任務的一件棘手之事。

因為是處於 '-n' 安靜方式,所以 'p' 命令還是完成其慣有任務,即明確告訴 sed 列印該行。試著對 C 原始檔執行該命令 -- 它應該輸出整個 main() { } 塊,包括開始的 "main()" 和結束的 '}'。
[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040128/,如需轉載,請註明出處,否則將追究法律責任。

相關文章