weidagang2046的專欄

          物格而后知致
          隨筆 - 8, 文章 - 409, 評(píng)論 - 101, 引用 - 0
          數(shù)據(jù)加載中……

          sed

          在本文章系列中,Daniel Robbins 將為您演示如何使用功能十分強(qiáng)大(但常被遺忘)的 UNIX 流編輯器 sed。sed 是用批處理方式編輯文件或以十分有效的方式創(chuàng)建 shell 腳本以修改現(xiàn)有文件的理想工具。

          挑選編輯器
          在 UNIX 世界中有很多文本編輯器可供我們選擇。思考一下 -- vi、emacs 和 jed 以及很多其它工具都會(huì)浮現(xiàn)在腦海中。我們都有自己已逐漸了解并且喜愛的編輯器(以及我們喜愛的組合鍵)。有了可信賴的編輯器,我們可以輕松處理任何數(shù)量與 UNIX 有關(guān)的管理或編程任務(wù)。

          雖然交互式編輯器很棒,但卻有其限制。盡管其交互式特性可以成為強(qiáng)項(xiàng),但也有其不足之處??紤]一下需要對(duì)一組文件執(zhí)行類似更改的情形。您可能會(huì)本能地運(yùn)行自己所喜愛的編輯器,然后手工執(zhí)行一組煩瑣、重復(fù)和耗時(shí)的編輯任務(wù)。然而,有一種更好的方法。

          進(jìn)入 sed
          如果可以使編輯文件的過程自動(dòng)化,以便用“批處理”方式編輯文件,甚至編寫可以對(duì)現(xiàn)有文件進(jìn)行復(fù)雜更改的腳本,那將太好了。幸運(yùn)的是,對(duì)于這種情況,有一種更好的方法 -- 這種更好的方法稱為 "sed"。

          sed 是一種幾乎包括在所有 UNIX 平臺(tái)(包括 Linux)的輕量級(jí)流編輯器。sed 有許多很好的特性。首先,它相當(dāng)小巧,通常要比您所喜愛的腳本語言小很多倍。其次,因?yàn)?sed 是一種流編輯器,所以,它可以對(duì)從如管道這樣的標(biāo)準(zhǔn)輸入接收的數(shù)據(jù)進(jìn)行編輯。因此,無需將要編輯的數(shù)據(jù)存儲(chǔ)在磁盤上的文件中。因?yàn)榭梢暂p易將數(shù)據(jù)管道輸出到 sed,所以,將 sed 用作強(qiáng)大的 shell 腳本中長(zhǎng)而復(fù)雜的管道很容易。試一下用您所喜愛的編輯器去那樣做。

          GNU sed
          對(duì) Linux 用戶來說幸運(yùn)的是,最好的 sed 版本之一恰好是 GNU sed,其當(dāng)前版本是 3.02。每一個(gè) Linux 發(fā)行版都有(或至少應(yīng)該有)GNU sed。GNU sed 之所以流行不僅因?yàn)榭梢宰杂煞职l(fā)其源代碼,還因?yàn)樗∏捎性S多對(duì) POSIX sed 標(biāo)準(zhǔn)便利、省時(shí)的擴(kuò)展。另外,GNU 沒有 sed 早期專門版本的很多限制,如行長(zhǎng)度限制 -- GNU 可以輕松處理任意長(zhǎng)度的行。

          最新的 GNU sed
          在研究這篇文章之時(shí)我注意到:幾個(gè)在線 sed 愛好者提到 GNU sed 3.02a。奇怪的是,在ftp.gnu.org(有關(guān)這些鏈接,請(qǐng)參閱參考資料)上找不到 sed 3.02a,所以,我只得在別處尋找。我在alpha.gnu.org 的 /pub/sed 中找到了它。于是我高興地將其下載、編譯然后安裝,而幾分鐘后我發(fā)現(xiàn)最新的 sed 版本卻是 3.02.80 -- 可在alpha.gnu.org 上 3.02a 源代碼旁邊找到其源代碼。安裝完 GNU sed 3.02.80 之后,我就完全準(zhǔn)備好了。

          正確的 sed
          在本系列中,將使用 GNU sed 3.02.80。在即將出現(xiàn)的本系列后續(xù)文章中,某些(但非常少)最高級(jí)的示例將不能在 GNU sed 3.02 或 3.02a 中使用。如果您使用的不是 GNU sed,那么結(jié)果可能會(huì)不同。現(xiàn)在為什么不花些時(shí)間安裝 GNU sed 3.02.80 呢?那樣,不僅可以為本系列的余下部分作好準(zhǔn)備,而且還可以使用可能是目前最好的 sed。

          sed 示例
          sed 通過對(duì)輸入數(shù)據(jù)執(zhí)行任意數(shù)量用戶指定的編輯操作(“命令”)來工作。sed 是基于行的,因此按順序?qū)γ恳恍袌?zhí)行命令。然后,sed 將其結(jié)果寫入標(biāo)準(zhǔn)輸出 (stdout),它不修改任何輸入文件。

          讓我們看一些示例。頭幾個(gè)會(huì)有些奇怪,因?yàn)槲乙盟鼈冄菔?sed 如何工作,而不是執(zhí)行任何有用的任務(wù)。然而,如果您是 sed 新手,那么理解它們是十分重要的。下面是第一個(gè)示例:

          $ sed -e 'd' /etc/services

          如果輸入該命令,將得不到任何輸出。那么,發(fā)生了什么?在該例中,用一個(gè)編輯命令 'd' 調(diào)用 sed。sed 打開 /etc/services 文件,將一行讀入其模式緩沖區(qū),執(zhí)行編輯命令(“刪除行”),然后打印模式緩沖區(qū)(緩沖區(qū)已為空)。然后,它對(duì)后面的每一行重復(fù)這些步驟。這不會(huì)產(chǎn)生輸出,因?yàn)?"d" 命令除去了模式緩沖區(qū)中的每一行!

          在該例中,還有幾件事要注意。首先,根本沒有修改 /etc/services。這還是因?yàn)?sed 只讀取在命令行指定的文件,將其用作輸入 -- 它不試圖修改該文件。第二件要注意的事是 sed 是面向行的。'd' 命令不是簡(jiǎn)單地告訴 sed 一下子刪除所有輸入數(shù)據(jù)。相反,sed 逐行將 /etc/services 的每一行讀入其稱為模式緩沖區(qū)的內(nèi)部緩沖區(qū)。一旦將一行讀入模式緩沖區(qū),它就執(zhí)行 'd' 命令,然后打印模式緩沖區(qū)的內(nèi)容(在本例中沒有內(nèi)容)。我將在后面為您演示如何使用地址范圍來控制將命令應(yīng)用到哪些行 -- 但是,如果不使用地址,命令將應(yīng)用到所有行。

          第三件要注意的事是括起 'd' 命令的單引號(hào)的用法。養(yǎng)成使用單引號(hào)來括起 sed 命令的習(xí)慣是個(gè)好注意,這樣可以禁用 shell 擴(kuò)展。

          另一個(gè) sed 示例
          下面是使用 sed 從輸出流除去 /etc/services 文件第一行的示例:

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

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

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

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

          當(dāng)用逗號(hào)將兩個(gè)地址分開時(shí),sed 將把后面的命令應(yīng)用到從第一個(gè)地址開始、到第二個(gè)地址結(jié)束的范圍。在本例中,將 'd' 命令應(yīng)用到第 1 到 10 行(包括這兩行)。所有其它行都被忽略。

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

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

          試一下該例,看看發(fā)生了什么。您將注意到,sed 成功完成了預(yù)期任務(wù)。現(xiàn)在,讓我們分析發(fā)生的情況。

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

          因此,'/^#/' 是一個(gè)規(guī)則表達(dá)式。但是,它做些什么呢?很明顯,現(xiàn)在該復(fù)習(xí)規(guī)則表達(dá)式了。

          規(guī)則表達(dá)式復(fù)習(xí)
          可以使用規(guī)則表達(dá)式來表示可能會(huì)在文本中發(fā)現(xiàn)的模式。您在 shell 命令行中用過 '*' 字符嗎?這種用法與規(guī)則表達(dá)式類似,但并不相同。下面是可以在規(guī)則表達(dá)式中使用的特殊字符:

          字符 描述
          與行首匹配
          與行末尾匹配
          與任一個(gè)字符匹配
          將與前一個(gè)字符的零或多個(gè)出現(xiàn)匹配
          [ ] 與 [ ] 之內(nèi)的所有字符匹配

          感受規(guī)則表達(dá)式的最好方法可能是看幾個(gè)示例。所有這些示例都將被 sed 作為合法地址接受,這些地址出現(xiàn)在命令的左邊。下面是幾個(gè)示例:

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

          在這些示例中,鼓勵(lì)您嘗試幾個(gè)。花一些時(shí)間熟悉規(guī)則表達(dá)式,然后嘗試幾個(gè)自己創(chuàng)建的規(guī)則表達(dá)式。可以如下使用 regexp:

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

          這將導(dǎo)致 sed 刪除任何匹配的行。然而,通過告訴 sed打印 regexp 匹配并刪除不匹配的內(nèi)容,而不是與之相反的方法,會(huì)更有利于熟悉規(guī)則表達(dá)式??梢杂靡韵旅钸@樣做:

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

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

          有關(guān)地址的更多內(nèi)容
          目前為止,我們已經(jīng)看到了行地址、行范圍地址和 regexp 地址。但是,還有更多的可能。我們可以指定兩個(gè)用逗號(hào)分開的規(guī)則表達(dá)式,sed 將與所有從匹配第一個(gè)規(guī)則表達(dá)式的第一行開始,到匹配第二個(gè)規(guī)則表達(dá)式的行結(jié)束(包括該行)的所有行匹配。例如,以下命令將打印從包含 "BEGIN" 的行開始,并且以包含 "END" 的行結(jié)束的文本塊:

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

          如果沒發(fā)現(xiàn) "BEGIN",那么將不打印數(shù)據(jù)。如果發(fā)現(xiàn)了 "BEGIN",但是在這之后的所有行中都沒發(fā)現(xiàn) "END",那么將打印所有后續(xù)行。發(fā)生這種情況是因?yàn)?sed 面向流的特性 -- 它不知道是否會(huì)出現(xiàn) "END"。

          C 源代碼示例
          如果只要打印 C 源文件中的 main() 函數(shù),可輸入:
          $ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more

          該命令有兩個(gè)規(guī)則表達(dá)式 '/main[[:space:]]*(/' 和 '/^}/',以及一個(gè)命令 'p'。第一個(gè)規(guī)則表達(dá)式將與后面依次跟有任意數(shù)量的空格或制表鍵以及開始圓括號(hào)的字符串 "main" 匹配。這應(yīng)該與一般 ANSI C main() 聲明的開始匹配。

          在這個(gè)特別的規(guī)則表達(dá)式中,出現(xiàn)了 '[[:space:]]' 字符類。這只是一個(gè)特殊的關(guān)鍵字,它告訴 sed 與 TAB 或空格匹配。如果愿意的話,可以不輸入 '[[:space:]]',而輸入 '[',然后是空格字母,然后是 -V,然后再輸入制表鍵字母和 ']' -- Control-V 告訴 bash 要插入“真正”的制表鍵,而不是執(zhí)行命令擴(kuò)展。使用 '[[:space:]]' 命令類(特別是在腳本中)會(huì)更清楚。

          好,現(xiàn)在看一下第二個(gè) regexp。'/^}' 將與任何出現(xiàn)在新行行首的 '}' 字符匹配。如果代碼的格式很好,那么這將與 main() 函數(shù)的結(jié)束花括號(hào)匹配。如果格式不好,則不會(huì)正確匹配 -- 這是執(zhí)行模式匹配任務(wù)的一件棘手之事。

          因?yàn)槭翘幱?'-n' 安靜方式,所以 'p' 命令還是完成其慣有任務(wù),即明確告訴 sed 打印該行。試著對(duì) C 源文件運(yùn)行該命令 -- 它應(yīng)該輸出整個(gè) main() { } 塊,包括開始的 "main()" 和結(jié)束的 '}'。

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

          sed 是十分強(qiáng)大和小巧的文本流編輯器。在本文章系列的第二篇中,Daniel Robbins 為您演示如何使用 sed 來執(zhí)行字符串替換、創(chuàng)建更大的 sed 腳本以及如何使用 sed 的附加、插入和更改行命令。
          sed 是很有用(但常被遺忘)的 UNIX 流編輯器。在以批處理方式編輯文件或以有效方式創(chuàng)建 shell 腳本來修改現(xiàn)有文件方面,它是十分理想的工具。本文是前一篇介紹 sed 文章的續(xù)篇。

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

          $ sed -e 's/foo/bar/' myfile.txt 上面的命令將 myfile.txt 中每行第一次出現(xiàn)的 'foo'(如果有的話)用字符串 'bar' 替換,然后將該文件內(nèi)容輸出到標(biāo)準(zhǔn)輸出。請(qǐng)注意,我說的是每行第一次出現(xiàn),盡管這通常不是您想要的。在進(jìn)行字符串替換時(shí),通常想執(zhí)行全局替換。也就是說,要替換每行中的所有出現(xiàn),如下所示:

          $ sed -e 's/foo/bar/g' myfile.txt 在最后一個(gè)斜杠之后附加的 'g' 選項(xiàng)告訴 sed 執(zhí)行全局替換。

          關(guān)于 's///' 替換命令,還有其它幾件要了解的事。首先,它是一個(gè)命令,并且只是一個(gè)命令,在所有上例中都沒有指定地址。這意味著,'s///' 還可以與地址一起使用來控制要將命令應(yīng)用到哪些行,如下所示:

          $ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt 上例將導(dǎo)致用短語 'entrapment' 替換所有出現(xiàn)的短語 'enchantment',但是只在第一到第十行(包括這兩行)上這樣做。

          $ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt 該例將用 'mountains' 替換 'hills',但是,只從空行開始,到以三個(gè)字符 'END' 開始的行結(jié)束(包括這兩行)的文本塊上這樣做。

          關(guān)于 's///' 命令的另一個(gè)妙處是 '/' 分隔符有許多替換選項(xiàng)。如果正在執(zhí)行字符串替換,并且規(guī)則表達(dá)式或替換字符串中有許多斜杠,則可以通過在 's' 之后指定一個(gè)不同的字符來更改分隔符。例如,下例將把所有出現(xiàn)的 /usr/local 替換成 /usr:

          $ sed -e 's:/usr/local:/usr:g' mylist.txt 在該例中,使用冒號(hào)作為分隔符。如果需要在規(guī)則表達(dá)式中指定分隔符字符,可以在它前面加入反斜杠。

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

          $ sed -e 's/<.*>//g' myfile.html 這是要從文件除去 HTML 標(biāo)記的第一個(gè)很好的 sed 腳本嘗試,但是由于規(guī)則表達(dá)式的特有規(guī)則,它不會(huì)很好地工作。原因何在?當(dāng) sed 試圖在行中匹配規(guī)則表達(dá)式時(shí),它要在行中查找最長(zhǎng)的匹配。在我的前一篇 sed 文章中,這不成問題,因?yàn)槲覀兪褂玫氖?'d' 和 'p' 命令,這些命令總要?jiǎng)h除或打印整行。但是,在使用 's///' 命令時(shí),確實(shí)有很大不同,因?yàn)橐?guī)則表達(dá)式匹配的整個(gè)部分將被目標(biāo)字符串替換,或者,在本例中,被刪除。這意味著,上例將把下行:

          This is what I meant. 變成:

          meant. 我們要的不是這個(gè),而是:

          This is what I meant. 幸運(yùn)的是,有一種簡(jiǎn)便方法來糾正該問題。我們不輸入“'<' 字符后面跟有一些字符并以 '>' 字符結(jié)束”的規(guī)則表達(dá)式,而只需輸入一個(gè)“'<' 字符后面跟有任意數(shù)量非 '>' 字符并以 '>' 字符結(jié)束”的規(guī)則表達(dá)式。這將與最短、而不是最長(zhǎng)的可能性匹配。新命令如下:

          $ sed -e 's/<[^>]*>//g' myfile.html 在上例中,'[^>]' 指定“非 '>'”字符,其后的 '*' 完成該表達(dá)式以表示“零或多個(gè)非 '>' 字符”。對(duì)幾個(gè) html 文件測(cè)試該命令,將它們管道輸出到 "more",然后仔細(xì)查看其結(jié)果。

          更多字符匹配
          '[ ]' 規(guī)則表達(dá)式語法還有一些附加選項(xiàng)。要指定字符范圍,只要字符不在第一個(gè)或最后一個(gè)位置,就可以使用 '-',如下所示:

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

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

          盡可能使用字符類是很有利的,因?yàn)樗鼈兛梢愿玫剡m應(yīng)非英語 locale(包括某些必需的重音字符等等).

          高級(jí)替換功能
          我們已經(jīng)看到如何執(zhí)行簡(jiǎn)單甚至有些復(fù)雜的直接替換,但是 sed 還可以做更多的事。實(shí)際上可以引用匹配規(guī)則表達(dá)式的部分或全部,并使用這些部分來構(gòu)造替換字符串。作為示例,假設(shè)您正在回復(fù)一條消息。下例將在每一行前面加上短語 "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 插入整個(gè)匹配的規(guī)則表達(dá)式。因此,可以將與 '.*' 匹配的任何內(nèi)容(行中的零或多個(gè)字符的最大組或整行)插入到替換字符串中的任何位置,甚至多次插入。這非常好,但 sed 甚至更強(qiáng)大。

          那些極好的帶反斜杠的圓括號(hào)
          's///' 命令甚至比 '&' 更好,它允許我們?cè)谝?guī)則表達(dá)式中定義區(qū)域,然后可以在替換字符串中引用這些特定區(qū)域。作為示例,假設(shè)有一個(gè)包含以下文本的文件:

          foo bar oni eeny meeny miny larry curly moe jimmy the weasel 現(xiàn)在假設(shè)要編寫一個(gè) sed 腳本,該腳本將把 "eeny meeny miny" 替換成 "Victor eeny-meeny Von miny" 等等。要這樣做,首先要編寫一個(gè)由空格分隔并與三個(gè)字符串匹配的規(guī)則表達(dá)式。

          '.* .* .*' 現(xiàn)在,將在其中每個(gè)感興趣的區(qū)域兩邊插入帶反斜杠的圓括號(hào)來定義區(qū)域:

          '\(.*\) \(.*\) \(.*\)' 除了要定義三個(gè)可在替換字符串中引用的邏輯區(qū)域以外,該規(guī)則表達(dá)式的工作原理將與第一個(gè)規(guī)則表達(dá)式相同。下面是最終腳本:

          $ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' myfile.txt 如您所見,通過輸入 '\x'(其中,x 是從 1 開始的區(qū)域號(hào))來引用每個(gè)由圓括號(hào)定界的區(qū)域。輸入如下:

          Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel 隨著對(duì) sed 越來越熟悉,您可以花最小力氣來進(jìn)行相當(dāng)強(qiáng)大的文本處理。您可能想如何使用熟悉的腳本語言來處理這種問題 -- 能用一行代碼輕易實(shí)現(xiàn)這樣的解決方案嗎?

          組合使用
          在開始創(chuàng)建更復(fù)雜的 sed 腳本時(shí),需要有輸入多個(gè)命令的能力。有幾種方法這樣做。首先,可以在命令之間使用分號(hào)。例如,以下命令系列使用 '=' 命令和 'p' 命令,'=' 命令告訴 sed 打印行號(hào),'p' 命令明確告訴 sed 打印該行(因?yàn)樘幱?'-n' 模式)。

          $ sed -n -e '=;p' myfile.txt 無論什么時(shí)候指定了兩個(gè)或更多命令,都按順序?qū)⒚總€(gè)命令應(yīng)用到文件的每一行。在上例中,首先將 '=' 命令應(yīng)用到第 1 行,然后應(yīng)用 'p' 命令。接著,sed 繼續(xù)處理第 2 行,并重復(fù)該過程。雖然分號(hào)很方便,但是在某些場(chǎng)合下,它不能正常工作。另一種替換方法是使用兩個(gè) -e 選項(xiàng)來指定兩個(gè)不同的命令:

          $ sed -n -e '=' -e 'p' myfile.txt 然而,在使用更為復(fù)雜的附加和插入命令時(shí),甚至多個(gè) '-e' 選項(xiàng)也不能幫我們的忙。對(duì)于復(fù)雜的多行腳本,最好的方法是將命令放入一個(gè)單獨(dú)的文件中。然后,用 -f 選項(xiàng)引用該腳本文件:

          $ sed -n -f mycommands.sed myfile.txt 這種方法雖然可能不太方便,但總是管用。

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

          1,20{
          在這篇 sed 系列的總結(jié)性文章中,Daniel Robbins 帶您體驗(yàn) sed 的真正力量。在介紹完幾個(gè)重要的 sed 腳本之后,他將通過將一個(gè) Quicken .QIF 文件轉(zhuǎn)換成可讀文本格式來演示一些基本 sed 腳本的編寫。該轉(zhuǎn)換腳本不僅實(shí)用,而且還是展現(xiàn) sed 腳本編寫能力的極佳示例。

          強(qiáng)健的 sed
          在第二篇 sed 文章中,我提供了一些示例來演示 sed 的工作原理,但是它們當(dāng)中很少有示例能實(shí)際做特別有用的事。在這篇 sed 系列的最后文章中,我要改變那種方式,并使用 sed 來做實(shí)際的事。我將為您顯示幾個(gè)示例,它們不僅演示 sed 的能力,而且還做一些真正巧妙(和方便)的事。例如,在本文的后半部,將為您演示如何設(shè)計(jì)一個(gè) sed 腳本來將 .QIF 文件從 Intuit 的 Quicken 金融程序轉(zhuǎn)換成具有良好格式的文本文件。在那樣做之前,我們將看一下不怎么復(fù)雜但卻很有用的 sed 腳本。

          文本轉(zhuǎn)換
          第一個(gè)實(shí)際腳本將 UNIX 風(fēng)格的文本轉(zhuǎn)換成 DOS/Windows 格式。您可能知道,基于 DOS/Windows 的文本文件在每一行末尾有一個(gè) CR(回車)和 LF(換行),而 UNIX 文本只有一個(gè)換行。有時(shí)可能需要將某些 UNIX 文本移至 Windows 系統(tǒng),該腳本將為您執(zhí)行必需的格式轉(zhuǎn)換。

          $ sed -e 's/$/\r/' myunix.txt > mydos.txt


          在該腳本中,'$' 規(guī)則表達(dá)式將與行的末尾匹配,而 '\r' 告訴 sed 在其之前插入一個(gè)回車。在換行之前插入回車,立即,每一行就以 CR/LF 結(jié)束。請(qǐng)注意,僅當(dāng)使用 GNU sed 3.02.80 或以后的版本時(shí),才會(huì)用 CR 替換 '\r'。如果還沒有安裝 GNU sed 3.02.80,請(qǐng)?jiān)谖业牡谝黄?sed 文章中查看如何這樣做的說明。

          我已記不清有多少次在下載一些示例腳本或 C 代碼之后,卻發(fā)現(xiàn)它是 DOS/Windows 格式。雖然很多程序不在乎 DOS/Windows 格式的 CR/LF 文本文件,但是有幾個(gè)程序卻在乎 -- 最著名的是 bash,只要一遇到回車,它就會(huì)出問題。以下 sed 調(diào)用將把 DOS/Windows 格式的文本轉(zhuǎn)換成可信賴的 UNIX 格式:

          $ sed -e 's/.$//' mydos.txt > myunix.txt


          該腳本的工作原理很簡(jiǎn)單:替代規(guī)則表達(dá)式與一行的最末字符匹配,而該字符恰好就是回車。我們用空字符替換它,從而將其從輸出中徹底刪除。如果使用該腳本并注意到已經(jīng)刪除了輸出中每行的最末字符,那么,您就指定了已經(jīng)是 UNIX 格式的文本文件。也就沒必要那樣做了!

          反轉(zhuǎn)行
          下面是另一個(gè)方便的小腳本。與大多數(shù) Linux 發(fā)行版中包括的 "tac" 命令一樣,該腳本將反轉(zhuǎn)文件中行的次序。"tac" 這個(gè)名稱可能會(huì)給人以誤導(dǎo),因?yàn)?"tac" 不反轉(zhuǎn)行中字符的位置(左和右),而是反轉(zhuǎn)文件中行的位置(上和下)。用 "tac" 處理以下文件:

          foo bar oni


          ....將產(chǎn)生以下輸出:

          oni bar foo


          可以用以下 sed 腳本達(dá)到相同目的:

          $ sed -e '1!G;h;$!d' forward.txt > backward.txt


          如果登錄到恰巧沒有 "tac" 命令的 FreeBSD 系統(tǒng),將發(fā)現(xiàn)該 sed 腳本很有用。雖然方便,但最好還是知道該腳本為什么那樣做。讓我們對(duì)它進(jìn)行討論。

          反轉(zhuǎn)解釋
          首先,該腳本包含三個(gè)由分號(hào)隔開的單獨(dú) sed 命令:'1!G'、'h' 和 '$!d'。現(xiàn)在,需要好好理解用于第一個(gè)和第三個(gè)命令的地址。如果第一個(gè)命令是 '1G',則 'G' 命令將只應(yīng)用第一行。然而,還有一個(gè) '!' 字符 -- 該 '!' 字符忽略該地址,即,'G' 命令將應(yīng)用到除第一行之外的所有行。'$!d' 命令與之類似。如果命令是 '$d',則將只把 'd' 命令應(yīng)用到文件中的最后一行('$' 地址是指定最后一行的簡(jiǎn)單方式)。然而,有了 '!' 之后,'$!d' 將把 'd' 命令應(yīng)用到除最后一行之外的所有行?,F(xiàn)在,我們所要理解的是這些命令本身做什么。

          當(dāng)對(duì)上面的文本文件執(zhí)行反轉(zhuǎn)腳本時(shí),首先執(zhí)行的命令是 'h'。該命令告訴 sed 將模式空間(保存正在處理的當(dāng)前行的緩沖區(qū))的內(nèi)容復(fù)制到保留空間(臨時(shí)緩沖區(qū))。然后,執(zhí)行 'd' 命令,該命令從模式空間中刪除 "foo",以便在對(duì)這一行執(zhí)行完所有命令之后不打印它。

          現(xiàn)在,第二行。在將 "bar" 讀入模式空間之后,執(zhí)行 'G' 命令,該命令將保留空間的內(nèi)容 ("foo\n") 附加到模式空間 ("bar\n"),使模式空間的內(nèi)容為 "bar\n\foo\n"。'h' 命令將該內(nèi)容放回保留空間保護(hù)起來,然后,'d' 從模式空間刪除該行,以便不打印它。

          對(duì)于最后的 "oni" 行,除了不刪除模式空間的內(nèi)容(由于 'd' 之前的 '$!')以及將模式空間的內(nèi)容(三行)打印到標(biāo)準(zhǔn)輸出之外,重復(fù)同樣的步驟。

          現(xiàn)在,要用 sed 執(zhí)行一些強(qiáng)大的數(shù)據(jù)轉(zhuǎn)換。

          sed QIF 魔法
          過去幾個(gè)星期,我一直想買一份 Quicken 來結(jié)算我的銀行帳戶。Quicken 是一個(gè)非常好的金融程序,當(dāng)然會(huì)成功地完成這項(xiàng)工作。但是,經(jīng)過考慮之后,我覺得自己可以輕易編寫某個(gè)軟件來結(jié)算我的支票簿。我想,畢竟,我是個(gè)軟件開發(fā)人員!

          我開發(fā)了一個(gè)很好的小型支票簿結(jié)算程序(使用 awk),它通過分析包含我的所有交易的文本文件的語法來計(jì)算余額。略微調(diào)整之后,我將其改進(jìn),以便可以象 Quicken 那樣跟蹤不同的貸款和借款類別。但是,我還要添加一個(gè)特性。最近,我將帳戶轉(zhuǎn)移到一家有聯(lián)機(jī) Web 帳戶界面的銀行。有一天,我注意到,這家銀行的 Web 站點(diǎn)允許以 Quicken 的 .QIF 格式下載我的帳戶信息。我馬上覺得,如果可以將該信息轉(zhuǎn)換成文本格式,那就太棒了。

          兩種格式的故事
          在查看 QIF 格式之前,先看一下我的 checkbook.txt 格式:

          28 Aug 2000 food - - Y Supermarket 30.94 25 Aug 2000 watr - 103 Y Check 103 52.86


          在我的文件中,所有字段都由一個(gè)或多個(gè)制表符分開,每個(gè)交易占據(jù)一行。日期之后的下一個(gè)字段列出支出類型(如果是收入項(xiàng),則為 "-")。第三個(gè)字段列出收入類型(如果是支出項(xiàng),則為 "-")。然后,是一個(gè)支票號(hào)字段(如果為空,則還是 "-"),一個(gè)交易完成字段("Y" 或 "N"),一個(gè)注釋和一個(gè)美元金額字段?,F(xiàn)在,讓我們看一下 QIF 格式。當(dāng)用文本查看器查看下載的 QIF 文件時(shí),它看起來如下:

          !Type:Bank D08/28/2000 T-8.15 N PCHECKCARD SUPERMARKET ^ D08/28/2000 T-8.25 N PCHECKCARD PUNJAB RESTAURANT ^ D08/28/2000 T-17.17 N PCHECKCARD SUPERMARKET


          瀏覽過文件之后,不難猜出其格式 -- 忽略第一行,其余的格式如下:

          D<數(shù)據(jù)>
          T<交易量>
          N<支票號(hào)>
          P<描述>
          ^ (這是字段分隔符)


          開始處理
          在處理象這樣重要的 sed 項(xiàng)目時(shí),不要?dú)怵H -- sed 允許您將數(shù)據(jù)逐漸修改成最終形式。在進(jìn)行當(dāng)中,可以繼續(xù)細(xì)化 sed 腳本,直到輸出與預(yù)期的完全一樣為止。無需在試第一次時(shí)就保證其完全正確。

          要開始,首先創(chuàng)建一個(gè)名為 "qiftrans.sed" 的文件,然后開始修改數(shù)據(jù):

          1d /^^/d s/[[:cntrl:]]//g


          第一個(gè) '1d' 命令刪除第一行,第二個(gè)命令從輸出除去那些討厭的 '^' 字符。最后一行除去文件中可能存在的任何控制字符。既然在處理外來文件格式,我想消除在中途遇到任何控制字符的風(fēng)險(xiǎn)。到目前為止,一切順利?,F(xiàn)在,要向該基本腳本中添加一些處理功能:

          1d /^^/d s/[[:cntrl:]]//g /^D/ {
          s/^D\(.*\)/\1\tOUTY\tINNY\t/
          s/^01/Jan/ s/^02/Feb/
          s/^03/Mar/ s/^04/Apr/
          s/^05/May/ s/^06/Jun/
          s/^07/Jul/ s/^08/Aug/
          s/^09/Sep/ s/^10/Oct/
          s/^11/Nov/ s/^12/Dec/
          s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3: }


          首先,添加一個(gè) '/^D/' 地址,以便 sed 只在遇到 QIF 數(shù)據(jù)字段的第一個(gè)字符 'D' 時(shí)才開始處理。當(dāng) sed 將這樣一行讀入其模式空間時(shí),將按順序執(zhí)行花括號(hào)中的所有命令。

          花括號(hào)中的第一個(gè)命令將把如下行:

          D08/28/2000


          變換成:

          08/28/2000

          posted on 2005-08-03 11:34 weidagang2046 閱讀(205) 評(píng)論(0)  編輯  收藏 所屬分類: Linux

          主站蜘蛛池模板: 兴隆县| 平塘县| 蕉岭县| 宜春市| 大连市| 文安县| 准格尔旗| 平罗县| 巴青县| 德安县| 曲水县| 福清市| 托里县| 称多县| 松江区| 靖远县| 浏阳市| 错那县| 克拉玛依市| 高台县| 锡林郭勒盟| 久治县| 盱眙县| 潜山县| 奈曼旗| 牙克石市| 东乌| 邛崃市| 横峰县| 融水| 汶上县| 抚远县| 北京市| 荥经县| 常州市| 东方市| 东丰县| 乐昌市| 南投市| 沂水县| 蒙山县|