应lyh728之约, 也算是对命令行和正则表达式专题的支持, 随便写点东西介绍下正则表达式的基础概念. 由于本版偏重应用, 故只取EmEditor的正则子集来作介绍. Perl 和 CLR 的 Regex 的内容远比这类编辑器所支持的功能多.
正则表达式实在包含的内容太多, 仅仅用一篇文章来涵盖是没可能的了, 所以我只是简要的做些介绍和基本的模式应用举例. 即使这样也需要多次分章节的来连载了~~~ 闲话少说, 以下正文:
正则表达式, 英文 Regular expression, 简写Regexes或Regex.
应用概述: 提供与预期的搜索结果匹配的确切文本来进行字符串的搜索和替换操作, 这种技术不仅仅用于开发领域, 更被集成到一些常见的文本扩展编辑器, 如UltraEdit, Emeditor等. 历史上第一个实用应用程序是Unix 中的qed 编辑器。
举一个简单的类比: 我们对DOS中的通配符"*"和"?"应该很熟悉, 如命令"dir *.exe" 将列出所有后缀名为exe的文件名. 正则表达式提供的方法与其类似, 而且远比通配符强大的多.
从某种意义上说, 正则表达式是一种语言, 通过及其简短的一行代码即可以高效, 精确的描述要匹配的复杂文本, 当然, 它最大的优点也是他最大的缺点: 语法复杂, 创建困难. (熟悉之后就可以忽略后半句了 )
主要应用:
普通字符是指除了 ".", "*", "?", "+", "(", ")", "{", "}", "[", "]", "^", "$" 和 "\" 这些特殊字符之外的所有其他字符. 而这些特殊字符也可以通过前面加上"\"前缀而变为普通字符. 比如, 搜索"CCF"即为在文本中匹配所有的"CCF"字符串, 搜索"\[CCF\]"则是在文本中匹配所有的"[CCF]"字符串.2.2 元字符
简而言之, 普通字符即为只匹配自身的字符.
元字符不匹配其自身,它用特殊方式来解析从而实现更多的逻辑功能。正则表达式通过元字符在模式中包含选择和循环2.2.1 特殊字符
中括号表达式是在方括号内包含一个或多个字符构成的列表的表达式。普通字符在中括号内表示本身,大多数特殊字符在中括号表达式内出现时失去它们的意义。除了转义字符'\', (要包含'\', 需要使用'\\') 如: 正则表达式 No [1234] 匹配 No 1, No 2, No 3 和 No 4.
如果想在中括号中使用一个范围作为列表来匹配字符,可以用连字符 '-' 将范围中的开始字符和结束字符分开。单个字符的字符值确定范围内的相对顺序。如: 正则表达式 No [1-4] = No [1234]
注意 1. 开始值的Unicode值必须在结束值Unicode值的前面。
注意 2. [\-]匹配连字符'-', 放在中括号列表的开始或结尾也可起到同样的效果, 如 [-c-f] 匹配 c 至 f 的字符和连字符
如果需要匹配不属于列表或范围内的任何字符,可以在列表开头加上'^'前缀。如: 正则表达式 No [^1-4] 匹配 No 5 和更大的编号.
中括号表达式还可进行组合, 如 [A-Za-z0-9] 匹配A-Z, a-z, 0-9 的字符
字符 n 匹配字符 n
\n 匹配换行符
序列 \\ 匹配 \
序列 \( 匹配 (
(数量)限定字符
限定字符能够指定正则表达式的某个部分必须出现的次数
- * 零次或多次匹配前面的字符或子表达式。如,c*f 可以匹配 f 和 ccf。* = {0,}
- + 一次或多次匹配前面的字符或子表达式。如,c+f 可以匹配 cf 和 ccf,但不匹配 f。+ = {1,}
- ? 零次或一次匹配前面的字符或子表达式。如,cc?f 可以匹配 cf 或 ccf。? = {0,1}
- {n} n 是非负整数。正好匹配 n 次。如,c{2}f 可以匹配 ccf。
- {n,} n 是非负整数。至少匹配 n 次。如,c{2,}f 不匹配 cf,而可以匹配 ccccccf。c{1,} = c+。c{0,} = c*
- {n,m} m 和 n 是非负整数,其中 n <= m。至少匹配 n 次,至多匹配 m 次。如,c{1,3} 可以匹配 ccf 中的cc。c{0,1} 等效于 c?。
2.2.3 换码字符
- \a Bell 字符。= 0x07
- \f 换页符匹配。= 0x0C
- \n 换行符匹配。= 0x0A
- \r 匹配一个回车符。= 0x0D
- \t 制表符匹配。= 0x09
- \v 垂直制表符匹配。= 0x0B
- \e ASCII 换码字符。= 0x1B
- \0dd 八进制换码字符, dd代表八进制数字。
- \xXXXX或\x{XXXX} 4位十六进制Unicode字符, XXXX代表十六进制数字。
- \cZ Z-'@' 控制字符Control-Z, Z为大于等与"@"的ASCII字符
2.2.4 转义字符串
- \w 任一单词字符, 如A-Z, a-z, 0-9, _等, 如 \w\w\w可以匹配 U_4 但不匹配 %^e
- \W 任一非单词字符, 如 \W\W 可以匹配 *& 但不匹配 7#
- \s 任一空白字符,包括空格、制表符、换页符、回车符和垂直制表符。= [ \f\n\r\t\v]
- \S 任一非空白字符. = [^ \f\n\r\t\v]
- \d 0-9的任一数字字符, 如 \d\d可以匹配 54 但不匹配 a4
- \D 任一非数字字符. 如 \D\D可以匹配 a4 但不匹配 54
- \l a-z 之间的任一小写字符, 如 \l\l\l可以匹配 ccf 但不匹配 ccF
- \L 任一非小写字符, 如 \L\L\L可以匹配 CCF 但不匹配 cCF
- \u a-z 之间的任一大写字符, 如 \u\u\u可以匹配 CCF 但不匹配 CCf
- \U 任一非大写字符, 如 \U\U\U可以匹配 ccf 但不匹配 ccF
- \C 任一字符, = '.'
- \Q 前置引号符, 其后的任意字符均被认为普通字符直至出现后置引号符\E. 同时匹配单引号和双引号
- \E 后置引号符
2.2.5 定位字符
- alnum 任一单词字符和数字字符. = [\w\d]
- alpha 任何一个单词字符, 如A-Z, a-z, 0-9
- blank 任一空白字符,包括空格、制表符、换页符、回车符和垂直制表符。= [ \f\n\r\t\v] = \s
- cntrl 任一控制字符.
- digit 0-9的任一数字字符, = \d
- graph 任一图形字符.
- lower a-z 之间的任一小写字符 =\l
- print 任一可打印字符 = '.' = \C
- punct 任一标点符号
- space 任一空格字符
- upper a-z 之间的任一大写字符 = \u
- xdigit 4位十六进制Unicode字符, = \xXXXX
- word 任何一个单词字符, 如A-Z, a-z, 0-9, _等, = \w
- unicode 任何一个ASCII值大于255的字符
3. 分组捕获和替换
- ^ 匹配输入字符串开始的位置。如果设置customize中的"regular expressions can match new line characters",那么 ^ 还匹配 \n 或 \r 后面的位置。 但在中括号表达式中使用的情况除外,在那种情况下它对字符集求反。
- $ 匹配输入字符串结尾的位置。如果设置customize中的"regular expressions can match new line characters",那么 $ 还匹配 \n 或 \r 前面的位置。
严格的说, 后面两个分组不能称之为分组, 他们只是模式声明, 他们不能成为匹配结果, 也不能被捕获. 在正则全集中, 还有反向声明分组(?<=)(?<!)和非回溯分组(?>), 在emeditor中不被支持.
- () 组捕获. 这种分组对模式在括号内所捕获的字符进行组合, 并且每个分组捕获的匹配结果都将保存为一个实体以备其后的操作所引用. 甚至在正则全集中还可对前面的分组进行反向引用(这是题外话, emeditor不支持). 举例说明:
源文本:
使用正则表达式:代码:site status- online members: 65, online guests: 12
括号中有两个可能的匹配: members 和 guests, 只需要匹配其中任意一个; 其后是冒号和一个空格, 最后匹配至少一个数字. 匹配模式结果如下:代码:(members|guests): \d+
其中members和guests在两次匹配中被捕捉, 可以在随后的操作中引用.代码:members: 65 guests: 12
- (? 非组捕获. 这种分组仅仅对模式在括号内所匹配的字符进行组合, 模式所匹配的字符将不会作为一个组来捕获. 虽然他也同样成为最终的匹配结果的一部分, 但无法为其后的操作所引用. 同样以上例继续:
使用正则表达式:
匹配模式结果同样为:代码:(?:members|guests): \d+
但是members和guests仅仅在两次匹配中被分组, 并不被捕获, 也不可以在随后的操作中引用.代码:members: 65 guests: 12
使用非捕获组有其原因和场合. 其一, 从效率上说, 捕获一个分组需要消耗额外的资源和处理时间, 所以不应该捕获不需要使用的数据. 其二, 对模式中有多个捕获组的情况, 对不需要处理的分组进行捕获只会对分组信息造成混乱. 其三, 避免不需要贪婪匹配的场合发生贪婪匹配, 贪婪匹配是正则引擎的一个重要特性, 要说清楚其机理可能还需要另外开一个专题了. 对这一点, 还以上例说明一下:
使用不带分组的正则表达式:
匹配模式为:代码:members|guests: \d+
这个正则表达式的问题在于, 他匹配的是"members" 或 "guests: \d+", 这是模式中贪婪"消费"字符引起的. 而通过增加括号进行分组, 使正则引擎将两个匹配选项作为一个组处理, 从而正确匹配其中的一个匹配项.代码:members guests: 12
- (?=) 正声明组, 非捕获. 此分组中的模式必须出现在声明的右侧, 并且, 这个模式不构成匹配结果的一部分. 举例:
源文本:
使用正则表达式:代码:site status- online members: 65, online guests: 12
此模式中规定了\s\d+必须出现在\S+声明的右侧. 也就是说, 在至少一个非空格字符(声明)的右侧必须出现一个空格字符和至少一个数字, 而且只有这个声明构成匹配结果. 匹配模式结果如下:代码:\S+(?=\s\d+)
这两次匹配中不被捕捉.代码:members: guests:
- (?!) 负声明组, 非捕获. 此分组中的模式不得出现在声明的右侧, 并且, 这个模式不构成匹配结果的一部分. 还是用上面的例子:
使用正则表达式:
此模式中规定了","不得出现在\d{2}声明的右侧. 也就是说, 在连续两个数字(声明)的右侧不得出现逗号才能被匹配. 匹配模式结果如下:代码:\d{2}(?!,)
这两次匹配中不被捕捉.代码:12
site status- online members: 65, online guests: 12
(members|guests)
ccf-\1
members guests
site status- online ccf-members: 65, online ccf-guests: 12
(members|guests): (\d{2})
ccf-\1 = \2
members: 65 guests: 12
site status- online ccf-members = 65, online ccf-guests = 12
\d{2}
*\0*
65 12
site status- online ccf-members: *65*, online ccf-guests: *12*
- \U 大写修饰. 将其后的所有的字符替换为大写
- \L 小写修饰. 将其后的所有的字符替换为小写
- \H 半角修饰. 将其后的所有的字符替换为半角字符. 写到这里, 不得不称许一下emeditor对中文的良好支持, 这个\H至少我是很常用的, 不喜欢看到文本里面都是些123abc之类的全角字符...
- \F 全角修饰. 将其后的所有的字符替换为全角字符
- \E 关闭之前的\U, \L, \H, \F修饰.
其他相关内容:
用正则表达式可以写出复杂的匹配规则来选择文本,但匹配后的反选问题经常有人问到,在此简单总结下。
考虑一个具体的实例,有一段文本:
第一集
第一章 始动
“小卫啊,上工了。”工头老李看着一个正在埋头苦读的小伙子喊道。看到他那略显消瘦的身影,心中不由得叹息一声想到:“农家的孩子就是受苦啊,多好的孩子啊,前程就这么断送了。”再联想到自己的娃,也差不多到了该用钱的时候了,也不知道自己这点工钱够不够他上学用的?摇摇头不再乱想,转过身去上工了。
第二章 突破
连续一个星期了,陈卫还是每天都在修路,那个山头也被炸的差不多了,看样子再有几天就可以开始铺路了。
但让陈卫纳闷不已的是,他现在每天晚上都会做同一个梦。就是那个关于“影子”陈卫的梦。更离谱的是梦的内容居然也都是一摸一样的。
第三章 融合
午夜时分,天上挂着一轮明月,白白的月光把大地照得也白茫茫的一片。陈卫盘膝坐在一个白天刚炸出来的一个大坑里,全身环绕着一层紫色的光雾----没错,他正在修炼。
想提取其章节信息:
第一集
第一章 始动
第二章 突破
第三章 融合
可以很容易写出匹配章节信息的正则表达式:
^\s*第.+[章集].*$
在EmEditor中匹配效果如下:
匹配后的困惑是,如何将章节信息保留下来,而将非章节信息替换为空?逆向思维可以发现:真正的问题实际上是匹配非章节信息并将其替换为空。
我们要匹配的并不是章节信息,而是非章节信息,这就是正则的反向匹配问题。
利用正则中Lookaround可以轻松完成:
(?<=^|(第.+[章集])).*?(?=$|(第.+[章集]))
Lookaround是Lookahead和Lookbehind的统称。对于向前匹配(Lookahead)相信大家都有所接触,包括向前正向匹配(Positive Lookahead)和向前负向匹配(Negative Lookahead),语法是?=和?!. 上面的(?=$|(第.+[章集])就是向前正向匹配,表示要匹配的字符后面必须是行尾($)或者是章节标题(第.+[章集])。
类似的,理解了向前匹配,向后匹配(Lookbehind)就很容易理解了。(?<=^|(第.+[章集]))表示的含义是要匹配的字符前面必须是行首(^)或者是章节标题(第.+[章集])。
中间的.*?是非贪婪匹配任意字符。结合上面的Lookaround,在此表达的含义就是匹配所有非章节信息。
在EmEditor中匹配效果如下:
点击Replace All, 大功告成:
PS:可以进一步处理,譬如将空白行去掉,将章节缩进等等。
小结:Lookaround在正则中非常有用,经常可以利用它来完成一些看似不可完成的匹配,合理利用Lookaround能让正则如虎添翼^o^
Lookaround参考教程:http://www.regular-expressions.info/lookaround.html
Comments(3) | Add Comments补充一些非纯正则的其它方法:
1. 用grep
cat 1.txt |grep "^\s*第" > 2.txt
类似的命令行工具还有findstr等
2. 用emeditor中的宏 by 社会青年@CCF
//extracting strings matching user's Regex from a txt file document.selection.selectall(); str=document.selection.text; document.selection.collapse();; pattern=prompt("Regular expression?",""); re = new RegExp( pattern, "ig" ); editor.newfile(); while((result = re.exec(str)) != null) { document.writeln(result[0]); } 对于用正则表达式替换查找结果中的指定内容,使用如下语法
查找(^\w+). ,替换\1\t 结果是把所有行首的数字后面加点的单词后面的点去掉,换成TAB
查找<H3(.*)>,替换<H4\1> 结果是把所有<H3 ...> 都替换成<H4 ...>.
综合上述内容,替换的使用方法是:括号内的内容是保留内容,其他的内容,则是要替换的内容,因为涉及到返回基准,所以这里用1。至于是不是还有2的情况等。现在暂时不清楚。
http://jiangyuan15.blog.163.com/blog/static/4968574200711229323750/