大家好,我是早起。
在之前的办公自动化系列文章,我们大多基于 Python 实现,因为使用 Python 具有灵活、强大的特点。使用 VIM 具有快速、可视化的优势。两者对大量同构文本进行修改,可大幅提高工作效率 。但相较于编写 Python 程序,VIM 可视化执行更胜一筹。
这也提示我们,Python 不是万能的——至少在某些方面、某些场景下,不一定是最优解 。合适的工具运用到合适的场合是效率最高的方式。不能自己是锤子,看什么就都是钉子。
在对 VIM 不熟悉的用户看来,VIM 的操作过程可能更复杂、难懂。但这是先入为主的印象,VIM 处理文本还是很方便快捷的:我们有了 Python 这把锤子,不排斥再来 VIM 这个锯嘛,这样才能“工欲善其事,必先利其器”。 本文将对比使用 Python 和 VIM 对同一个文本编辑任务处理的情况。
有大量类似结构的文本文件需要处理,目录结构如下:
E:. └─content ├─a │ └──content.txt ├─b │ └──content.txt └─c └──content.txt
其中的每个文件 content .txt内容结构如下:
"/_vsl/012A716D176AFA6EBBAF64BD4CB63BCA/994A4168/AE5BB"> "/_vsl/2ADBFFCE33AAE9B2E79E758EF6AD5626/CEFD12BB/A8DC5">
要求是:
将
标签改为
标签。 将 /_vsl/012A7
表示的相对地址,变成另一个 URL 地址,如 http://image.xx.com/image/
。 修改后的文件为:
"http://image.xx.com/image/16D176AFA6EBBAF64BD4CB63BCA_994A4168_AE5BB.png"> "http://image.xx.com/image/FCE33AAE9B2E79E758EF6AD5626_CEFD12BB_A8DC5.png">
首先让我们用 Python 编写程序来完成,代码比较简单,但面对如此简单的问题,写一个程序还是“高射炮打蚊子 ” 了。而且调试 Python 正则表达式,并不是一个直观的过程。
import os import re def rep(strs): strs = re.sub(r',r' ,strs) strs = re.sub(r',r',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 最主要好处就是:构造查找正则表达式时结果可视化 ,这样就可以逐步求精地写正则表达式,反之刚才写程序时,我得来回测试,十分费力。
下面是使用 VIM 实现需求所需要注意的几点
本例使用 VIM 中的 :%s
替换指令很容易完成替换操作。正则表达式构造需要慢慢来。 如果牵涉到复杂替换时,还需要对搜索结果分组,以便使用分组结果。 为了批量完成序列替换操作,需要将操作写入批处理脚本,再用 :source
执行脚本。 以上操作在单文件中执行,为了在许多文件中同时完成,需要使用缓冲区执行 :bufdo
命令。 3.1 构造正则表达式搜索 为了替换 ,我们构造一个查找正则表达式。
构造出的表达式如下:
/
这个表达式搜索了 开头的所有内容。
“ 在 /
指令后按向上箭头表示上一次输入的查询历史。按 q/
表示所有查询历史,可以在此历史上修改,这样就可以逐步精化。
” 3.2 替换 常规替换指令 :%s/pattern/string/g
,留空的查询域表示上次搜索的结果。在上步查询基础上,我们可以使用 :%s// 的方式完成更改。
“ 这个操作很重要:很多复杂的正则表达式,不可能一步直接构造出来;采用搜索的方法,可以高亮显示每次的搜索结果,进而改进正则表达式。而替换时留空查找域,直接表示上次搜索结果,极大方便了替换操作。使一步替换操作转换为:搜索,替换两步,降低了难度,提高了效率。
”
注意以下替换语句,使用了 \
转义字符来匹配
的特殊字符 \
。
:%s/
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/ :%s/:%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 ,用来表示抑制错误,就可以修正这个问题。
使用 VIM 中的替换指令很容易完成操作。但正则表达式构造需要慢慢来。逐步求精,还可能需要分组和非贪婪模式。批处理文件 .vim 和 :source
命令可以大大简化工作。缓冲区列表执行 :bufdo
命令则进一步提高工作效率。
VIM 编辑器处理这个问题,使用的技巧都比较通用,可以迁移到其他文本处理任务中。最主要的是,构造正则表达式的过程是直接反馈、可视化的,利于构造复杂表达式。
Python 不是万能的——至少在某些方面、某些场景下,不一定是最优解 。合适的工具运用到合适的场合是效率最高的方式。不能自已是锤子,看什么就都是钉子。
--- EOF ---