2010年8月17日 星期二

vim 的取代置換功能「s」

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

今天我們不談別的其他指令,就光談在 vim 中的「 :s 」指令。小寫 :s 表示置換(substitute)的意思,不過通常你用 vim 下 :h :s 指令的時候會看到這樣的畫面:
vim help for :s substitute
其實這一串東西就是在說 :s 這個指令的格式要怎麼下。
Range:
一般對於整份文件都要作置換的話,我都會下像這樣的指令:
:%s/, /,/g
最前面的「%」就是表示全域,也就是現在編輯的這一份文件都要作後面取代的工作。那這個指令就是說要把半形的逗點「,」變成全形「,」。那後面 「g」又代表什麼意思呢?在 g 這個欄位上的東西是用來表示對目前這個指令所做的額外的選項。就拿 g 來說, g 代表在文件中每一個出現的半形逗點都要置換成全形。或許你會問剛剛不是已經用「%」表示全域了嗎,怎麼又要用 g 呢?我應該這樣說,「%」用來表示從文件的第一行到最後一行。但是在比對(match)的時候,如果不加「g」這個額外選項的話, vim 只會把每一行比對到的第一個作取代,同行其他也 match 到的就不管了。所以用「g」表示每一行中每一個比對到的都要置換。
所以同理,如果你要把那些因為 un*ix 和 DOS 之間格式不合所造成的^M消掉的話,也只需要下成這樣就可以了:
:%s/^M//g
可是有時候你想要作置換的只是文件中的某一部份的話怎麼辦?不要緊,還記得選取模式嗎?在你想置換部分的開頭按下大寫「V」然後用移動游標(我們之前講的 vim 移動游標的方法在選取模式下都可以用)到你想要的位置之後按「:」就會跳到輸入指令的狀態,如下圖最下面那行看到的一樣:
vim selection for substitute
當然你很確定行數,你也可以這樣下:
: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
下去就會變成根據每一行的最前頭作排序的結果。所以結果就會變成:
1223450012   錢小名
1235478982   趙大明
1384914191   周渚衛
2345934981   沈以情
5938123812   王孫李
很簡單吧? :p
如果你願意配合暫存器( registers )的話,有時候也能省點力氣。在講 vim search 的時候,我們曾經提了用 搭配數字鍵 0 來把暫存器 0 的東西叫出來。同樣的,我們不管用 / 或是 ? 等等輸入字串的時候,事實上都已經把要搜尋的字串寫入「/」的暫存器裡面,所以當我們只是想置換的字串就是搜尋的字串的話,我們可以這樣做:
:%s//用來取代的字串/g
表示在按 之後按下「/」這個按鍵。這樣就可以把搜尋的字串叫出來並用之於置換指令上。
額外指令
我們剛剛在前面談到可以作像「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 吧!

from http://greenisland.csie.nctu.edu.tw/wp/2005/09/02/302/
-----------------------------------------------------------------------------------------------------------------------------------------

vi/vim 中可以使用 :s 命令來替換字串。該命令有很多種不同細節使用方法,可以實現複雜的功能,記錄幾種在此,方便以後查詢。

:s/vivian/sky/ 替換當前行第一個 vivian 為 sky

:s/vivian/sky/g 替換當前行所有 vivian 為 sky

:n,$s/vivian/sky/ 替換第 n 行開始到最後一行中每一行的第一個 vivian 為 sky

:n,$s/vivian/sky/g 替換第 n 行開始到最後一行中每一行所有 vivian 為 sky

n 為數字,若 n 為 .,表示從當前行開始到最後一行

:%s/vivian/sky/(等同於 :g/vivian/s//sky/) 替換每一行的第一個 vivian 為 sky

:%s/vivian/sky/g(等同於 :g/vivian/s//sky/g) 替換每一行中所有 vivian 為 sky

可以使用 # 作為分隔符號,此時中間出現的 / 不會作為分隔符號

:s#vivian/#sky/# 替換當前行第一個 vivian/ 為 sky/

:%s+/oradata/apras/+/user01/apras1+ (使用+ 來 替換 / ): /oradata/apras/替換成/user01/apras1/

1.:s/vivian/sky/ 替換當前行第一個 vivian 為 sky

:s/vivian/sky/g 替換當前行所有 vivian 為 sky

2. :n,$s/vivian/sky/ 替換第 n 行開始到最後一行中每一行的第一個 vivian 為 sky

:n,$s/vivian/sky/g 替換第 n 行開始到最後一行中每一行所有 vivian 為 sky

(n 為數字,若 n 為 .,表示從當前行開始到最後一行)

3. :%s/vivian/sky/(等同於 :g/vivian/s//sky/) 替換每一行的第一個 vivian 為 sky

:%s/vivian/sky/g(等同於 :g/vivian/s//sky/g) 替換每一行中所有 vivian 為 sky

4. 可以使用 # 作為分隔符號,此時中間出現的 / 不會作為分隔符號

:s#vivian/#sky/# 替換當前行第一個 vivian/ 為 sky/

5. 刪除文本中的^M

問題描述:對於換行,window下用回車換行(0A0D)來表示,linux下是回車(0A)來表示。這樣,將window上的檔拷到unix上用時,總會有個^M.請寫個用在unix下的過濾windows文件的分行符號(0D)的shell或c程式。

。 使用命令:cat filename1 | tr -d “^V^M” > newfile;

。 使用命令:sed -e “s/^V^M//” filename > outputfilename.需要注意的是在1、2兩種方法中,^V和^M指的是Ctrl+V和Ctrl+M.你必須要手工進行輸入,而不是粘貼。

。 在vi中處理:首先使用vi打開檔,然後按ESC鍵,接著輸入命令:%s/^V^M//.

。 :%s/^M$//g

如果上述方法無用,則正確的解決辦法是: [Page]

。 tr -d \"\\r\" < src >dest

。 tr -d \"\\015\" dest

。 strings A>B
6. 替換確認
我們有很多時候會需要某個字元(串)在文章中某些位置出現時被替換,而其它位置不被替換的有選擇的操作,這就需要使用者來進行確認,vi的查找替換同樣支持
例如
:s/vivian/sky/g 替換當前行所有 vivian 為 sky
在命令後面加上一個字母c就可以實現,即:s/vivian/sky/gc
顧名思意,c是confirm的縮寫

7. 其它

利用 :s 命令可以實現字串的替換。具體的用法包括:

:s/str1/str2/ 用字串 str2 替換行中首次出現的字串 str1

:s/str1/str2/g 用字串 str2 替換行中所有出現的字串 str1

:。,$ s/str1/str2/g 用字串 str2 替換正文當前行到末尾所有出現的字串 str1

:1,$ s/str1/str2/g 用字串 str2 替換正文中所有出現的字串 str1

:g/str1/s//str2/g 功能同上

從上述替換命令可以看到:g 放在命令末尾,表示對搜索字串的每次出現進行替換;不加 g,表示只對搜索

字串的首次出現進行替換;g 放在命令開頭,表示對正文中所有包含搜索字串的行進行替換操作

(原文地址: http://wzgyantai.blogbus.com/logs/28117977.html)

3 則留言:

  1. 超讚的 没看人家寫 vi 的 search replace 寫得這麼棒的喔 !

    回覆刪除
  2. 實用 3Q!!學了幾招

    回覆刪除
  3. 謝謝,介紹得很仔細,很受用,感恩

    回覆刪除

How to use simple speedtest in RaspberryPi CLI

  pi@ChunchaiRPI2:/tmp $  wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py --2023-06-26 10:4...