批量处理文件,除了 Python,不妨试试 VIM!
在之前的办公自动化系列文章,我们大多基于 Python 实现,因为使用 Python 具有灵活、强大的特点。使用 VIM 具有快速、可视化的优势。两者对大量同构文本进行修改,可大幅提高工作效率。但相较于编写 Python 程序,VIM 可视化执行更胜一筹。
这也提示我们,Python 不是万能的——至少在某些方面、某些场景下,不一定是最优解。合适的工具运用到合适的场合是效率最高的方式。不能自己是锤子,看什么就都是钉子。
在对 VIM 不熟悉的用户看来,VIM 的操作过程可能更复杂、难懂。但这是先入为主的印象,VIM 处理文本还是很方便快捷的:我们有了 Python 这把锤子,不排斥再来 VIM 这个锯嘛,这样才能“工欲善其事,必先利其器”。本文将对比使用 Python 和 VIM 对同一个文本编辑任务处理的情况。
01
需求说明
有大量类似结构的文本文件需要处理,目录结构如下:
E:.
└─content
├─a
│ └──content.txt
├─b
│ └──content.txt
└─c
└──content.txt
其中的每个文件 content .txt内容结构如下:
<vsbimg src="/_vsl/012A716D176AFA6EBBAF64BD4CB63BCA/994A4168/AE5BB"></vsbimg>
<vsbimg src="/_vsl/2ADBFFCE33AAE9B2E79E758EF6AD5626/CEFD12BB/A8DC5"></vsbimg>
要求是:
将 <vsbimg></vsbimg>
标签改为<img></img>
标签。将 /_vsl/012A7
表示的相对地址,变成另一个 URL 地址,如http://image.xx.com/image/
。将 src 的最后两个/改为 _。 将整个 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实现
首先让我们用 Python 编写程序来完成,代码比较简单,但面对如此简单的问题,写一个程序还是“高射炮打蚊子” 了。而且调试 Python 正则表达式,并不是一个直观的过程。
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 最主要好处就是:构造查找正则表达式时结果可视化,这样就可以逐步求精地写正则表达式,反之刚才写程序时,我得来回测试,十分费力。
03
VIM实现
下面是使用 VIM 实现需求所需要注意的几点
本例使用 VIM 中的 :%s
替换指令很容易完成替换操作。正则表达式构造需要慢慢来。如果牵涉到复杂替换时,还需要对搜索结果分组,以便使用分组结果。 为了批量完成序列替换操作,需要将操作写入批处理脚本,再用 :source
执行脚本。以上操作在单文件中执行,为了在许多文件中同时完成,需要使用缓冲区执行 :bufdo
命令。
3.1 构造正则表达式搜索
为了替换 <vsbimg
,我们构造一个查找正则表达式。
构造出的表达式如下:
/<vsbimg
这个表达式搜索了 <vsbimg
开头的所有内容。
“在
”/
指令后按向上箭头表示上一次输入的查询历史。按q/
表示所有查询历史,可以在此历史上修改,这样就可以逐步精化。
3.2 替换
常规替换指令 :%s/pattern/string/g
,留空的查询域表示上次搜索的结果。在上步查询基础上,我们可以使用 :%s//<img/g
的方式完成更改。
“这个操作很重要:很多复杂的正则表达式,不可能一步直接构造出来;采用搜索的方法,可以高亮显示每次的搜索结果,进而改进正则表达式。而替换时留空查找域,直接表示上次搜索结果,极大方便了替换操作。使一步替换操作转换为:搜索,替换两步,降低了难度,提高了效率。
”
注意以下替换语句,使用了 \
转义字符来匹配 </vsbimg>
的特殊字符 \
。
:%s/<\/vsbimg/<\/img/g
3.3 搜索结果分组、使用
在对 \
转换为 _
的操作中,我们需要记住之前的匹配对象,用来在替换时作为不改变的内容引用。
这里用 ()
圈起来需要分组的部分,在搜索或者替换部分用 \1
表示第一个分组,以此类推。具体看代码:
:%s/\("\/_vsl\/.\{-1,}\)\//\1_/g
因为有两个 \
,所以需要执行两次。
替换域里的 \1
指代的是 ()
中的匹配内容,也就是 src
从 \_vsb/
之后遇到的第一个 \
为止的内容。当替换时,我们依然把这部分,用 \1
使用上,只是把 \
改为\_
。
3.4 非贪婪模式
上例子可见 .\{-1,}
的代码,这是对任意字符进行非贪婪匹配,以缩小 /
适配范围,适配到第一个 /
为止,不再继续贪婪最大适配。
在给 src 添加 .png
后缀时,也使用了分组和非贪婪概念。将 src 到第一个"的内容视为一个分组,然后替换为分组内容和 .png"
。
:%s/\(src=".\{-1,}\)"/\1.png"/g
将相对地址修改为 URL 时,URL 部分需要进行很多次转义。
:%s/src="\/_vsl\/.\{5\}/src="http:\/\/192\.168\.22\.117\/cnv\/jflyfox\/mtg\/cnvImage\//g
最后,我们把以上修改保存进原文件:w
。
以上,我们通过搜索和替换操作,完成了对单个文件的修改。
如果对每一个文件都执行如上的程序,就显得比较复杂了,好在 VIM 支持批处理操作。
3.5 批处理文件执行 source
这里,我们将以上操作步骤,写到 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
在另一个新的待处理文件中,我们输入 :source oper.vim
,就将以上所有操作在新文件中重做。
操作一个新文件可行了,如何操作大批量的文件呢?
“按
”q:
表示所有替换历史,将这些替换命令拷贝出来,避免输入带来的麻烦和错误。
3.6 缓冲区批量执行 bufdo
VIM 的 Buffer 缓冲区,相当于内存。当我们具体修改某个文件时,实际是在内存中对他进行修改,只有当输入 :w
命令时,修改才写回硬盘。
使用 vim a.txt b.txt
指令,一次性打开两个文件,当前访问和修改的是 a.txt
。使用指令 :bnext
在缓冲区之间跳转。指令 :ls
列出了当前所有缓冲区文件。
使用 vim *.txt
,批量打开 txt 后缀的文件。
在当前缓冲区列表上的所有文件执行命令,输入 :bufdo excommand
。
本文中我们打开目录 a,b,c 下的 content.txt 文件,使用 vim content/*/*.txt
即可。在打开的窗口中执行 :ls
即可查看当前缓冲区文件。确认无误后,执行 :bufdo source oper.vim
,即可完成对所有缓冲区文件的修改。
“抑制错误:当我们使用以上 vim 脚本时,很容易因为搜索规则或者文本问题导致出错,进而导致脚本停止。在每个替换语句之后加上 e ,用来表示抑制错误,就可以修正这个问题。
04
小结
使用 VIM 中的替换指令很容易完成操作。但正则表达式构造需要慢慢来。逐步求精,还可能需要分组和非贪婪模式。批处理文件 .vim 和 :source
命令可以大大简化工作。缓冲区列表执行 :bufdo
命令则进一步提高工作效率。
VIM 编辑器处理这个问题,使用的技巧都比较通用,可以迁移到其他文本处理任务中。最主要的是,构造正则表达式的过程是直接反馈、可视化的,利于构造复杂表达式。
Python 不是万能的——至少在某些方面、某些场景下,不一定是最优解。合适的工具运用到合适的场合是效率最高的方式。不能自已是锤子,看什么就都是钉子。
推荐阅读