通用線程 -- sed 實例 1

挑選編輯器
在 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 之後,我就完全準備好了。

正確的 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 新手,那麼理解它們是十分重要的。下面是第一個示例:

$ 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 文件第一行的示例:

$ sed -e '1d' /etc/services | more


如您所見,除了前面有 '1' 之外,該命令與第一個 'd' 命令十分類似。如果您猜到 '1' 指的是第一行,那您就猜對了。與第一個示例中只使用 'd' 不同的是,這一次使用的 'd' 前面有一個可選的數字地址。通過使用地址,可以告訴 sed 只對某一或某些特定行進行編輯。

地址範圍
現在,讓我們看一下如何指定地址範圍。在本例中,sed 將刪除輸出的第 1 到 10 行:

$ sed -e '1,10d' /etc/services | more


當用逗號將兩個地址分開時,sed 將把後面的命令應用到從第一個地址開始、到第二個地址結束的範圍。在本例中,將 'd' 命令應用到第 1 到 10 行(包括這兩行)。所有其它行都被忽略。

帶規則表達式的地址
現在演示一個更有用的示例。假設要查看 /etc/services 文件的內容,但是對查看其中包括的註釋部分不感興趣。如您所知,可以通過以 '#' 字符開頭的行在 /etc/services 文件中放置註釋。爲了避免註釋,我們希望 sed 刪除以 '#' 開始的行。以下是具體做法:

$ sed -e '/^#/d' /etc/services | more


試一下該例,看看發生了什麼。您將注意到,sed 成功完成了預期任務。現在,讓我們分析發生的情況。

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

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

規則表達式複習
可以使用規則表達式來表示可能會在文本中發現的模式。您在 shell 命令行中用過 '*' 字符嗎?這種用法與規則表達式類似,但並不相同。下面是可以在規則表達式中使用的特殊字符:

字符 描述
與行首匹配
與行末尾匹配
與任一個字符匹配
將與前一個字符的零或多個出現匹配
[ ] 與 [ ] 之內的所有字符匹配

感受規則表達式的最好方法可能是看幾個示例。所有這些示例都將被 sed 作爲合法地址接受,這些地址出現在命令的左邊。下面是幾個示例:

規則
表達式
描述
/./ 將與包含至少一個字符的任何行匹配
/../ 將與包含至少兩個字符的任何行匹配
/^#/ 將與以 '#' 開始的任何行匹配
/^$/ 將與所有空行匹配
/}^/ 將與以 '}'(無空格)結束的任何行匹配
/} *^/ 將與以 '}' 後面跟有或多個空格結束的任何行匹配
/[abc]/ 將與包含小寫 'a'、'b' 或 'c' 的任何行匹配
/^[abc]/ 將與以 'a'、'b' 或 'c'開始的任何行匹配

在這些示例中,鼓勵您嘗試幾個。花一些時間熟悉規則表達式,然後嘗試幾個自己創建的規則表達式。可以如下使用 regexp:

$ sed -e '/regexp/d' /path/to/my/test/file | more


這將導致 sed 刪除任何匹配的行。然而,通過告訴 sed打印 regexp 匹配並刪除不匹配的內容,而不是與之相反的方法,會更有利於熟悉規則表達式。可以用以下命令這樣做:

$ sed -n -e '/regexp/p' /path/to/my/test/file | more


請注意新的 '-n' 選項,該選項告訴 sed 除非明確要求打印模式空間,否則不這樣做。您還會注意到,我們用 'p' 命令替換了 'd' 命令,如您所猜想的那樣,這明確要求 sed 打印模式空間。就這樣,將只打印匹配部分。

有關地址的更多內容
目前爲止,我們已經看到了行地址、行範圍地址和 regexp 地址。但是,還有更多的可能。我們可以指定兩個用逗號分開的規則表達式,sed 將與所有從匹配第一個規則表達式的第一行開始,到匹配第二個規則表達式的行結束(包括該行)的所有行匹配。例如,以下命令將打印從包含 "BEGIN" 的行開始,並且以包含 "END" 的行結束的文本塊:

$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more


如果沒發現 "BEGIN",那麼將不打印數據。如果發現了 "BEGIN",但是在這之後的所有行中都沒發現 "END",那麼將打印所有後續行。發生這種情況是因爲 sed 面向流的特性 -- 它不知道是否會出現 "END"。

C 源代碼示例
如果只要打印 C 源文件中的 main() 函數,可輸入:

$ 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()" 和結束的 '}'。

下一篇
既然已經觸及了基本知識,我們將在後兩篇文章中加快步伐。如果想看一些更豐富的 sed 資料,請耐心一些 -- 馬上就有!同時,您可能想查看下列 sed 和規則表達式資源。

參考資料
有關 sed



關於規則表達式



關於作者
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 聯繫。

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