星期五, 四月 24, 2020

VIM学习笔记 正则表达式-捕获组(Regex-Groups)

()用于保证需要作为整体而组合出现的字符。例如表达式a\(XY\)*b将会匹配ab, aXYb, aXYXYb, aXYXYXYb。

逆向引用(Back Reference)表达式\n,用于引用之前定义的第n个捕获组。其中n为数字1–9。

例如对于文本“he fly fly flies”,表达式\(fly\) \1将匹配“fly fly”。因为\1再次引用了第一个捕获组“fly”,所以将匹配2个“fly”。

捕获组匹配


示例格式
(800)555-1212(3位数字)3位数字-4位数字
(800) 555-1212(3位数字)3位数字-4位数字
(800)-555-1212(3位数字)-3位数字-4位数字
800-555-12123位数字-3位数字-4位数字
555-12123位数字-4位数字

假设需要查找以上多种格式的电话号码,其中:

  • 可能包含区号,也可能没有区号;
  • 区号由3位数字组成;
  • 区号可能被括号包围,也可能没有括号;
  • 区号和号码之间,可能有横线或者空格相连,也可能没有任何连接符;
  • 号码由3位数字,横线连接符,和4位数字组成。

使用以下命令,可以满足以上格式需求:

/\((\d\{3})[- ]\?\|\d\{3}-\)\?\d\{3}-\d\{4}

我们将在下表中按照命令中的色彩标识,来分步理解命令中的表达式:

颜色表达式作用
\( to \)匹配区号及其后的连接符的捕获组
\?匹配0个或1个捕获组(即区号部分)
\d\{3}-\d\{4}匹配不包括区号的电话号码
\|或操作,匹配区号的多种形式
绿(\d\{3})[- ]\?匹配由括号包围的区号及之后的分隔符
\d\{3}-匹配没有括号包围的区号

由此可见,将区号及其后的连接符作为一个整体捕获为组,这样就可以通过后续的 \? 表达式来匹配0个或1个捕获组,以实现匹配包含区号和不包含区号的多种情况。

捕获组嵌套

以下文本中包含FIRSTNAME LASTNAME格式的姓名信息:

Prepared by Tommas Young
Prepared by Tommy Young

使用以下命令,可以将其转换为LASTNAME, FIRSTNAME格式:

:%s/\(Tom\%(mas\|my\)\) \(Young\)/\2, \1/g

Prepared by Young, Tommas
Prepared by Young, Tommy

从以上命令可以看到,捕获组是可以嵌套的;\%(\) 指定的组将不会被计数,这可以允许我们使用更多的组,并且查找速度也更快。

捕获组替换

假设需要将以下文本中的单引号替换为双引号:

The string contains a 'quoted' word.
The string contains 'two' quoted 'words'.
The 'string doesn't make things easy'.
The string doesn't contain any quotes, isn't it.

通过以下命令中的嵌套捕获组来完成替换操作:

:%s/\s'\(\('\w\|[^']\)\+\)'/ "\1"/g

The string contains a "quoted" word.
The string contains "two" quoted "words".
The "string doesn't make things easy".
The string doesn't contain any quotes, isn't it.

其中,\s' 用于匹配紧跟在空格之后即单词开头的单引号;\('\w\|[^']\) 则将非开头的单引号视为单词的一部分,以防止其被替换位双引号。

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期一, 四月 13, 2020

VIM学习笔记 过滤器(Filter)

过滤程序是一个接受文本作为标准输入(stdin),然后进行某些处理,并把结果放置到标准输出(stdout)的程序。可以理解为,Vim将当前缓冲区同时作为输入和输出,并借助外部程序进行文字处理。例如使用外部sort命令进行排序,然后用输出结果替换当前文本内容。

过滤器命令

使用以下格式的过滤器命令,可以利用外部程序 {filter} 过滤 {motion} 跨越的多行:

:{range}!{motion}{filter}

使用%指代整个文件,可以将所有行的字母转换为大写:

:%!tr [:lower:] [:upper:]

filter_all_lines_tr

连续点击!!键,将转到屏幕底部继续输入针对当前行的:.!命令。以下命令将本行的字母转换为大写:

:.!tr [:lower:] [:upper:]

filter_curr_lines_tr

使用以下命令,将指定行的字母转换为大写:

:4,6!tr [:lower:] [:upper:]

filter_range_lines_tr

通过在命令之前增加数字前缀,也可以实现相同的功能。首先将光标移动到第4行,然后连续点击3!!键,将转到屏幕底部继续输入针对行范围:.,.+2!的命令。以下命令,将本行及后续2行(共计3行)的字母转换为大写:

:.,.+2!tr [:lower:] [:upper:]

使用以下命令,可以将指定标记(Mark)之间的行的字母转换为大写:

:'a,'b!tr [:lower:] [:upper:]

在可视化模式下,也可以使用外部程序 {filter} 过滤高亮选中的行。例如选中文本之后,点击!键,将转到屏幕底部继续输入针对选中范围'<,'>!的命令。以下命令,将注释选中的行:

'<,'>! perl -nle 'print "\#".$_'

请注意,过滤程序将对整行进行处理,所以请使用V命令进入的行可视化模式(Linewise visual mode)。

文本处理

使用以下命令,将按照数字顺序对当前文本进行排序。请注意,此处的外部sort命令,与Vim内部的:sort命令是不同的。

:%!sort -n

使用以下命令,将在行首增加行号:

:%!cat -n

filter_cat_n

例如在当前文件中,包含以逗号分隔的多列文本:

One,Two,Three,Four,Five
arborist,apple,artichoke,ant,author
branch,banana,broccoli,bee,book

使用以下Linux命令cut,可以仅保留其中的2-3列文本:

:%!cut -f2-3 -d,

Two,Three
apple,artichoke
banana,broccoli

生成并执行Shell命令

shell环境(比如sh和cmd),也可以作为过滤器来执行外部命令。

使用以下命令,可以将当前目录下的文件列表读取到当前文件中:

:r! ls *.html

利用替代命令,可以针对文件列表组合出需要的Shell命令。例如生成mv命令,将所有HTML文件重命名为XHTML文件:

:%s/\(.*\).html/mv & \1.xhtml

使用以下命令,则可以调用Shell来执行当前文件中的命令:

:!sh

可以看到,利用过滤器可以灵活生成并执行操作系统命令,以完成例如文件复制备份等批处理操作。

假设当前文件中包含以下Windows命令:

ping -n 1 1.1.1.1
ping -n 1 1.1.1.2
ping -n 1 1.1.1.3

使用以下命令,将用文件中ping命令的输出来替换当前文本:

%!cmd

Microsoft Windows [Version 10.0.18362.720]
(c) 2019 Microsoft Corporation. All rights reserved.

D:\Temp>ping -n 1 1.1.1.1

Pinging 1.1.1.1 with 32 bytes of data:
Reply from 1.1.1.1: bytes=32 time=148ms TTL=54

Ping statistics for 1.1.1.1:
Packets: Sent = 1, Received = 1, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 148ms, Maximum = 148ms, Average = 148ms
...

在以上命令的执行过程中可以看到,Vim会将文本行保存为临时文件,然后调用外部程序进行过滤处理,然后再用输出结果替换当前文本。

filter_windows_tempdir

过滤器使用的临时文件,通常保存在以下位置:

  • Windows:$TMP、$TEMP、c:\TMP、c:\TEMP;
  • Linux:$TMPDIR、/tmp、当前目录、$HOME。

请注意,Vim将依次检查以上目录,请确保有足够的权限读写这些目录。

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期五, 四月 10, 2020

VIM学习笔记 替换字符串中的元字符(Metacharacters in Replacement Strings)

在替换和全局命令中, 某些元字符(metacharacters)在查找表达式和替换表达式中的含义是不同的。

例如以下命令,查找部分中的“.”因为有特殊意义,所有需要进行转义;而替换部分中的“.”和“$”则会被视为普通文本。

:%s/1\. Start/2. Next, start with $100/

1. Start   -------->   2. Next, start with $100

例如以下命令,会将“A”或“B”或“C”,分别替换为“[abc]”。也就是说,“[]”在替换部分也会被视为普通文本。

:%s/[ABC]/[abc]/g

ABC   -------->   [abc][abc][abc]

替换表达式中的元字符(Metacharacters)

元字符替换内容命令实例替换前替换后
\n\1-\9
查找表达式中由\(\)定义的第n个组
:%s/\(.*\) = \(.*\)/\2 = \1/x = yy = x
\l将下一个字符转换为小写:%s/Yes, Sir/\lYes, \lSir/Yes, Siryes, sir
\L将之后所有字符转换为小写:%s/Yes, Sir/\LYes, Sir/Yes, Siryes, sir
\u将下一个字符转换为大写:%s/yes, sir/\uyes, \usir/yes, sirYes, Sir
\U将之后所有字符转换为大写:%s/yes, sir/\Uyes, sir/yes, sirYES, SIR
\e结束\U和\L:%s/yes, sir/\Uyes, \esir/yes, sirYES, sir
\E:%s/yes, sir/\Uyes, \Esir/yes, sirYES, sir
\r换行符:%s/yes, sir/yes\rsiryes, siryes
sir
\转义符
将之后的特殊字符解释为普通文本
:%s/yes, sir/yes\\sir/yes, siryes\sir
&查找表达式所匹配的文本:%s/yes, sir/"&"/yes, sir"yes, sir"
~上一次替换命令中的替换文本:s/thier/their/thiertheir
:s/his/~/histhier

使用元字符交换文本位置

使用以下命令,可以交换以逗号分隔的列:

:%s/\([^,]*\),\([^,]*\),\(.*\)/\2,\1,\3/

Regex_Replacement_SwitchFields

查找表达式包括:

  • \([^,]*\),匹配除逗号之外的所有字符,即将第一列的内容捕获为组1;
  • \([^,]*\),匹配除逗号之外的所有字符,即将第二列的内容捕获为组2;
  • \(.*\),匹配剩余的所有字符,捕获为组3;

替换表达式包括:

  • \2,\1,\3,按照组2组1组3的顺序恢复各组的内容,即交换第二列与第一例的次序。

请注意,作为分隔符的逗号,在查找和替换部分均被视为普通文本。

假设需要将以下记录中的Name字段,转换为Firstname Lastname的形式:

Name: McFly, Susan S.; Areas: Graphics; Phone: 999-3333

可以使用以下命令,将逗号之前的Lastnmae捕获至组1,将分号之前的Firtname捕获至组2,然后再按照从组2到组1的顺序进行替换:

:%s/: \([^,]*\), \([^;]*\);/: \2 \1;/

Name: Susan S. McFly; Areas: Graphics; Phone: 999-3333

使用以下全局命令,则可以将替换操作限制在以“Name:”开头的行:

:g/^Name/s/: *\([^,]*\), \([^;]*\);/: \2 \1;/

使用元字符转换大小写

使用以下命令,将单词首字母转换为大写:

:%s/\<\(.\)\([^[:space:][:punct:]]*\)\>/\u\1\2/g

Regex_Replacement_TitleCase

查找表达式包括:

  • \<,匹配单词开头;
  • \(.\),匹配单词首字母,捕获为组1;
  • \([^[:space:][:punct:]]*\),匹配除空格和标点符号之外的字符串,即将单词首字母之外的其余部分捕获为组2;
  • \>,匹配单词末尾;

替换表达式包括:

  • \u,将下一个字符转换为大写;
  • \1,恢复捕获组1(即单词首字母)
  • \2,恢复捕获组2(即单词剩余部分)

使用以下命令,可以将匹配文本全部转换为大写:

:%s/Fortran/\U&/

fortran   -------->   FORTRAN

使用元字符进行精确替换

假设以下代码中rotine的名称以“test”为前缀,以“box”为后缀,而黄色高亮的中间部分则是可变的:

Regex_example_suffix_0

如果希望将后缀替换为“block”,那么可以将可变的字母作为捕获组进行替换:

:g/test\([abc]\)box/s//test\1block/g

Regex_example_suffix_1

假设需要将代码中“?dep=01”替换为:

{{path('index',{dep:01})}}

也就是说,只替换表达式的字符部分,但保留数字部分不变。可以使用(\d\d)捕获两位数字部分,然后在替换时使用\1来恢复数字:

:%s/?dep=\(\d\d\)/{{path('index',{dep:\1})}}/g

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>