歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> 關於Unix

通用線程 -- sed 實例,第 1 部分

2001 年 10 月 在本文章系列中,Daniel Robbins 將為您演示如何使用功能十分強大(但常被遺忘)的 UNIX 流編輯器 sed。sed 是用批處理方式編輯文件或以十分有效的方式創建 shell 腳本以修改現有文件的理想工具。 挑選編輯器 在 UNIX 世界中有很多文本編輯器可 2001 年 10 月
在本文章系列中,Daniel Robbins 將為您演示如何使用功能十分強大(但常被遺忘)的 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 新手,那麼理解它們是十分重要的。下面是第一個示例:

clearcase/" target="_blank" >cccccc" border="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 文件第一行的示例:

$ 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 和規則表達式資源。

Copyright © Linux教程網 All Rights Reserved