查看原文
其他

批量处理文件,除了 Python,不妨试试 VIM

IT服务圈儿 2022-09-11

The following article is from 早起Python Author 大奎

来源丨本文经授权转载自 早起Python(ID:Zaoqi_Python)

作者丨大奎 


在之前的办公自动化系列文章,我们大多基于 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 不是万能的——至少在某些方面、某些场景下,不一定是最优解。合适的工具运用到合适的场合是效率最高的方式。不能自已是锤子,看什么就都是钉子。

1、VS Code能自己编程了,GitHub推出“AI程序员”插件,根据注释自动补全代码

2、36 张图详解 DNS :网络世界的导航

3、Java8的Optional是不是鸡肋?

4、为什么 Python 没有函数重载?如何用装饰器实现函数重载?

点分享

点点赞

点在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存