Fri 2 Sep 2005
在前面我們所談的那些可以說是比較基本的東西,但是對於一份文件來說,光有前面所介紹的游標移動、刪除等等功能是不足夠的。面對一份文件我們通常會因為某些緣故而使得我們必須去修改當中固定出現的字串樣式(pattern)成我們想要的樣子。最常遇到的就像中文文件的標點符號問題,或是 un*ix 和 DOS 文件格式之間轉換常會有個 ^M 結尾會讓人覺得很討厭,又或是我們想要把一份 HTML 格式的文件去除掉它的 HTML tag。
對於這些事情來說,拿中文標點符號置換這個很多編輯器都做得到,簡單地說如果想更動的 pattern 是一個固定的字串,那對於一般編輯器來說都不會太困難,但是對於具有固定格式,但字串內容卻不一定的該怎麼辦?就像要去掉 HTML 格式中的所有 HTML tag?這就是 vim 開始大顯身手的地方了。
今天我們不談別的其他指令,就光談在 vim 中的「 :s 」指令。小寫 :s 表示置換(substitute)的意思,不過通常你用 vim 下 :h :s 指令的時候會看到這樣的畫面:

其實這一串東西就是在說 :s 這個指令的格式要怎麼下。
Range:
一般對於整份文件都要作置換的話,我都會下像這樣的指令:
:%s/, /,/g
最前面的「%」就是表示全域,也就是現在編輯的這一份文件都要作後面取代的工作。那這個指令就是說要把半形的逗點「,」變成全形「,」。那後面「g」又代表什麼意思呢?在 g 這個欄位上的東西是用來表示對目前這個指令所做的額外的選項。就拿 g 來說, g 代表在文件中每一個出現的半形逗點都要置換成全形。或許你會問剛剛不是已經用「%」表示全域了嗎,怎麼又要用 g 呢?我應該這樣說,「%」用來表示從文件的第一行到最後一行。但是在比對(match)的時候,如果不加「g」這個額外選項的話, vim 只會把每一行比對到的第一個作取代,同行其他也 match 到的就不管了。所以用「g」表示每一行中每一個比對到的都要置換。
所以同理,如果你要把那些因為 un*ix 和 DOS 之間格式不合所造成的^M消掉的話,也只需要下成這樣就可以了:
:%s/^M//g
可是有時候你想要作置換的只是文件中的某一部份的話怎麼辦?不要緊,還記得選取模式嗎?在你想置換部分的開頭按下大寫「V」然後用移動游標(我們之前講的 vim 移動游標的方法在選取模式下都可以用)到你想要的位置之後按「:」就會跳到輸入指令的狀態,如下圖最下面那行看到的一樣:

當然你很確定行數,你也可以這樣下:
:1,300s/vim/VIM/g
或者是如果你很確定要從現在游標所在行之後的所有行都置換,可以下成:
:.,$s/vim/VIM/g
冒號一開頭的那一小點「 . 」就代表游標現在所在行,「$」則用來表示最後一行。
或許聰明的你已經想到怎麼樣可以從現在所在行之前的都要置換了,
:.,1s/vim/VIM/g
不過當你這樣打的時候, vim 會跳出來一個訊息:
Backwards range given, OK to swap (y/n)?
這就是說你給了一個起始行數比結束行數還要大的範圍。這是因為 vim 所定的範圍都是從小到大,如果你要從大到小不是不行,只是多個訊息確認你沒有打錯罷了。
Pattern:
像前面說,固定字串像把半形逗點換成全形逗點這都還容易,如果只是格式固定,但是字串內容會變動怎麼辦?就像去 HTML tag 的時候就很麻煩。
還記得我們在淺談vim那時候提過的指令嗎?
- :%s/\n/^V/g
- :%s/< [^<>]*>//g
- :%s/^V/\n/g
在中間的那一些 [^<>]*看起來像外星文字的,就是 pattern 比對的主力。事實上關於這些東西,我們在談 vim 的 search 搜尋功能的時候也有提到過一些。所以我們今天就來補一些上次沒有講到的東西。
假如我現在有一筆人名、電話的資料,由於是隨手記的,上面自然就是沒有排序過。那沒排序過對於想要在上面找資料的人就很麻煩。萬一人名記不太清楚,電話號碼也記得七七八八,雖然說有 vim 方便的 search 功能,但總是感覺不足。(當然這只是假設情況,因為實際上可能大家都已經建立某種方便搜尋的資料庫了)
我們先假設人名、電話的對應長成這樣:
趙大明 1235478982 錢小名 1223450012 王孫李 5938123812 周渚衛 1384914191 沈以情 2345934981
那我可不可以讓它變成這樣子?
1235478982 趙大明 1223450012 錢小名 5938123812 王孫李 1384914191 周渚衛 2345934981 沈以情
我可以下這樣的指令完成這個工作:
:%s/\([^0-9]\{1,}\)\([0-9]\{1,}\)/\2 \1/g
前面橘色的部分 \([^0-9]\{1,}\) 用 \( \) 括起來的,表示這是一個的單元,之後就可以依照它出現的順序而使用 \1, \2,… ,來代表它。所以我們可以看到 [^0-9] 就表示非數字的部分,後面的\{1,} 如果你還記得的話,就是代表出現至少一次。不過這個時候DK長輩會跟你說用 vim 要文明一點,要用「\+」來代表至少出現一次。看個人喜好了,如果你願意多記一些代替的符號就多記一點,你可以用 :h /multi 看到更多這些替代符號。
所以把 pattern 用 \( \) 分開之後,我們就可以用 \1,\2 來把他們交換位置。
不過你可能會說這樣還沒排序啊。我們可以用選取模式把整份文件選起來,或是你懶得用上下左右改變,你可以用 ggVG (gg 跳到第一行之後,用大寫V進入選擇模式,以大寫G跳到最後一行)把整份文件選起來之後按 :!sort,這時候在你 vim 視窗的最底下就會變成這樣:
:’< ,'>!sort
按<Enter>下去就會變成根據每一行的最前頭作排序的結果。所以結果就會變成:
1223450012 錢小名 1235478982 趙大明 1384914191 周渚衛 2345934981 沈以情 5938123812 王孫李
很簡單吧? :p
如果你願意配合暫存器( registers )的話,有時候也能省點力氣。在講 vim search 的時候,我們曾經提了用 <Ctrl+R>搭配數字鍵 0 來把暫存器 0 的東西叫出來。同樣的,我們不管用 / 或是 ? 等等輸入字串的時候,事實上都已經把要搜尋的字串寫入「/」的暫存器裡面,所以當我們只是想置換的字串就是搜尋的字串的話,我們可以這樣做:
:%s/<Ctrl+R></>/用來取代的字串/g
</>表示在按 <Ctrl+R>之後按下「/」這個按鍵。這樣就可以把搜尋的字串叫出來並用之於置換指令上。
額外指令:
我們剛剛在前面談到可以作像「g」這些的額外控制。那有哪些控制可以作呢?
比方說在我們作置換的時候,由於 vim 預設是有大小寫的差別,如果你不管大小寫都要取代的話,那可以用「i」這個額外參數來控制 vim 取代的時候就不管大小寫都會作取代的工作。
不過有時候,我們可能不太能確定是不是整份文件中的每一個都要取代,那我們就可以加「c」 confirm 確認參數來控制,那使用上也很容易。 vim 會在比對到之後問
(y/n/a/q/l/^E/^Y)?
- y 是代表執行目前的取代。
- n 是跳過。
- a 代表 always ,就是從目前以後的取代都會執行。
- q 則是不要作取代,並且離開詢問要不要取代的狀態,並回到指令模式或原來的模式下。
- l 則是 last 的意思,就是目前這個取代執行後就離開詢問的取代模式,回到指令模式或原來的模式下。
- ^E 表示往前一頁。
- ^Y 表示往後一頁。
對於 :s 的用法到這裡我們就已經把常用的幾個方式都說完了。礙於篇幅,我也決定先寫到這裡就好,不然寫一大篇,可能連有興趣進來瞭解 vim 的人光看到就害怕了,怎麼還有辦法體會 vim 的好呢?其實關於 :s 的用法,還有 pattern 還有很多的方式在本篇中沒有提到,你可以在 vim 中用 :h :s 看到更多詳細的資料。今天就來 vim 吧!
September 8th, 2005 at 2:27 pm
[…] 當你對 vim 瞭解越多的時候,有時候你會覺得自己變得很貪婪,常常會問:如果 vim 連這個都做得到,那它可不可以怎麼怎麼?就像我們之前介紹的置換功能,你可能會覺得 vim 真的可以幫你做到很多事情。但是它能不能做更多事情? 舉例來說,有時候一篇文章裡面你可能不想要它的空白行,但是你用「:s」的話都是以一行一行當作單位,沒有辦法讓你砍掉這些空白行。雖然說你其實可以把換行符號取代掉,讓文章變成一行,然後再做兩三個取代命令來達成。但是萬一遇到要把包含某個字串的那行搬移或是刪除的時候又該怎麼辦呢?那這就是今天要講的「 :g 」全域指令。 […]
September 10th, 2005 at 4:02 pm
[…] 我們在講到 vim 的取代置換功能 :s 的時候,內文提到了一個 <Ctrl+R>然後按 / 就可以叫出用 / 作搜尋時所打的字串。這種呼叫出之前打過的字的方式,就是本次的主角 register。 […]
September 13th, 2005 at 12:08 am
[…] 我們前面談的很多東西像搜尋「/」、取代置換「:s」、全域命令「:g」這些,功能都可以說是很強大,但如果配合上常規表示法(regular expression),就像我們在談 vim 搜尋的時候有約略提到用 [0-9] 表示只出現一次的數字,而數字可以是 0 1 2 3 4 5 6 7 8 9 。這些都可以說是被規範在常規表示法裡面(regular expression)。或許你看到這個英文已經打開 vim 打 :h regular-expression 來看說明了。沒錯,這就是我們這次要談的在 vim 裡面的常規表示法。 基本上,我們不會討論學術上常規表示法的定義,我們只會談用 vim 配合上這種表示法(以及相關的特殊符號)能夠為我們做些什麼。所以我們可以輕鬆一點來討論這個話題。 […]
October 29th, 2005 at 11:05 pm
December 16th, 2005 at 7:00 pm
:%s/\([^0-9]\{1,}\)\([0-9]\{1,}\)/\2 \1/g
应该改为:
:%s/\([0-9]\{1,\}\) \([^0-9]\{1,\}\)/\2 \1/g
??
December 16th, 2005 at 8:00 pm
To chengqisheng,
我想您說的指的是對於{min,max} 這樣的表示式是不是應該表示成\{min,max\}?是的,的確最好是像您寫的那個樣子,不過少打後面的「\」其實也可以用的(在我用vim6.3的windows版本是OK的)。
Anyway, 我想,寫文件可能還是乖乖寫比較好一點。還是謝謝您的建議。
February 24th, 2006 at 6:20 pm
December 7th, 2006 at 12:11 am
若是想取代(刪除)所有文中兩個字母間的空白而非連續的空白時要如何做呢?
eg:
From
” A B C X Y Z”
To
” ABC XYZ”
December 8th, 2006 at 2:02 pm
如果 C 和 X 間的空白,和其他字元間的空白沒有什麼不同,那就沒有什麼好方法。
如果說是只消兩個字母間的空白,大概可以這樣下:
:%s/\([a-zA-Z]\)[空白]\+\([a-zA-Z]\)/\1\2/g
December 8th, 2006 at 2:16 pm
如果說是連續空白不消,而專只消單一空白:
%s/[空白]\([^空白]\)/\1/g
July 20th, 2007 at 9:19 am