正则表达式

Linux中大部分时间都是在处理文本内容,而为了更加快速和自动化的处理文本,我们就需要正则表达式。正则表达式提供了功能强大、灵活而又高效的方式来处理文本。正则表达式的模式匹配可以使你在大量的文本内容中快速找到特定的字符序列。验证文本来确保所匹配的模式符合某种规则:如电子邮件地址,还可以提起,编辑,删除文本字符串。对于快速处理大量的文本内容,正则表达式是不可或缺的工具。

正则表达式是你所定义的模式模版(pattern template),Linux工具可以使用它来过滤和处理文本。如grep,sed,awk`等工具都支持正则表达式。如果数据匹配模式,它就会被接受并进行下一步处理,如果数据不匹配模式则它会被丢弃。下图描述下这个过程:

文本数据 -----> 正则表达式 -------> 匹配的数据
                |
                |
                |
                ↓
            丢弃的数据

1. 正则表达式的类型

正则表达式是通过正则表达式引擎(regular expression engine)实现的。正则表达式引擎是一套底层软件,负责解释正则表达式模式并使用这些模式进行文本匹配。
Linux中,有两种流行的正则表达式引擎:

  • BRE: POSIX基础正则表达式引擎(basic regular expression)
  • ERE: POSIX扩展正则表达式引擎(extended regular expression)

2. BRE模式

最基本的BRE模式就是匹配数据流中的文本字符。

2.1 纯文本

下面使用sed程序使用正则表达式过滤数据.

$ echo "This is a test" | sed -n '/test/p'
This is a test
$ echo "This is a test" | sed -n '/tset/p'
$
$ echo "This is a test" | sed -n '/this/p'
$
$ echo "This is a test" | sed -n '/This/p'
This is a test
$

第一个例子使用test来匹配数据流中的文本。由于echo语句中包含了单词test,所以sed能匹配到该文本模式,然后使用p命令输出匹配到的文本行。第二个例子使用tset,然后文本流中并没有这个单词,所以sed并没有匹配到这个模式,就没有输出任何数据。第三个例子使用的this,这次也没有匹配成功,应该文本流中并没有小写的this,所以也没有匹配成功。第四次文本流中出现了This,所以匹配成功。

正则表达式并不关心模式匹配出现在数据流的位置,它也不关心模式出现了多少次,一旦匹配到该模式,则会把数据流中的文本行传回给Linux命令。但是正则表达式会区分大小写,它们只匹配字符序列和模式一样的文本行。匹配到的文本行中必须出现模式,而且文本流中匹配到的字符序列必须和模式中的字符顺序一样。

$ echo "There are many dog." | sed -n '/dogs/p'
$
$ echo "There are many dogs." | sed -n '/dogs/p'
There are many dogs.
$

上面的第一个例子匹配dogs,但是文本流中只有dog,所以匹配失败。第二个例子则成功的匹配到了dogs。所以模式中的字符串必须安装顺序全部出现在文本流中才能匹配成功。
如果只使用上面这张字符串匹配则并不能一次处理大量的文本。如果想一次匹配很多字符串则必须把它们都写出来,这并不能发挥正则表达式的强大能力,下面会介绍正则表达式中非常有用的一些特殊字符来构造复杂的模式。

2.2 特殊字符

有些字符在正则表达式中有特别的含义,结合这些特殊字符可以构造复杂的模式匹配。这些特殊字符包括:

.*[]^$\+?|()

因为这些特殊字符在正则表达式中具有特殊意义,如果只是想把它们作为文本字符来匹配则需要使用\来转义。而且如果想匹配反斜线也必须使用\来转义。

$ echo "\ is a special character" | sed -n '///p'
$
$ echo "\ is a special character" | sed -n '/\//p'
\ is a special character
$

2.3 锚字符

默认情况下,当匹配模式时,只要模式在数据流的任何位置出现,模式匹配都会成功。有两个字符可以现在模式在数据流中出现的位置。

2.3.1 行首

^字符定义从数据流中文本行的行首开始的模式。如果模式出现在行首则匹配成功,否则无法匹配。要使用^,必须将它放到正则表达式中模式的前面。

$ echo "Make American great again."  | sed -n '/^make/p'
$
$ echo "Make American great again."  | sed -n '/^Make/p'
Make American great again.

2.3.2 行尾

^一样,$表示模式必须出现在行尾。将这个特殊字符放在文本模式后面来指明数据必须以该文本模式结尾。

$ echo "MAKE AMERICAN GREAT AGAIN." | sed -n '/again$/p'
$
$ echo "MAKE AMERICAN GREAT AGAIN." | sed -n '/AGAIN$/p'
MAKE AMERICAN GREAT AGAIN.

2.3.2 组合锚点

在某些情况下可以在同一行中把行首锚点和行尾锚点组合起来使用。一种情况是为了查找只含有特定文本的数据行。

$ cat data1.txt
make american great again.
Make american great again.
MAKE AMERICAN GREAT AGAIN.
$ sed -n '/^MAKE AMERICAN GREAT AGAIN.$/p' data1.txt
MAKE AMERICAN GREAT AGAIN.
$

还有一个情况就是在行首锚点和行尾锚点之间不加任何文本,这样就会过滤出数据流中的空白行。

$ cat data2.txt
make american great again.

Make american great again.

MAKE AMERICAN GREAT AGAIN.
$ sed -n '/^$/d' data2.txt
make american great again.
Make american great again.
MAKE AMERICAN GREAT AGAIN.
$

上面定义的正则表达式模式会查找行首和行尾之间什么都没有的数据行。然后使用d命令来删除匹配到的空白行,因此输出的文本流中就没有空白行了。这是从文档中删除空白行的很有效的方法。

2.4 点字符

特殊字符点号用来匹配除换行符之外的任意一个字符。在点号出现的位置必须有且只有一个字符,才能匹配成功

$ cat zip.txt
bzip2
bunzip
gzip
gunzip
unzip
zip
$ sed -n '/.zip/p' zip.txt
bzip2
bunzip
gzip
gunzip
unzip
$

上面的例子中.zip表示最少得匹配4个字符,其中第一个可以是任意字符,后面三个必须是zip。所以最后一行没有匹配成功,其他则都匹配成功。

2.5 字符组

特殊字符点号在匹配某个位置上的任意字符时很有效,但是如果想限制字符的范围的时候就没有那么给力了。但是正则表达式中的字符组(character class)可以用来限定待匹配的字符是一组字符中任意一个。如果字符组中的某个字符出现在数据流中则匹配成功。
使用方括号来定义一个字符组。方括号中的字符是你希望匹配的字符,然后你就可以使用整个组来匹配字符。

$ sed -n '[bg]zip' zip.txt
bzip2
gzip
$

上面的模式匹配中的方括号就是字符组,只要匹配到其中的任何一个字符都会匹配成功。这个匹配模式可以匹配bzipgzip。字符组中的位置必须要有其中的一个字符出现才能匹配成功。字符组中不仅能使用字符,也可以使用数字。

$ cat data3.txt
This is test line0.
This is test line1.
This is test line2.
This is test line3.
This is a end line.
$ sed -n '/[12345]/p' data3.txt
This is test line1.
This is test line2.
This is test line3.
$

这个正则表达式模式匹配数据行中有数字0,1,2,3,4,5的行。其他数字以及没有数字的行都会被过滤。
可以讲字符组组合在一起以检查特定的字符序列是否符合某种格式:比如电话号码和邮编。

cat data4.txt
606333
462018
2230011
4353
51900
$ sed -n '
> /[0123456789][0123456789][0123456789][0123456789][0123456789][0123456789]/
> ' data4.txt
606333
462018
2230011

上面的例子成功的过滤了6位数以下的邮编,但是它也让7位数的邮编通过了模式匹配。正则表达式只有在文本流中匹配到了最小的模式字符串就匹配成功,上面的例子中7位数字中的前6位已经匹配成功,则整行文本都会匹配成功。如果想只匹配长度为6个数字的邮编则可以使用行首行尾限定符:

$ sed -n '
> /^[0123456789][0123456789][0123456789][0123456789][0123456789][0123456789]$/
> ' data4.txt
606333
462018
$

2.6 区间

上面的例子只是匹配6个数字的邮编,如果我们想匹配11位的移动手机号码,如果按照上面的方式...:( 。好在有一种可以方式可以避免写那么多字母和数字。这就是区间,只要在方括号的开始和结束指定一个字符,中间使用破折号分隔这两个字符就可以了。现在可以优化下上面的邮编的例子:

$ sed -n '
> /^[0-9][0-9][0-9][0-9][0-9][0-9]$/
> ' data4.txt
606333
462018
$

这个模式和上面的是一样的,只能匹配6位数字。同样的方法也使用于字母字符:

$ sed -n '/[c-h]at/p' data5.txt
The cat is sleeping.
That is a very nice hat.
$

上面的例子中[c-h]匹配ch之间的所有字母。还可以在单个字符组中指定多个字符区间:

$ sed -n '/[c-hx-z]at/p' data5.txt
The cat is sleeping.
That is a very nice hat.
$

2.7 排除型字符组

在正则表达式中我们也可以反转字符组的作用。就是匹配不在字符组中的字符。在字符组的前面加上^,就可以匹配字符组之外的字符了。

$ sed -n '/[^c-h]at/p' data5.txt
$

通过排除型字符组,可以匹配除了ch之间的所有字符。

2.8 特殊的字符组

除了自己定义的字符组之外,BRE还包含了一些特殊的字符组,可以用来匹配特定类型的字符。

描述
[[:alpha:]] 匹配任意字母字符,不管是大写还是小写
[[:alnum:]] 匹配任意字母数字字符09、AZ或a~z
[[:blank:]] 匹配空格或制表符
[[:digit:]] 匹配0~9之间的数字
[[:lower:]] 匹配小写字母字符a~z
[[:print:]] 匹配任意可打印字符
[[:punct:]] 匹配标点符号
[[:space:]] 匹配任意空白字符:空格、制表符、NL、FF、VT和CR
[[:upper:]] 匹配任意大写字母字符A~Z

可以在正则表达式中将特殊表达式当作普通字符组使用:

$ echo "abc" | sed -n '/[[:digit:]]/p' 
$
$ echo "abc" | sed -n '/[[:alpha:]]/p'
abc
$ echo "abc123" | sed -n '/[[:digit:]]/p'
abc123
$ echo "This is, a test" | sed -n '/[[:punct:]]/p'
This is, a test
$ echo "This is a test" | sed -n '/[[:punct:]]/p'
$

2.9 星号

在字符后面放置星号表面前面的字符必须在模式匹配中出现0次或多次。

$ echo "k" | sed -n '/o*k/p'
k
$ echo "ok" | sed -n '/o*k/p'
ok
$ echo "oooook" | sed -n '/o*k/p'
oooook
$

o*表示模式中可以有0个o,1个o,或者多个o

3. 扩展正则表达式

POSIX ERE模式包括了BRE而且还提供了一些额外的特殊符号。下面来介绍ERE模式中剩下的特殊符号。

3.1 问号

问号类似于星号,但是问号和星号又不同。问号表示前面的字符只能出现0次或1次,不能是其他次数。就是说问号前面的字符要么不出现,要么只出现一次,其他情况都会匹配失败。

$ echo "ct" | gawk '/ca?t/{print $0}'
ct
$ echo "cat" | gawk '/ca?t/{print $0}'
cat
$ echo "caat" | gawk '/ca?t/{print $0}'
$
$ echo "caaat" | gawk '/ca?t/{print $0}'
$

?表示前面的字符只能出现0次或1次,其他情况都不会匹配成功。?也可以和字符组一起使用:

$ echo "ct" | gawk '/c[au]?t/{print $0}'
ct
$ echo "cat" | gawk '/c[au]?t/{print $0}'
cat
$ echo "cut" | gawk '/c[au]?t/{print $0}'
cut
$ echo "cot" | gawk '/c[au]?t/{print $0}'
$
$ echo "caut" | gawk '/c[au]?t/{print $0}'
$
$ echo "caet" | gawk '/c[au]?t/{print $0}'
$

3.2 加号

+*?类似,加号表示前面的字符最少出现1次。如果加号前面的字符没有出现则匹配失败。

$ echo "ct" | gawk '/ca+t/{print $0}' 
$
$ echo "cat" | gawk '/ca+t/{print $0}' 
cat
$ echo "cut" | gawk '/ca+t/{print $0}' 
$
$ echo "caat" | gawk '/ca+t/{print $0}' 
caat
$ echo "caet" | gawk '/ca+t/{print $0}' 
$
$ echo "caaat" | gawk '/ca+t/{print $0}' 
caaat
$

如果a字符没有出现则匹配失败。+也可以作用于字符组:

$ echo "ct" | gawk '/c[au]+t/{print $0}'
$
$ echo "cat" | gawk '/c[au]+t/{print $0}'
cat
$ echo "cut" | gawk '/c[au]+t/{print $0}'
cut
$ echo "caat" | gawk '/c[au]+t/{print $0}'
caat
$ echo "caut" | gawk '/c[au]+t/{print $0}'
caut
$ echo "caaaaut" | gawk '/c[au]+t/{print $0}'
caaaaut
$

3.3 花括号

ERE中的花括号允许前面的模式可以重复出现的次数。这通过称为间隔(interval)。可以用四种方式来指定:

限定符 描述
{n} 匹配前面的元素,如果它确切地出现了 n 次。
{n,m} 匹配前面的元素,如果它至少出现了 n 次,但是不多于 m 次。
{n,} 匹配前面的元素,如果它出现了 n 次或多于 n 次。
{,m} 匹配前面的元素,如果它出现的次数不多于 m 次。
$ echo "ct" | grep -E 'ca{1}t'
$
$ echo "cat" | grep -E 'ca{1}t'
cat
$ echo "caat" | grep -E 'ca{1}t'
$

设置间隔为1则表示a必须且只能出现一次,其他情况都不会匹配成功。grep -E可以识别ERE模式下的正则表达式。也可以单独设置重复出现的最少次数和最大次数,也可以设置一起设置最小和最大次数。

3.4 Alternation(交替)

管道符号允许你在检查正则表达式时,用逻辑OR方式指定多个模式。如果匹配了其中任何一个单独的模式则整个模式匹配成功,如果没有模式匹配上,则数据流匹配失败。使用管道符号的格式如下:

expr1 | expr2 | ...

$ echo "There is a dog." | gawk '/cat|dog/{print $0}'
There is a dog.
$ echo "There is a cat." | gawk '/cat|dog/{print $0}'
There is a cat.
$ echo "He has a cat" | gawk '/[ch]at|dog/{print $0}'
He has a cat
$

管道符号两侧的正则表达式可以采用任何形式的正则表达式来定义文本(包括字符组)。

3.5 表达式分组

可以使用圆括号对正则表达式分组,当你将正则表达式分组时,该组会被视为一个标准字符。可以像普通字符一样给改组使用特殊字符。

$ echo "Sat" | gawk '/Sat(urday)?/{print $0}'
Sat
$ echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
Saturday
$

结尾的urday分组就像一个普通字符,后面的问号表示urday可以不出现或者只出现一次。将分组和管道一起使用是很常见的做法。

$ echo "cat" | gawk '/(c|b)a(b|t)/{print $0}'
cat
$ echo "cab" | gawk '/(c|b)a(b|t)/{print $0}'
cab
$ echo "bat" | gawk '/(c|b)a(b|t)/{print $0}'
bat
$ echo "bab" | gawk '/(c|b)a(b|t)/{print $0}'
bab
$ echo "tab" | gawk '/(c|b)a(b|t)/{print $0}'
$
$ echo "tac" | gawk '/(c|b)a(b|t)/{print $0}'
$

上面就是 POSIX的正则表达式。每种编程语言都会在 POSIX基础上提供更加丰富的符号集。上面只是最基本的正则表达式,其他更加复杂的就需要你们自己去学习了。加油 :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容