Linux与正则表达式
正则表达式是一种符号表示法,它可以识别文本的特定模式,但是它没有一个严格的语法定义,在各种语言中的语法规则都略有不同,这里我们特指POSIX标准下的正则表达式(能兼容绝大多数命令行工具)。
Linux中万物皆文件,所以快速匹配并操作特定文本的技能是必须的,grep命令——global regular expression print,就是一个用正则表达式匹配文本并将结果输出的强大工具。
grep
grep程序以如下方式接受参数与选项。
其中字符串regex代表了一段特定的正则表达式。
下表列出了grep常用的选项。
我们接下来用简单的示例展示grep的使用。
❯ ls /usr/ > file1.txt
❯ ls /usr/bin > file2.txt
❯ ls ~/ > file3.txt
# 查找含有bzip的文件与匹配行
❯ grep bzip file*.txt
file2.txt:bzip2
file2.txt:bzip2recover
# 只输出匹配文件
❯ grep -l bzip file*.txt
file2.txt
# 只输出匹配行
❯ grep -L bzip file*.txt
file1.txt
file3.txt
元字符与文字
就像编程语言有它们自己的关键字一样,正则表达式的这些关键字被称为元字符,它们在正则表达式里被赋予了不同的含义,具有更强大的匹配功能。
^ $ . [ ] { } - ? * + ( ) | \
而非元字符的字符被称为文字字符,也就是最普通的字符,它们只能和自身进行匹配。
元字符\与其他语言类似,都是一种转义字符,可以将特定的文字字符转义为特定的控制符(比如\n),也可以将元字符转义为文字字符。
Tip
由于元字符与Shell命令里的很多特殊含义的字符(比如通配符*)重合,在使用grep之类的命令时建议把正则表达式用引号包起来,避免不必要的歧异发生。
'.': 来者不拒
.,句点,或者说点字符的含义是“任意”,它可以匹配任何一种字符,同样包括它自己。
'^' & '$': 哼哈二将
^代表以...开头,$代表以...结尾,它们可以像锚一样固定一串正则表达式的匹配对象必须以什么模式开头,又或者以什么模式结尾,又或者两者皆有。
'[]': 集体主义
[]支持你在特定的位置上匹配多种可能,但不是全部。简单来说,如果你使用[abc]ip,你会匹配到aip、bip与cip三种结果。
在[]中,^表示非,比如[^a]ip会匹配到除了aip以外.ip能匹配到的内容。但需要注意的是,仅有^在[]内最前面的时候表示非,在其他地方都会被认为是一个文字字符,并且该表达仍要求对应的位置上要有一个文字字符。
-表示字符范围,比如[a-c]ip与[abc]ip是等价的,它也可以有更多的扩展形式,比如[a-zA-Z0-9]。当然,如果你不把-放在两个字符之间,它就只是一个普通的文字字符,[-AZ]。
POSIX字符类
实际上之前我们对正则表达式的介绍都基于其遵循ASCII码表的前提下,实际上,在很多程序中,特别是POSIX兼容的,其遵循的字母表可能长这样:
这会导致[A-Z]在ASCII码表下的正则表达式与POSIX兼容应用程序的正则表达式解释不同,前者会被解释为A到Z的全部大写字母,而后者会被解释为除了a以外的所有大小写字母。
为了解决这个问题,POSI提供了一些标准的字符类以共所有遵循POSIX标准的程序通用。
扩展正则表达式
扩展正则表达式ERE是基本正则表达式BRE的进一步扩展,其支持更多的元字符。
^ $ . [ ] { } - ? * + ( ) | \ ( ) { }
一般来说,使用到ERE的需用通过egrep来进行,但是GUN下的grep可以通过启用-E选项来启用对ERE的支持。
POSIX
早期的Unix系统是很混乱的,各个生产商都在取得Unix系统源码后研发出互不完全兼容的系统,这极大增加了Unix系统程序的开发难度,毕竟你也不想写一个简单的小工具就要为其适配五花八门的系统吧(
于是20世纪80年代中期,IEEE指定了一套规范UNIX和类UNIX系统工作方式的标准,即IEEE 1003,后被提议命名为POSIX (是Portable Operating System Interface)。
'|': 中庸之道
|,也就是或,说世界不是非黑即白的,它允许目标字符串存在任意一种我们所期待的匹配模式。
❯ echo "BBB" | grep -E "AAA|BBB"
BBB
❯ echo "AAA" | grep -E "AAA|BBB"
AAA
❯ echo "CCC" | grep -E "AAA|BBB"
'?' & '*' & '+': 取之有道与狼吞虎咽
?可以匹配某个元素0次或1次,*可以匹配某个元素0次或无数次,+可以匹配某个元素1次或无数次。
它们具体要匹配什么模式取决于它们前一个字符是啥。
❯ echo "11122233333" | grep -E "^(\+86 )?[[:digit:]]+$"
11122233333
❯ echo "+86 11122233333" | grep -E "^(\+86 )?[[:digit:]]+$"
+86 11122233333
❯ echo "+86 11122233333A" | grep -E "^(\+86 )?[[:digit:]]+$"
❯ echo "11122233333" | grep -E "^(\+86 )?[[:digit:]]+$"
11122233333
'{ }': 计划匹配
{ }提供了控制匹配次数的能力,具体见下表:
有了{ },我们就可以让前面的正则表达式更加精确一点。