Shell编程—正则表达式
作者:互联网
1什么是正则表达式
1.1定义
正则表达式是你所定义的模式模板,Linux工具可以用它来过滤文本。Linux 工具(比如sed编辑器或gawk程序)能够在处理数据时使用正则表达式对数据进行模式匹配。
1.2正则表达式的类型
正则表达式是通过正则表达式引擎实现的。正则表达式引擎是一套底层软件,负责解释正则表达式模式并使用这些模式进行文本匹配。
在Linux中,有两种流行的正则表达式引擎:
- POSIX基础正则表达式(basic regular expression,BRE)引擎
- POSIX扩展正则表达式(extended regular expression,ERE)引擎
2 定义 BRE 模式
2.1 纯文本
$ echo "This is a test" | sed -n '/test/p' This is a test $ echo "This is a test" | sed -n '/trial/p' $ $ echo "This is a test" | gawk '/test/{print $0}' This is a test $ echo "This is a test" | gawk '/trial/{print $0}' $
第一个模式定义了一个单词test。sed编辑器和gawk程序脚本用它们各自的print命令打印出匹配该正则表达式模式的所有行。由于echo语句在文本字符串中包含了单词test,数据流文本能够匹配所定义的正则表达式模式,因此sed编辑器显示了该行。
第二个模式也定义了一个单词,这次是trial。因为echo语句文本字符串没包含该单词,所以正则表达式模式没有匹配,因此sed编辑器和gawk程序都没打印该行。
你可能注意到了,正则表达式并不关心模式在数据流中的位置。它也不关心模式出现了多少次。一旦正则表达式匹配了文本字符串中任意位置上的模式,它就会将该字符串传回Linux工具。
正则表达式模式都区分大小写。这意味着它们只会匹配大小写也相符的模式。
$ echo "This is a test" | sed -n '/this/p' $ $ echo "This is a test" | sed -n '/This/p' This is a test
第一次尝试没能匹配成功,因为this在字符串中并不都是小写,而第二次尝试在模式中使用大写字母,所以能正常工作。
在正则表达式中,你不用写出整个单词。只要定义的文本出现在数据流中,正则表达式就能够匹配。
$ echo "The books are expensive" | sed -n '/book/p'
The books are expensive
尽管数据流中的文本是books,但数据中含有正则表达式book,因此正则表达式模式跟数据匹配。当然,反之正则表达式就不成立了。
$ echo "The book is expensive" | sed -n '/books/p' $
你也不用局限于在正则表达式中只用单个文本单词,可以在正则表达式中使用空格和数字。
$ echo "This is line number 1" | sed -n '/ber 1/p' This is line number 1 $
在正则表达式中,空格和其他的字符并没有什么区别。
$ echo "This is line number1" | sed -n '/ber 1/p' $
如果你在正则表达式中定义了空格,那么它必须出现在数据流中。甚至可以创建匹配多个连续空格的正则表达式模式。
$ cat data1 This is a normal line of text. This is a line with too many spaces. $ sed -n '/ /p' data1 This is a line with too many spaces.
2.2特殊字符
正则表达式识别的特殊字符包括:
.*[]^${}\+?|()
如果要用某个特殊字符作为文本字符,就必须转义。在转义特殊字符时,你需要在它前面加一个特殊字符来告诉正则表达式引擎应该将接下来的字符当作普通的文本字符。这个特殊字符就是反斜线(\)。
举个例子,如果要查找文本中的美元符,只要在它前面加个反斜线。
$ cat data2 The cost is $4.00 $ sed -n '/\$/p' data2 The cost is $4.00
由于反斜线是特殊字符,如果要在正则表达式模式中使用它,你必须对其转义,这样就产生了两个反斜线。
$ echo "\ is a special character" | sed -n '/\\/p' \ is a special character
2.3锚字符
1. 锁定在行首
脱字符(^)定义从数据流中文本行的行首开始的模式。如果模式出现在行首之外的位置,正则表达式模式则无法匹配。
要用脱字符,就必须将它放在正则表达式中指定的模式前面。
$ echo "The book store" | sed -n '/^book/p'
$
$ echo "Books are great" | sed -n '/^Book/p'
Books are great
脱字符会在每个由换行符决定的新数据行的行首检查模式。
$ cat data3 This is a test line. this is another test line. A line that tests this feature. Yet more testing of this $ sed -n '/^this/p' data3 this is another test line.
只要模式出现在新行的行首,脱字符就能够发现它。
如果你将脱字符放到模式开头之外的其他位置,那么它就跟普通字符一样,不再是特殊字符了:
$ echo "This ^ is a test" | sed -n '/s ^/p' This ^ is a test
2. 锁定在行尾
跟在行首查找模式相反的就是在行尾查找。特殊字符美元符($)定义了行尾锚点。将这个特殊字符放在文本模式之后来指明数据行必须以该文本模式结尾。
$ echo "This is a good book" | sed -n '/book$/p' This is a good book $ echo "This book is good" | sed -n '/book$/p'
使用结尾文本模式的问题在于你必须要留意到底要查找什么。
$ echo "There are a lot of good books" | sed -n '/book$/p' $
3. 组合锚点
考虑下面这个例子。
$ cat data5 This is one test line. This is another test line. $ sed '/^$/d' data5 This is one test line. This is another test line.
定义的正则表达式模式会查找行首和行尾之间什么都没有的那些行。由于空白行在两个换行符之间没有文本,刚好匹配了正则表达式模式。sed编辑器用删除命令d来删除匹配该正则表达式模式的行,因此删除了文本中的所有空白行。这是从文档中删除空白行的有效方法。
2.4点号字符
特殊字符点号用来匹配除换行符之外的任意单个字符。它必须匹配一个字符,如果在点号字符的位置没有字符,那么模式就不成立。
来看一些在正则表达式模式中使用点号字符的例子。
$ cat data6 This is a test of a line. The cat is sleeping. That is a very nice hat. This test is at line four. at ten o'clock we'll go home. $ sed -n '/.at/p' data6 The cat is sleeping. That is a very nice hat. This test is at line four.
你应该能够明白为什么第一行无法匹配,而第二行和第三行就可以。第四行有点复杂。注意,我们匹配了at,但在at前面并没有任何字符来匹配点号字符。其实是有的!在正则表达式中,空格也是字符,因此at前面的空格刚好匹配了该模式。第五行证明了这点,将at放在行首就不会匹配该模式了。
2.5字符组
字符组可以定义用来匹配文本模式中某个位置的一组字符。
$ sed -n '/[ch]at/p' data6 The cat is sleeping. That is a very nice hat.
这次我们成功滤掉了只包含单词at的行。匹配这个模式的单词只有cat和hat。还要注意以at开头的行也没有匹配。字符组中必须有个字符来匹配相应的位置。
在不太确定某个字符的大小写时,字符组会非常有用。
$ echo "Yes" | sed -n '/[Yy]es/p' Yes $ echo "yes" | sed -n '/[Yy]es/p' yes
可以在单个表达式中用多个字符组。
$ echo "Yes" | sed -n '/[Yy][Ee][Ss]/p' Yes $ echo "yEs" | sed -n '/[Yy][Ee][Ss]/p' yEs $ echo "yeS" | sed -n '/[Yy][Ee][Ss]/p' yeS
正则表达式使用了3个字符组来涵盖了3个字符位置含有大小写的情况。字符组不必只含有字母,也可以在其中使用数字。
$ cat data7 This line doesn't contain a number. This line has 1 number on it. This line a number 2 on it. This line has a number 4 on it. $ sed -n '/[0123]/p' data7 This line has 1 number on it. This line a number 2 on it.
这个正则表达式模式匹配了任意含有数字0、1、2或3的行。含有其他数字以及不含有数字的行都会被忽略掉
可以将字符组组合在一起,以检查数字是否具备正确的格式,比如电话号码和邮编。但当你尝试匹配某种特定格式时,必须小心。这里有个匹配邮编出错的例子。
$ cat data8 60633 46201 223001 4353 22203 $ sed -n ' >/[0123456789][0123456789][0123456789][0123456789][0123456789]/p >' data8 60633 46201 223001 22203
这个结果出乎意料。它成功过滤掉了不可能是邮编的那些过短的数字,因为 后一个字符组没有字符可匹配。但它也通过了那个六位数,尽管我们只定义了5个字符组。
正则表达式模式可见于数据流中文本的任何位置。经常有匹配模式的字符之外的其他字符。如果要确保只匹配五位数,就必须将匹配的字符和其他字符分开,要么用空格,要么像这个例子中这样,指明它们就在行首和行尾。
$ sed -n ' > /^[0123456789][0123456789][0123456789][0123456789][0123456789]$/p > ' data8 60633 46201 22203
2.6排除型字符组
在正则表达式模式中,也可以反转字符组的作用。可以寻找组中没有的字符,而不是去寻找组中含有的字符。要这么做的话,只要在字符组的开头加个脱字符。
$ sed -n '/[^ch]at/p' data6 This test is at line four.
通过排除型字符组,正则表达式模式会匹配c或h之外的任何字符以及文本模式。由于空格字符属于这个范围,它通过了模式匹配。但即使是排除,字符组仍然必须匹配一个字符,所以以at开头的行仍然未能匹配模式。
2.7区间
可以用单破折线符号在字符组中表示字符区间。只需要指定区间的第一个字符、单破折线以及区间的后一个字符就行了。正则表达式会包括此区间内的任意字符。现在你可以通过指定数字区间来简化邮编的例子。
$ sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data8 60633 46201 45902
同样的方法也适用于字母。
$ sed -n '/[c-h]at/p' data6 The cat is sleeping. That is a very nice hat.
新的模式[c-h]at匹配了首字母在字母c和字母h之间的单词。这种情况下,只含有单词at的行将无法匹配该模式。
还可以在单个字符组指定多个不连续的区间。
$ sed -n '/[a-ch-m]at/p' data6 The cat is sleeping. That is a very nice hat.
该字符组允许区间a~c、h~m中的字母出现在at文本前,但不允许出现d~g的字母。
$ echo "I'm getting too fat." | sed -n '/[a-ch-m]at/p' $
该模式不匹配fat文本,因为它没在指定的区间。
2.8特殊的字符组
组 |
描 述 |
[[:alpha:]] |
匹配任意字母字符,不管是大写还是小写 |
[[:alnum:]] |
匹配任意字母数字字符0~9、A~Z或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 "ik" | sed -n '/ie*k/p' ik $ echo "iek" | sed -n '/ie*k/p' iek $ echo "ieek" | sed -n '/ie*k/p' ieek $ echo "ieeek" | sed -n '/ie*k/p' ieeek $ echo "ieeeek" | sed -n '/ie*k/p' ieeeek
这个模式符号广泛用于处理有常见拼写错误或在不同语言中有拼写变化的单词。举个例子,如果需要写个可能用在美式或英式英语中的脚本,可以这么写:
$ echo "I'm getting a color TV" | sed -n '/colou*r/p' I'm getting a color TV $ echo "I'm getting a colour TV" | sed -n '/colou*r/p' I'm getting a colour TV
另一个方便的特性是将点号特殊字符和星号特殊字符组合起来。这个组合能够匹配任意数量的任意字符。它通常用在数据流中两个可能相邻或不相邻的文本字符串之间。
$ echo "this is a regular pattern expression" | sed -n ' > /regular.*expression/p' this is a regular pattern expression
可以使用这个模式轻松查找可能出现在数据流中文本行内任意位置的多个单词。星号还能用在字符组上。它允许指定可能在文本中出现多次的字符组或字符区间。
$ echo "bt" | sed -n '/b[ae]*t/p' bt $ echo "bat" | sed -n '/b[ae]*t/p' bat $ echo "bet" | sed -n '/b[ae]*t/p' bet $ echo "btt" | sed -n '/b[ae]*t/p' $ $ echo "baat" | sed -n '/b[ae]*t/p' baat $ echo "baaeeet" | sed -n '/b[ae]*t/p' baaeeet $ echo "baeeaeeat" | sed -n '/b[ae]*t/p' baeeaeeat $ echo "baakeeet" | sed -n '/b[ae]*t/p'
只要a和e字符以任何组合形式出现在b和t字符之间(就算完全不出现也行),模式就能够匹配。如果出现了字符组之外的字符,该模式匹配就会不成立。
3扩展正则表达式
POSIX ERE模式包括了一些可供Linux应用和工具使用的额外符号。gawk程序能够识别ERE 模式,但sed编辑器不能。
3.1问号
问号类似于星号,不过有点细微的不同。问号表明前面的字符可以出现0次或1次,但只限于此。它不会匹配多次出现的字符。
$ echo "bt" | gawk '/be?t/{print $0}' bt $ echo "bet" | gawk '/be?t/{print $0}' bet $ echo "beet" | gawk '/be?t/{print $0}' $ $ echo "beeet" | gawk '/be?t/{print $0}' $
如果字符e并未在文本中出现,或者它只在文本中出现了1次,那么模式会匹配。与星号一样,你可以将问号和字符组一起使用。
$ echo "bt" | gawk '/b[ae]?t/{print $0}' bt $ echo "bat" | gawk '/b[ae]?t/{print $0}' bat $ echo "bot" | gawk '/b[ae]?t/{print $0}' $ $ echo "bet" | gawk '/b[ae]?t/{print $0}' bet $ echo "baet" | gawk '/b[ae]?t/{print $0}' $ $ echo "beat" | gawk '/b[ae]?t/{print $0}' $ $ echo "beet" | gawk '/b[ae]?t/{print $0}' $
如果字符组中的字符出现了0次或1次,模式匹配就成立。但如果两个字符都出现了,或者其中一个字符出现了2次,模式匹配就不成立。
3.2加号
加号是类似于星号的另一个模式符号,但跟问号也有不同。加号表明前面的字符可以出现1次或多次,但必须至少出现1次。如果该字符没有出现,那么模式就不会匹配。
$ echo "beeet" | gawk '/be+t/{print $0}' beeet $ echo "beet" | gawk '/be+t/{print $0}' beet $ echo "bet" | gawk '/be+t/{print $0}' bet $ echo "bt" | gawk '/be+t/{print $0}' $
如果字符e没有出现,模式匹配就不成立。加号同样适用于字符组,与星号和问号的使用方式相同。
$ echo "bt" | gawk '/b[ae]+t/{print $0}' $ $ echo "bat" | gawk '/b[ae]+t/{print $0}' bat $ echo "bet" | gawk '/b[ae]+t/{print $0}' bet $ echo "beat" | gawk '/b[ae]+t/{print $0}' beat $ echo "beet" | gawk '/b[ae]+t/{print $0}' beet $ echo "beeat" | gawk '/b[ae]+t/{print $0}' beeat
3.3使用花括号
ERE中的花括号允许你为可重复的正则表达式指定一个上限。这通常称为间隔(interval)。
可以用两种格式来指定区间。
- m:正则表达式准确出现m次。
- m, n:正则表达式至少出现m次,至多n次。
这个特性可以精确调整字符或字符集在模式中具体出现的次数。
默认情况下,gawk程序不会识别正则表达式间隔。必须指定gawk程序的--re- interval 命令行选项才能识别正则表达式间隔。
这里有个使用简单的单值间隔的例子。
$ echo "bt" | gawk --re-interval '/be{1}t/{print $0}' $ $ echo "bet" | gawk --re-interval '/be{1}t/{print $0}' bet $ echo "beet" | gawk --re-interval '/be{1}t/{print $0}' $
通过指定间隔为1,限定了该字符在匹配模式的字符串中出现的次数。如果该字符出现多次,模式匹配就不成立。很多时候,同时指定下限和上限也很方便。
$ echo "bt" | gawk --re-interval '/be{1,2}t/{print $0}' $ $ echo "bet" | gawk --re-interval '/be{1,2}t/{print $0}' bet $ echo "beet" | gawk --re-interval '/be{1,2}t/{print $0}' beet $ echo "beeet" | gawk --re-interval '/be{1,2}t/{print $0}' $
在这个例子中,字符e可以出现1次或2次,这样模式就能匹配;否则,模式无法匹配。间隔模式匹配同样适用于字符组。
$ echo "bt" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' $ $ echo "bat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' bat $ echo "bet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' bet $ echo "beat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' beat $ echo "beet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' beet $ echo "beeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' $ $ echo "baeet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' $ $ echo "baeaet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}' $
如果字母a或e在文本模式中只出现了1~2次,则正则表达式模式匹配;否则,模式匹配失败。
3.4管道符号
管道符号允许你在检查数据流时,用逻辑OR方式指定正则表达式引擎要用的两个或多个模式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本匹配失败。
$ echo "The cat is asleep" | gawk '/cat|dog/{print $0}' The cat is asleep $ echo "The dog is asleep" | gawk '/cat|dog/{print $0}' The dog is asleep $ echo "The sheep is asleep" | gawk '/cat|dog/{print $0}' $
这个例子会在数据流中查找正则表达式cat或dog。正则表达式和管道符号之间不能有空格,否则它们也会被认为是正则表达式模式的一部分。管道符号两侧的正则表达式可以采用任何正则表达式模式(包括字符组)来定义文本。 3
$ echo "He has a hat." | gawk '/[ch]at|dog/{print $0}' He has a hat.
这个例子会匹配数据流文本中的cat、hat或dog。
3.5表达式分组
正则表达式模式也可以用圆括号进行分组。当你将正则表达式模式分组时,该组会被视为一
个标准字符。可以像对普通字符一样给该组使用特殊字符。举个例子:
$ echo "Sat" | gawk '/Sat(urday)?/{print $0}' Sat $ echo "Saturday" | gawk '/Sat(urday)?/{print $0}' Saturday
结尾的urday分组以及问号,使得模式能够匹配完整的Saturday或缩写Sat。
将分组和管道符号一起使用来创建可能的模式匹配组是很常见的做法。
$ 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}' $
模式(c|b)a(b|t)会匹配第一组中字母的任意组合以及第二组中字母的任意组合。
4正则表达式实战
4.1 目录文件计数
$ cat countfiles #!/bin/bash # count number of files in your PATH mypath=$(echo $PATH | sed 's/:/ /g') count=0 for directory in $mypath do check=$(ls $directory) for item in $check do count=$[ $count + 1 ] done echo "$directory - $count" count=0 done $ ./countfiles /usr/local/sbin - 0 /usr/local/bin - 2 /usr/sbin - 213 /usr/bin - 1427 /sbin - 186 /bin - 152 /usr/games - 5 /usr/local/games – 0
4.2验证电话号码
在美国,电话号码有几种常见的形式:
(123)456-7890
(123) 456-7890
123-456-7890
123.456.7890
脚本如下。
$ cat isphone #!/bin/bash # script to filter out bad phone numbers gawk --re-interval '/^\(?[2-9][0-9]{2}\)?(| |-|\¬[0-9]{3}( |-|\.)[0-9]{4}/{print $0}' $ $ cat phonelist 000-000-0000 123-456-7890 212-555-1234 (317)555-1234 (202) 555-9876 33523 1234567890 234.123.4567 $ cat phonelist | ./isphone 212-555-1234 (317)555-1234 (202) 555-9876 234.123.4567
4.3解析邮件地址
邮件地址的基本格式为:
username@hostname
username值可用字母数字字符以及以下特殊字符:
- 点号
- 单破折线
- 加号
- 下划线
邮件地址的hostname部分由一个或多个域名和一个服务器名组成。服务器名和域名也必须遵照严格的命名规则,只允许字母 19 数字字符以及以下特殊字符:
- 点号
- 下划线
整个模式放在一起会生成如下模式。
^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$
这个模式会从数据列表中过滤掉那些格式不正确的邮件地址。现在可以创建脚本来实现这个正则表达式了。
$ echo "rich@here.now" | ./isemail rich@here.now $ echo "rich@here.now." | ./isemail $ $ echo "rich@here.n" | ./isemail $ $ echo "rich@here-now" | ./isemail $ $ echo "rich.blum@here.now" | ./isemail rich.blum@here.now $ echo "rich_blum@here.now" | ./isemail rich_blum@here.now $ echo "rich/blum@here.now" | ./isemail $ $ echo "rich#blum@here.now" | ./isemail $ $ echo "rich*blum@here.now" | ./isemail $
标签:字符,Shell,正则表达式,编程,gawk,echo,sed,print 来源: https://www.cnblogs.com/ericz2j/p/12045831.html