當你在編輯一份文件時,有時候可能會發現有一個名詞或單字必須被修改, 這時候你可能必須要修改整個文件中出現這的名詞或單字的地方, 這個時候你可以用全域代換來完成。代換的指令是 s/pattern1/pattern2/, 這個指令要在 line mode 使用。 Vi 會將 pattern1 取代為 pattern2。 Pattern 翻譯成中文是樣式, 用 pattern 而不用 string﹝字串﹞的理由讀者很快會知道。
當你要代換文字時,你必須要先指定列數,當你下指令 1,$s/student/teacher/, vi 會把整份文件的 student 取代成 teacher。 但是這樣還不夠,這個指令只會把每一列的第一個 student 取代成 teacher , 如果一列中有兩個 student 的話,那只有第一個 student 會被代換成 teacher。 如果你是要把整份文件的 student 代換成 teacher, 那你的指令應該是 1,$s/student/teacher/g。 放在指令尾巴的 g 是全域﹝global﹞的意思,加上這個指令, 不管你一列中出現幾次 student,全部會代換成 teacher。
當你在作全域代換時一定要小心,特別是只代換一個字的時候, 常常會發現有些不想代換的字被代換。 例如你下指令 1,$s/行/列/g,這個指令為把文章裡的「行」代換成「列」, 所以「執行」會變成「執列」,這就不是我們想要的結果。 為了小心起見,我們可以再加一個指令 c(confirm), 這樣 vi 在每次代換之前就會先詢問是否要代換, 所以上面的指令可以改成 1,$s/行/列/gc。
Regular expression
現在把重點放到 pattern,也就是樣式。 在前面有說過,vi 會把 patter1 代換成 pattern2。 但是或許你會發現,有幾個符號會出現一些錯誤, 這些符號大概有 .、*、^、$、\、 [、] 等。這是因為 vi 對字串的搜尋是支援 regular expression 的。 Regular expression,翻譯作「規則字串」,是 unix 定下的一種規則。 當你在搜尋字串時, 只要依照 regular expression 告訴程式你要尋找的字串大概是什麼樣子, 程式就會將符合條件的字串找出來。 而上述的那些符號就是 regular expression 使用的符號, 所以 vi 會用不同的意義解釋這些符號。
如果你希望 vi 用原來的意義解釋那些符號, 只要在前面加上 \﹝跳脫字元﹞就行了。 例如你要代換「\」成「*」,就下指令 1,$s/\\/*/g。 在 pattern2 的 * 前面不加 \ 是因為 pattern2 不是搜尋用的, 所以規則和 pattern1 不一樣。
現在我們了了解一下用在 regular expression 的符號代表什麼意義:
看到這裡,你會發現上面介紹的 regurlar expression 都是用在 pattern1, 沒有一個能用在 pattern2。接下來要介紹的就是用在 pattern2 的語法。
- . 代表任意一個可見字符或空格或 tab。如果你的 vi 認得中文字, 那 . 也代表任意一個中文字。比如指令 1,$s/a.b/ab/g, 這個指令會把你文章中出現的字串像是 acb、a愛b 等代換成 ab。
再比如指令 1,$s/././g,這個指令會把你文章中的每一個字元都換成「.」, 在 pattern1 中的「.」是一個規則字串,代表任意一個字元。 而在 pattern2 的「.」則代表字元「.」。很明顯地, 如果在 pattern2 中 . 依然代表任何一個字元, 那 vi 就無法知道要代入什麼字元了,總不能跟它說隨便啦。
- * 的前面一定要有一個字元,如果沒有,那它就代表字元「*」。 當一個字元後面跟著 * 時,表示那個字元重複出現從零到任意多﹝越多越好﹞次。 例如下指令 1,$s/aa*/a/g會將如 a、aa、aaaaaaaa 這樣格式的字串代換成 a。
pattern1 中使用 aa* 和 a* 結果是不同的, 因為 a* 代表從任意多﹝越多越好﹞個 a, 所以 1,$s/a*/a/g 會變成任兩個字元之間都會插入一個 a。 之前有「越多越好」,這是指像 aaaaaaaaa 這樣的字串, a* 一定是代表 aaaaaaaaa,而不會代表 aa 或是 aaaa。
- ^ 放在最前面就代表一列的開頭,否則就代表字元「^」。 例如 1,$s/^Key/Lock/ 會把出現在開頭的 Key 換成 Lock, 而不是在開頭的 Key 則不會有影響。 這樣的指令我想是用不著加 g(global),因為一列大概只會有一個開頭。
- $ 放在最後面就代表一列的尾巴,否則就代表字元「$」。 例如 1,$s/Lock$/Key/ 會把出現在尾巴的 Lock 換成 Key, 而不在尾巴的 Lock 不會有影響。
- [ 和 ] 是一對的,這兩個字元要同時使用。 放在方括號內的所有字元只要找到一個就算是符合條件。 舉個例子:.,20s/2[abcd]/2e/g 會將目前這一列到第二十列的 2a、 2b、2c、2d 代換成 2e。
方括號中還有另一種指定法:[a-z] 表示從 a 到 z 中的任一字元。 [B-Ea-z3-5] 表示 B 到 E、a 到 z 和 3 到 5 中的任一字元。
在方括號的開頭放入 ^ 表示否定。 例如 5,100s/[^a-z]//g 會把第五列到第一百列中所有「不是」小寫英文字母的字元全部刪除。 如果在方括號中 ^ 不是放在開頭的話,那它就表示字元「^」。
在方括號中除了 \、-、] 和放在開頭的 ^,所有的符號如 *、. 等都代表原來的意義。 如果要讓那四個符號表示原來的意義,只要在前面加上 \。 例如 [\^\\\-\]] 就代表 ^、\、-、] 中的任一字元。
- 以上介紹的符號是可以混合使用的。
例如下指令 1,$s/^...//g 會把每一列開頭的三個字元刪除。
1,$s/ [a-z]* //g 則是把小寫的單字全刪除﹝pattern1 的開頭和結尾是空白字元﹞。
1,$s/.*//g 則會把所有的列換成空列。
- & 代表 pattern1 所找到的字串。 例如指令 s/ry*s/a&z/g 會把 rs 代換成 arsz, rys 代換 arysz,ryys 代換成 aryysz 等。 在你想要把某種格式的字串的前面或後面再加上文字時用 & 會非常方便。 再比如 s/.*/mv & &.txt/,如果某一列是 foo, 這個指令會把它變成 mv foo foo.txt。
- ~ 會記錄你上一個使用的 pattern2。 例如先下指令 s/his/their/g,再下指令 s/his/~/g, 這時候第二個指令的 ~ 會變成 their。
- \( 和 \) 是成對的,包含在其間的「樣式」會被放到暫存區中。 最多可以放到九個,vi 會依序自動編號,從 1 到 9。 要使用暫存區只要按 \ 以後再按暫存區的編號。 \( 和 \) 其實還是用在 pattern1, 但是我認為這個部分對 pattern2 較有意義, 所以就放在這裡說明,請看下面的範例就可以明白。
s/b\(.*\)k/k\1b/g 這個指令會把類似 bxxxxxxk 的字串代換成 kxxxxxxb。中間的 xxxxxx 不會改變。 回頭看這個指令,夾在 \( 和 \) 之間的﹝要是不習慣,把 \ 當成隱形的, 就只剩下 ( 和 ),這樣想會比較容易接受﹞的「樣式」是 .*, 表示任何一個字串,越長越好。加上頭和尾,就是 b.*k。 所以 vi 會搜尋到 bk、book、bank、basic practice is pk 等等的字串。 每次搜尋到之後,vi 把中間的字串放入暫存區 1, 然後我們在 pattern2 要求 vi 把暫存區 1 的字串拿來, 頭跟尾加上其他字元,然後再代換原來的字串。
再看一個例子,指令 1,$s/b\([aeiou]\)c\([sz]\)/r\1g\2/g, 在 pattern2 的 \1 代表 [aeiou],\2 則代表 [sz], 所以這個指令會把整份文件的 bacs 代換成 rags, bicz 代換成 rigz 等共有十種字串會被代換。
- \u 會把後面的字元變成大寫, \l﹝這是小寫 L﹞會把後面的字元變成小寫。 舉例來說,指令 s/rar/\uzi\lp/g 等於指令 s/rar/Zip/g。 再說一個例子,有個句子 He and she are my friends., 你想要把 He 和 her 對調﹝宣示女男平等﹞, 下指令 s/\(He\) and \(she\)/\u\2 and \l\1/g, 句子就會變成 She and he are my friends.。 你可以試試看上面的指令如果不用 \u 和 \l 的話會有什麼結果。
- \U 會把後面的字變成大寫,\L 會把後面的字變成小寫。 如果後面沒有 \e 或 \E, 那 \U 和 \L 會運作到結尾, 否則會運作到 \e 或 \E。 例如指令 s/[a-z][a-z]*/\U&/g 會把所有的小寫單字變成大寫。 再比如指令 s/\([Tt]he\) hunter/\U\1\e hunter/g, 會把 The hunter 或 the hunter 代換成 THE hunter。
其他的指令
現在 regular expression 介紹到這裡,接著要再介紹一些跟代換有關的指令。直接下指令 s 會重複上一個代換。 例如先下指令 110s/a/b/g,再下指令 s, 就等於指令 s/a/b/﹝注意,沒有 g﹞。
在 command mode 下按【 & 】就等於 line mode 下的 s, 可以少按兩個按鍵。不過無法指令列數,也不能在後面加 g。
在前面的代換指令中,尾巴大部分都跟著 g(global), 其實 g 還有其他的用法。 當你下指令 g/bgs 時,vi 會找到所有含有 bgs 的列, 並且全部印出來。 所以我們也可以下指令 g/bgs/d, 含有 bgs 的列會全部被刪除。 這是一種指定列數的方法,即使這些列不連續也會被 vi 接受。
用上述的方式,我們可以這樣下指令 g/bgs/s/heresy/shann/g, 這個指令會找出所有包含 bgs 的列, 然後把這些列的 heresy 換成 shann,其他不包含 bgs 的列的 heresy 則保持不動。
全域代換的基本指令大致介紹完畢,剩下的就是應用。 這方面的組合可以說有無窮多,只要你自己覺得方便就行了。 我沒提到的細節,就是重複上一個代換。 我認為少打那幾個字差不了多少。 如果要一列一列修改,那乾脆用 command mode 還比較快。
製作人、 修改記錄 |
李易霖 (02/8/19) --- |