| * | 筆者整理了一些與 sed 相關的資源放在網頁上 | * | 謝謝馬兒(marr@xlinux.com) 先生的潤稿 |
(註:上一期經修正後的版本請到 Linuxers 或筆者的網頁中閱讀)
首先筆者提出一個具有實用性的問題﹐一方面讓讀者複習上期的內容﹐
另一方面則介紹如何『刪除』某些字串
(為讓讀者易於解讀﹐在某些地方我們會以
來表示空格):
[問題 8]
s/^/

/
會在該位置加入三個空格而達到所要求的效果:

由於管線符號(|)之後必須有 shell 命令﹐ 因此上例中在 | 後直接按 ENTER 時﹐shell 認為尚未結束而顯示 > 這個提示符號要求我們繼續輸入;當然這兩行也可以合併成一行。
s/正規表示式//g
在『用來取代的字串』這個部分﹐我們以不輸入任何字元的方式來代表空字串。 例如要把每一行中﹐所有的 abc 刪除掉﹐利用 s/abc//g 這個命令即可:

在知道如何『刪除』字串後﹐ 請讀者想想只讓 一行前端一或多個空格 能符合的 RE 該怎麼寫?分成兩個部分:
| 條件 | 對應的 RE |
|---|---|
| 零或多個空格 | * |
| 一個(含)以上的空格 | ![]() * |
| 兩個(含)以上的空格 | ![]() ![]() * |
^
*
這個 RE。 請注意﹐如果不加上 ^ 這個定位字元﹐而又加了 g 這個選項時﹐ 一行中所有的空格都會被刪掉﹐這是讀者應當留意的。實驗看看:



*
因此
s/

*/
/g
就是我們所要的編輯命令:

(註:用 s/
*/
/g 也可以達到相同的效果﹐
但意義不同﹐請讀者想想看)
| 取用符合 RE 字串的某些部分-\( 與 \) 的運用 |
[問題 9]
[01][0-9]-[0-3][0-9]-[0-9][0-9]
這個 RE 來描述﹐它的意義表列如下:
| 欲搜尋字串的 笫幾個字元 |
1 | 2、5、 7、8 |
4 | 3、6 |
|---|---|---|---|---|
| 允許值 | 0 或 1 | 0 到 9 | 0 到 3 | - |
\([01][0-9]\)-\([0-3][0-9]\)-\([0-9][0-9]\)
觀察這個新出爐的 RE 內共有三組由 \( 及 \) 所構成的部分﹐ 組別的編號是從左至右﹐從一開始算起:
| 組別 | 內容 | 說明 | 如何取用符合 RE 的字串的該部分 |
|---|---|---|---|
| 1 | [01][0-9] | 屬於『月』 | \1 |
| 2 | [0-3][0-9] | 屬於『日』 | \2 |
| 3 | [0-9][0-9] | 屬於『年』 | \3 |
\3-\1-\2
把這兩個部分組合成 s 命令:
s/\([01][0-9]\)-\([0-3][0-9]\)-\([0-9][0-9]\)/\3-\1-\2/g
試試看對不對呢?

| 定址-限制 sed 只對某些資料行執行編輯命令 |
首先製作測試檔 data3﹐內容如下:

編輯命令 s/xx/yy/g 可以把每一行中的 xx 都換成 yy:

如果只要把第二行內所有的 xx 換成 yy 時﹐執行

即可﹐其中編輯命令前的 2 代表第二行﹐如果是 3 則代表第三行﹐依此類推; 符號 $ 在此有特殊的意義--用來代表檔案的最後一行:

我們也可以要求 sed 只對某個範圍內(從第幾行起到第幾行為止)的資料行﹐ 執行把 xx 換成 yy 的命令:

編輯命令前的 3,$ 代表只對第三行起到最後一行為止的資料行執行該編輯命令。
在上述的例子中﹐指定第二、三及最後一行時所用的 2、3 與 $﹐ sed 稱它們為位址(address)﹐而要編輯命令只執行在某些資料行的動作 稱為定址(addressing)。sed 在執行編輯命令之前﹐ 會先判斷目前處理的資料行是否屬於位址所指定的範圍。 在前幾個例子中﹐我們看到編輯命令前的位址格式有三種:
| 位址與正規表示式的結合 |
/正規表示式/
若應用在 1-位址 的位址格式下﹐ 只有當一行中含有符合該正規表示式的字串時﹐ sed 才會對該行執行編輯命令。例如
/yes/s/xx/yy/g
這個命令含了兩個部分:

但這種寫法解讀起來有些不方便﹐ 我們可以利用一組大括弧把命令中的位址與編輯命令分開來:

讀者應注意:

另外﹐我們可以把編輯命令存在檔案中﹐再指定 sed 到該檔案中擷取命令: 首先把

存入 script 這個檔案中﹐然後執行
sed -f script data3
即可得到相同的結果。其中選項 -f 後為命令檔(script file)的名稱。
| 把正規表示式應用在 2-位址 的位址格式上 |

在上述這個例子中﹐第 2 行之後含有 yes 字串的是第 5 行﹐因此 sed 只會把從第 2 行起﹐到第 5 行為止的 xx 換成 yy。
為了能理解 sed 處理 2-位址 的方式﹐ 讀者不妨想像有一個開關﹐而 on 與 off 是由兩個位址來把關的﹐ 當在 on 的狀態時﹐sed 就執行位址後的編輯命令;而在 off 狀態時則否:
但如果第 2 行之後﹐都沒有任何一行含有 yes 字串時會怎樣呢? 由於開關不會被切到 off﹐因此 sed 會對第 2 行起到最後一行止﹐每一行執行 s/xx/yy/g 的命令:


在這個例子中﹐第一個含有 no 的資料行是第 3 行﹐因此開關在第 3 行被切為 on﹐ 一直要到最後一行($)處理完畢後才會被切為 off。

[說明]
| 刪除資料行-編輯命令 d |

時﹐sed 沒有輸出任何資料而直接回到 shell 的提示符號﹐ 因為 sed 每讀進一行﹐我們就要求它把該行刪掉﹐當然就沒有資料可以輸出了。
而命令 /yes/d 可以把含有 yes 字串的資料行刪除:

所有含有 yes 字串的資料行都被刪掉了。 如果要把空行刪掉呢?在編輯命令 d 前加上只有空行能符合的條件﹐ 也就是 /^$/ 即可:

| 不對某些資料行執行編輯命令 |
/yes/!d
會把不含有 yes 字串的資料行刪除掉:

| 字的邊界 |
cat
也就是 cat 前後各有一個空格才行。這樣雖然可以找出
It indicates that cat is too fat.
that 之後的 cat 而不是夾在 indicates 中的 cat﹐但這個解法並無法找出
I have a cat.
中的單字 cat。在設計更好的 RE 之前先想想看﹐ 當要找 cat 這個『單字』時﹐為什麼我們能一眼分辨出在這兩個句子中﹐真正的單字 cat 呢? 原因是真正的單字 cat 前後並不會緊接著英文字母。 根據這項推論﹐我們可以設計出新的 RE:
[^A-Za-z]cat[^A-Za-z]
試試看效果如何?

我們發現有些單字 cat 並沒有被找出來; 事實上﹐單字 cat 在一行中的位置可歸類為四種:
| 組別 | 單字 cat 的分佈情形 | 有效的 RE | 符合左方 RE 的字串的長度 |
|---|---|---|---|
| 1 | 只由 cat 這三個字元 所構成的資料行 |
^cat$ | 3 |
| 2 | 位在一行的開頭 | ^cat[^A-Za-z] | 4 |
| 3 | 位在一行的結尾 | [^A-Za-z]cat$ | 4 |
| 4 | 一行的中間 | [^A-Za-z]cat[^A-Za-z] | 5 |
剛才所想的新 RE 只能找出屬於第 4 組的 cat;看來要找出單字 cat 還真是件不容易的事。還好在 GNU 版本的 grep 以及 sed 內提供了 \b 這個特殊序列(meta-sequence)﹐ 用來代表字的邊界(word boundary)﹐它是一種不佔空間的定位字元; 有了它﹐要找出以上四種位置的 cat 就可以用
\bcat\b
這個 RE 來解決:

(註:grep 與 sed 的 \b 所設定的條件比前表所列的更嚴格﹐我們採用的是 [^A-Za-z]﹐而它們採用的是 [^A-Za-z0-9])
| 限制符合的次數 |
而在基本型 RE 中﹐我們可以利用 \{ 與 \} 可用來限制符合 X 的次數; 與 * 相同﹐它們也必須與佔有一個位置的 RE 共用。 以下的表格將列出 \{ 與 \} 的用法:
| 格式 | 字串須符合怎樣的 RE 才合乎要求 |
|---|---|
| X\{n\} | 由連續 n 個 X 所構成 |
| X\{n,m\} | 連續 n 到 m 個 X |
| X\{n,\} | 至少連續 n 個 X |
| |
如果要找的字串是 1 之後接著連續 2 到 4 個 0﹐再接著 1 時﹐ 我們可以把 RE 寫成 10\{2,4\}1﹐實驗一下:

[說明]
[問題 10]
^..........*$ 或 ^.\{9\}.*$ 來描述
(註:由於 * 有 greedy 的特性﹐把 RE 寫成 ^.\{9\}.* 也可以)。
而要保留前 9 個字元﹐我們把它們利用一對 \( 與 \) 圈起來﹐ RE 變成了:
^\(.\{9\}).*$
而編輯命令 s/^\(.\{9\}).*$/\1/ 則保留前 9 個字元﹐而把之後的字元刪除。
| 對一資料行執行多個編輯命令 |
sed -e 命令1 -e 命令2 .... -e 命令n 資料檔名
讀者也可以採用一行列出一個命令的方式﹐如
| 命令1 |
| 命令2 |
| : |
| 命令n |
把這些命令依序集合在一個命令檔(script file)內﹐ 然後使用
sed -f 命令檔檔名 資料檔名
的方式來執行。
而 sed 處理資料的方式﹐是依序對資料檔的每一行執行以下步驟:
[問題 11]

下圖為 sed 對

這行資料執行命令檔的過程:
『把當時 pattern space 內﹐所有的 xx 換成 yy』
在問題 11 的圖示中﹐很清楚的可以看到

*// 編輯的是原始的資料。


*/
/g 編輯的是經過第 1 個命令 s/^
*// 編輯過的內容。
更嚴謹的說法是
『若此時 pattern space 內含有字串 yes﹐則把所有的 xx 換成 yy』
在下一期的內容中﹐我們將介紹延伸型的 RE、egrep、以及如何在 perl 中使用 RE。
| 參考文獻 |