批量處理文件,除了 Python,不妨試試 VIM!
↑↑↑關(guān)注后"星標(biāo)"簡(jiǎn)說(shuō)Python
人人都可以簡(jiǎn)單入門(mén)Python、爬蟲(chóng)、數(shù)據(jù)分析 簡(jiǎn)說(shuō)Python推薦 來(lái)源:早起Python 作者:大奎
大家好,我是老表
在之前的辦公自動(dòng)化系列文章,我們大多基于 Python 實(shí)現(xiàn),因?yàn)槭褂?Python 具有靈活、強(qiáng)大的特點(diǎn)。使用 VIM 具有快速、可視化的優(yōu)勢(shì)。兩者對(duì)大量同構(gòu)文本進(jìn)行修改,可大幅提高工作效率。但相較于編寫(xiě) Python 程序,VIM 可視化執(zhí)行更勝一籌。
這也提示我們,Python 不是萬(wàn)能的——至少在某些方面、某些場(chǎng)景下,不一定是最優(yōu)解。合適的工具運(yùn)用到合適的場(chǎng)合是效率最高的方式。不能自己是錘子,看什么就都是釘子。
在對(duì) VIM 不熟悉的用戶(hù)看來(lái),VIM 的操作過(guò)程可能更復(fù)雜、難懂。但這是先入為主的印象,VIM 處理文本還是很方便快捷的:我們有了 Python 這把錘子,不排斥再來(lái) VIM 這個(gè)鋸嘛,這樣才能“工欲善其事,必先利其器”。本文將對(duì)比使用 Python 和 VIM 對(duì)同一個(gè)文本編輯任務(wù)處理的情況。
01
需求說(shuō)明
有大量類(lèi)似結(jié)構(gòu)的文本文件需要處理,目錄結(jié)構(gòu)如下:
E:.
└─content
├─a
│ └──content.txt
├─b
│ └──content.txt
└─c
└──content.txt
其中的每個(gè)文件 content .txt內(nèi)容結(jié)構(gòu)如下:
<vsbimg src="/_vsl/012A716D176AFA6EBBAF64BD4CB63BCA/994A4168/AE5BB"></vsbimg>
<vsbimg src="/_vsl/2ADBFFCE33AAE9B2E79E758EF6AD5626/CEFD12BB/A8DC5"></vsbimg>
要求是:
將 <vsbimg></vsbimg>標(biāo)簽改為<img></img>標(biāo)簽。將 /_vsl/012A7表示的相對(duì)地址,變成另一個(gè) URL 地址,如http://image.xx.com/image/。將 src 的最后兩個(gè)/改為 _。 將整個(gè) src 最后加上圖片后綴 .png。
修改后的文件為:
<img src="http://image.xx.com/image/16D176AFA6EBBAF64BD4CB63BCA_994A4168_AE5BB.png"></img>
<img src="http://image.xx.com/image/FCE33AAE9B2E79E758EF6AD5626_CEFD12BB_A8DC5.png"></img>
02
Python實(shí)現(xiàn)
首先讓我們用 Python 編寫(xiě)程序來(lái)完成,代碼比較簡(jiǎn)單,但面對(duì)如此簡(jiǎn)單的問(wèn)題,寫(xiě)一個(gè)程序還是“高射炮打蚊子” 了。而且調(diào)試 Python 正則表達(dá)式,并不是一個(gè)直觀(guān)的過(guò)程。
import os
import re
def rep(strs):
strs = re.sub(r'<vsbimg',r'<img',strs)
strs = re.sub(r'<\/vsbimg',r'</img',strs)
strs = re.sub(r'(/_vsl/.*?)/',r'\1_',strs)
strs = re.sub(r'(/_vsl/.*?)/',r'\1_',strs)
strs = re.sub(r'(src=".*?)"',r'\1.png"',strs)
strs = re.sub(r'src="/_vsl/.{5}',r'src="http://image.x.com/image/',strs)
return strs
def op(fn):
fn2 = os.path.join(os.path.split(fn)[0],os.path.split(fn)[1]+'new')
with open(fn,encoding='utf-8') as f,open(fn2,'w',encoding='utf-8') as f2:
for l in f.readlines():
l = rep(l)
f2.write(l)
for r,_,fs in os.walk('content'):
for f in fs:
if f.endswith('txt'):
fn = os.path.join(r,f)
op(fn)
殺雞不用牛刀,咱們改用 VIM 試試。
VIM 最主要好處就是:構(gòu)造查找正則表達(dá)式時(shí)結(jié)果可視化,這樣就可以逐步求精地寫(xiě)正則表達(dá)式,反之剛才寫(xiě)程序時(shí),我得來(lái)回測(cè)試,十分費(fèi)力。
03
VIM實(shí)現(xiàn)
下面是使用 VIM 實(shí)現(xiàn)需求所需要注意的幾點(diǎn)
本例使用 VIM 中的 :%s替換指令很容易完成替換操作。正則表達(dá)式構(gòu)造需要慢慢來(lái)。如果牽涉到復(fù)雜替換時(shí),還需要對(duì)搜索結(jié)果分組,以便使用分組結(jié)果。 為了批量完成序列替換操作,需要將操作寫(xiě)入批處理腳本,再用 :source執(zhí)行腳本。以上操作在單文件中執(zhí)行,為了在許多文件中同時(shí)完成,需要使用緩沖區(qū)執(zhí)行 :bufdo命令。
3.1 構(gòu)造正則表達(dá)式搜索
為了替換 <vsbimg,我們構(gòu)造一個(gè)查找正則表達(dá)式。
構(gòu)造出的表達(dá)式如下:
/<vsbimg
這個(gè)表達(dá)式搜索了 <vsbimg 開(kāi)頭的所有內(nèi)容。
“在
”/指令后按向上箭頭表示上一次輸入的查詢(xún)歷史。按q/表示所有查詢(xún)歷史,可以在此歷史上修改,這樣就可以逐步精化。
3.2 替換
常規(guī)替換指令 :%s/pattern/string/g,留空的查詢(xún)域表示上次搜索的結(jié)果。在上步查詢(xún)基礎(chǔ)上,我們可以使用 :%s//<img/g 的方式完成更改。
“這個(gè)操作很重要:很多復(fù)雜的正則表達(dá)式,不可能一步直接構(gòu)造出來(lái);采用搜索的方法,可以高亮顯示每次的搜索結(jié)果,進(jìn)而改進(jìn)正則表達(dá)式。而替換時(shí)留空查找域,直接表示上次搜索結(jié)果,極大方便了替換操作。使一步替換操作轉(zhuǎn)換為:搜索,替換兩步,降低了難度,提高了效率。
”
注意以下替換語(yǔ)句,使用了 \ 轉(zhuǎn)義字符來(lái)匹配 </vsbimg> 的特殊字符 \。
:%s/<\/vsbimg/<\/img/g
3.3 搜索結(jié)果分組、使用
在對(duì) \ 轉(zhuǎn)換為 _ 的操作中,我們需要記住之前的匹配對(duì)象,用來(lái)在替換時(shí)作為不改變的內(nèi)容引用。
這里用 () 圈起來(lái)需要分組的部分,在搜索或者替換部分用 \1 表示第一個(gè)分組,以此類(lèi)推。具體看代碼:
:%s/\("\/_vsl\/.\{-1,}\)\//\1_/g
因?yàn)橛袃蓚€(gè) \,所以需要執(zhí)行兩次。
替換域里的 \1 指代的是 () 中的匹配內(nèi)容,也就是 src 從 \_vsb/ 之后遇到的第一個(gè) \ 為止的內(nèi)容。當(dāng)替換時(shí),我們依然把這部分,用 \1 使用上,只是把 \ 改為\_。
3.4 非貪婪模式
上例子可見(jiàn) .\{-1,} 的代碼,這是對(duì)任意字符進(jìn)行非貪婪匹配,以縮小 / 適配范圍,適配到第一個(gè) / 為止,不再繼續(xù)貪婪最大適配。
在給 src 添加 .png 后綴時(shí),也使用了分組和非貪婪概念。將 src 到第一個(gè)"的內(nèi)容視為一個(gè)分組,然后替換為分組內(nèi)容和 .png"。
:%s/\(src=".\{-1,}\)"/\1.png"/g
將相對(duì)地址修改為 URL 時(shí),URL 部分需要進(jìn)行很多次轉(zhuǎn)義。
:%s/src="\/_vsl\/.\{5\}/src="http:\/\/192\.168\.22\.117\/cnv\/jflyfox\/mtg\/cnvImage\//g
最后,我們把以上修改保存進(jìn)原文件:w。
以上,我們通過(guò)搜索和替換操作,完成了對(duì)單個(gè)文件的修改。
如果對(duì)每一個(gè)文件都執(zhí)行如上的程序,就顯得比較復(fù)雜了,好在 VIM 支持批處理操作。
3.5 批處理文件執(zhí)行 source
這里,我們將以上操作步驟,寫(xiě)到 oper.vim 文件中去。
:%s/<vsbimg/<img/ge
:%s/<\/vsbimg/<\/img/ge
:%s/\("\/_vsl\/.\{-1,}\)\//\1_/ge
:%s/\("\/_vsl\/.\{-1,}\)\//\1_/ge
:%s/\(src=".\{-1,}\)"/\1.png"/ge
:%s/src="\/_vsl\/.\{5\}/src="http:\/\/192\.168\.22\.117\/cnv\/jflyfox\/mtg\/cnvImage\//ge
:w
在另一個(gè)新的待處理文件中,我們輸入 :source oper.vim,就將以上所有操作在新文件中重做。
操作一個(gè)新文件可行了,如何操作大批量的文件呢?
“按
”q:表示所有替換歷史,將這些替換命令拷貝出來(lái),避免輸入帶來(lái)的麻煩和錯(cuò)誤。
3.6 緩沖區(qū)批量執(zhí)行 bufdo
VIM 的 Buffer 緩沖區(qū),相當(dāng)于內(nèi)存。當(dāng)我們具體修改某個(gè)文件時(shí),實(shí)際是在內(nèi)存中對(duì)他進(jìn)行修改,只有當(dāng)輸入 :w 命令時(shí),修改才寫(xiě)回硬盤(pán)。
使用 vim a.txt b.txt 指令,一次性打開(kāi)兩個(gè)文件,當(dāng)前訪(fǎng)問(wèn)和修改的是 a.txt。使用指令 :bnext 在緩沖區(qū)之間跳轉(zhuǎn)。指令 :ls 列出了當(dāng)前所有緩沖區(qū)文件。
使用 vim *.txt,批量打開(kāi) txt 后綴的文件。
在當(dāng)前緩沖區(qū)列表上的所有文件執(zhí)行命令,輸入 :bufdo excommand。
本文中我們打開(kāi)目錄 a,b,c 下的 content.txt 文件,使用 vim content/*/*.txt 即可。在打開(kāi)的窗口中執(zhí)行 :ls 即可查看當(dāng)前緩沖區(qū)文件。確認(rèn)無(wú)誤后,執(zhí)行 :bufdo source oper.vim ,即可完成對(duì)所有緩沖區(qū)文件的修改。
“抑制錯(cuò)誤:當(dāng)我們使用以上 vim 腳本時(shí),很容易因?yàn)樗阉饕?guī)則或者文本問(wèn)題導(dǎo)致出錯(cuò),進(jìn)而導(dǎo)致腳本停止。在每個(gè)替換語(yǔ)句之后加上 e ,用來(lái)表示抑制錯(cuò)誤,就可以修正這個(gè)問(wèn)題。
04
小結(jié)
使用 VIM 中的替換指令很容易完成操作。但正則表達(dá)式構(gòu)造需要慢慢來(lái)。逐步求精,還可能需要分組和非貪婪模式。批處理文件 .vim 和 :source 命令可以大大簡(jiǎn)化工作。緩沖區(qū)列表執(zhí)行 :bufdo 命令則進(jìn)一步提高工作效率。
VIM 編輯器處理這個(gè)問(wèn)題,使用的技巧都比較通用,可以遷移到其他文本處理任務(wù)中。最主要的是,構(gòu)造正則表達(dá)式的過(guò)程是直接反饋、可視化的,利于構(gòu)造復(fù)雜表達(dá)式。
Python 不是萬(wàn)能的——至少在某些方面、某些場(chǎng)景下,不一定是最優(yōu)解。合適的工具運(yùn)用到合適的場(chǎng)合是效率最高的方式。不能自已是錘子,看什么就都是釘子。
--END--
掃碼即可加我微信
老表朋友圈經(jīng)常有贈(zèng)書(shū)/紅包福利活動(dòng)
學(xué)習(xí)更多: 整理了我開(kāi)始分享學(xué)習(xí)筆記到現(xiàn)在超過(guò)250篇優(yōu)質(zhì)文章,涵蓋數(shù)據(jù)分析、爬蟲(chóng)、機(jī)器學(xué)習(xí)等方面,別再說(shuō)不知道該從哪開(kāi)始,實(shí)戰(zhàn)哪里找了 優(yōu)秀的讀者都知道,“點(diǎn)贊”傳統(tǒng)美德不能丟

