前言

这篇文章将研究两个很有趣的内容:Vim编辑器与正则表达式。

「如何退出Vim编辑器?」应该是每个初学者都会遇到的问题,也是各大论坛上经常被问到,浏览量很高的一个问题。Vim让初学者如此不知所措,却又被誉为“编辑器之神”。从不会到会用是很简单的,但从会用到用得好却是一个鸿沟。这篇文章的目的之一是“Vim从会用到不会”。

大多数和我一样的学生第一次遇到正则表达式应该是在Python中。但是很遗憾,编程语言中的正则表达式和Unix中的正则表达式使用的是两个不同的规范:PCRE和POSIX。POSIX又分为BRE和ERE——Unix中的正则表达式还真让人头疼。


Vim编辑器

Vim四种工作模式

Vim的四种工作模式分别为命令模式、插入模式、命令行模式与可视模式

各模式间的切换如下:(后面会讲到)

image-20220620173447294

回答开头的问题(怎么退出vim)。使用:q:q!:wq

  1. 命令模式:刚进入Vim编辑器所处于的模式。一般在这个模式下停留最久,所以又被称为普通模式。此时我们输入的字母不会被当作文本写入文件中,而是会被当作命令解析执行。
  2. 插入模式:和我们平时使用编辑器的方式一样,输入什么,就向文件中写入什么。
  3. 命令行模式:可以执行一系列的命令。我们目前最常用的命令还是保存、退出。
  4. 可视模式:高亮选中一片区域,然后对其进行操作。它类似于鼠标的选中操作。

需要注意Vim的使用是只用到键盘的,所以才会有可视模式。至于纯键盘操作和键鼠操作哪个更高效,这是一个讨论了很久的问题了。

刚开始应该不是很懂为什么会设计一个命令模式。实际上,就像画家思考时会移开画笔而不是将画笔放在画上面一样,命令模式对程序员来说就是一个有“自然放松”的模式——这也是Vim优雅的一点所在。

由于我们通常都处于命令模式,只会在即时编辑文本的时候进入插入模式,所以需要重点掌握命令模式下的操作!

命令模式基本操作

光标的移动

命令模式下使用hjkl四个键分别进行光标在左、下、上、右方向上一个字符的移动。vimtutor对初学者记忆这四个键的建议如下:(虽然很奇怪……)

image-20220620170622864

除此之外,还有一些快速移动的操作:

  1. 0^移动光标到行首
  2. $移动光标到行尾
  3. w向后移动一个单词
  4. b向前移动光标到该单词的开头
  5. e向后移动光标到该单词的尾部

文本的修改

插入模式

插入模式可以对文本进行即时的编辑操作。我们可以使用i、I、a、A、o、O进入插入模式。它们的功能分别如下:

image-20220620180556101

  1. 区别ia的原因是当要进行插入操作时更倾向于先在命令模式下将光标移动到待插入的位置;
  2. I在行首插入和A在行尾插入可以与a在左侧插入和i在右侧插入一起记忆。
  3. oO就硬背吧,没想到什么好点的记法。关键还是得熟练。

命令模式修改

命令模式下也可以对文本进行修改,主要是删除操作:

image-20220621110836777

  1. R的进入的输入模式并不是再文本之间插入,而是向后覆盖写。所以它和ai进入的插入模式是有区别的。
  2. 需要注意删除字符的命令是x而不是delbackspace
  3. dd可以进行行删除,是一个很好用的功能。
  4. 可以使用数字+命令的方式进行重复操作。如4x可以删除当前连同其后面的总共4个字符;2dd可以删除当前连同下面的两行。进行重复操作时应该要清楚接下来光标在哪。

文本搜索

在命令模式下也可以方便地进行搜索。

  1. /[关键字]:从当前光标位置向后搜索(不包括当前位置)
  2. ?[关键字]:从当前光标位置向前搜索(不包括当前位置)

回车后输入n即可按照/?的方向继续搜索下一个匹配项。

理解“不包括当前位置”的意思。比如我想要向后搜索in:/in,且当前光标位置在include的i上面,那么就不会先匹配到include,而是会最后匹配到它。

image-20220620183604603

Vim的缓冲模型

缓冲模型

如果用户要编辑一个已经存在的文件,那么Vim的进行的工作为:

  1. 在内存中创建一个临时的缓存区,将文件从硬盘调入内存中。这个缓冲区称为工作缓冲区。
  2. 用户的修改只作用于缓冲区中的副本。最后可以选择保存:w不保存强制退出:q!。如果选择保存的话,内存中的副本就会替换原来的文件。

image-20220620184757294

如果用户要编辑的文件不存在,那么Vim进行的工作为:

  1. 在内存中创建一个临时的缓存区,直接进行编辑。
  2. 如果用户选择保存,那么就将缓冲区的内容写入硬盘空闲的空间,然后创建inode,并将文件名作为该inode的硬链接。(详见第二篇:文件系统的原理)

修改队列

Vim会维护一个修改队列来记录修改,它的目的时便于进行撤销操作u和重复上次的修改操作.,具体的原理如下;

  1. u命令回退队列,实现撤销操作。
  2. .命令将队列的最后一个元素append到队列,实现上次操作的重做。

多文件处理

编辑多个文件

之前我们都是用Vim打开一个文件,它使用到了一个buffer:工作缓冲区(实际上,一个文件也可以对应多个缓冲区)。如果要编辑多个文件,就需要创建多个缓冲区,我们只需要在shell中给Vim多个文件名即可:vim a.cpp b.cpp

这样我们就创建了两个缓冲区打开了这两个文件。在命令行模式下键入:n即可编辑下一个文件,键入:prev即可返回编辑上一个文件,使用:ar可以查看打开文件序列以及当前编辑的文件:

image-20220620195928643

需要注意的是切换到另一个文件的时候需要使用:w保存。

如果文件太多,我们可以使用:e [文件名]进行文件跳转。

总结一下:

image-20220620200555231

使用多个标签

上面方式的缺点是不清楚现在编辑的是哪个文件,所以更推荐使用tab。

创建tab的方式为:

1
2
3
4
vim //打开Vim
:tabnew a.cpp//创建a.cpp的标签
:tabnew b.cpp//创建b.cpp的标签
:tabnew c.cpp//创建c.cpp的标签

image-20220620201727426

要在tab键进行移动,可以使用的命令为:

  1. 向后移动::tabnext,简写为tabn
  2. 向前移动:tabprevious,简写为tabp
  3. tab键跳转:tabe a.cpp,跳转到a.cpp

使用:q命令即可退出当前文件并关闭它的tab。

寄存器

复制粘贴

Vim提供了一系列复制剪切粘贴的操作:

  1. 复制:在可视模式下选中文字(输入v后移动光标),然后输入y即可复制选中的文字;命令模式下输入yy可以复制整行。
  2. 剪切:使用x剪切当前光标所在的文字,使用dd剪切当前行(Vim中的删除其实都是剪切)
  3. 粘贴:使用pP即可进行粘贴。二者的区别是p在光标的右方或光标的下方粘贴文字;P在光标的左方或光标的上方粘贴文字。

认识寄存器

vim中设计了特别的暂存机制——寄存器,用于保存拷贝和删除的文本。上面的复制、剪切、粘贴操作就是借助寄存器完成的。

Vim提供了0-9共10个编号寄存器,它供Vim自己使用,存放删除和拷贝的文本;此外还有a-z共26个供用户使用的有名寄存器和一系列缺省寄存器。

Vim在各编号寄存器和缺省寄存器存放的内容如下:(寄存器以”开头,以区别命令)

image-20220621111159491

使用:registers命令可以查看当前各寄存器的内容:

image-20220621104103438

使用寄存器

结合motionrange可以对范围内的文字进行拷贝或删除操作,然后将其内容送入指定寄存器。格式为寄存器 [range] d/y/dd/yy [motion]

首先需要知道什么是motionrange

  1. motion是指移动光标的命令,包括^0$wbe。它们的意义见前面命”令模式下光标的移动”。
  2. range指定行数,包括当前行向下指定的那么多行。

如命令模式下输入:

  1. "w2yy表示将当前行和下面的一行(2行)送入"w寄存器。
  2. "zdw表示从当前光标位置向后删除一个字,并将其送入"z寄存器。

正则表达式

之前讲grep的时候,给出的命令格式为grep [选项] [搜索的字符串] [文件]。实际上,提供给grep的不仅可以是一个简单的字符串,也可以是一个正则表达式,进行强大且灵活的匹配。

正则表达式与规范

正则表达式使用单个字符串来描述,匹配一系列符合某个句法规则的字符串。它通常被用来检索、替换那些符合某个模式(规则)的文本。

Unix中的正则表达式属于POSIX流派。它包括了BRE(基本正则表达式)和ERE(扩展正则表达式)两个规范。本篇我们主要研究BRE,然后再BRE的基础上简要介绍ERE和它的不同之处。

BRE字符集

正则表达式的字符集包括普通字符元字符。普通字符是指字面含义不变的字符,按照完全匹配的方式匹配文本,而元字符具有特殊的含义,代表一类字符。下面分类介绍元字符。

匹配多个字符

首先是*.,它们在通配符中经常使用,且在正则表达式中含义是一样的:

  1. *表示匹配任意长度的任意字符串
  2. .表示匹配一个任意的字符

然后是自定义匹配多个字符的规则:

  1. [a-z],a和z可以替换为其他字符,表示一个范围。
  2. [az]要么匹配a,要么匹配z。在[]中列出多个字符,表示匹配其中一个。
  3. [^az]匹配除了az外的所有字符。[^]相当于对其中的字符取反。

要匹配多个字符,POSIX还提供了一系列字符类:

image-20220621010515479

这些字符类需要熟练使用。

image-20220621010628956

限定匹配位置

首先是两个锚点^$,它们平时也使用地比较多:

  1. ^表示锚定行首,匹配的字符串从行首开始。
  2. $表示锚定行尾,匹配的字符串从行尾开始。

需要注意^$必须分别放在正则表达式的开始和结束位置。如同时使用,表示按行匹配。

然后是定义和应用匹配位置的元字符:

  1. \(\)定义一个匹配位置,在后面可以使用位置来引用括号中的正则表达式。
  2. \n引用已经定义的位置,n可以从1取到9。

\(ab\)*\1等价于ab*ab\(ab\)定义了第一个匹配位置,\1引用第一个匹配位置的表达式。

限定匹配次数

使用\{\}可以限定匹配次数,它的格式如下:

  1. \{n,m\}:匹配次数在n-m之间
  2. \{n,\}:至少匹配n次
  3. \{,m\}:至多匹配m次

ERE和BRE对比

BRE和ERE二者的区别,简单的说就在于(){}+?|这7个特殊字符的使用方法上:

  1. 在BRE中如果想要这些特殊字符表示特定的含义,就需要把它们转义;在ERE中如果要这些字符不表示特殊的含义,就需要把它们转义。
  2. ERE中的特殊字符与BRE相比多了:+?|

第一点的区别是BRE和ERE默认的不同导致的,BRE默认特殊字符为它本来的形式;而ERE默认对其进行转义。

第二点中引入了新的字符:

  1. +表示匹配一个或多个字符
  2. ?表示匹配0个或1个字符
  3. |表示它左边的部分和右边的部分有一边匹配就行。