cat hotdog |sed 's/[0-9]//g'|sort|uniq -c|sort -nr -k2|more
1 正则表达式简介
正则表达式(Regular Expression)是一种描述文本(或字符串)模式的工具。正则表达式常用于查找文本的场合。想想一下我们日常生活中的例子,假如你想从电话本里找一个联系人的电话,而你又想不起联系人名字的准确拼法,你可以把电话本从头到尾翻一遍,如果电话本里联系人少的话也许并不是什么麻烦事,但想象一下如果这是记录着公司所有员工的电话本,你面临的工作便复杂得多了,有可能是从几百甚至几千条记录中寻找你想要的那条电话记录!而利用正则表达式则为解决这一问题提供了一种简便有效的方法。
正则表达式是一种描述具有某些共同特征的文本的文本,在不同的环境里有不同的格式,但其大致格式是相似的。本文将介绍Unix系统下的正则表达式。
其实,也许在你不清楚正则表达式是什么的时候你已经开始使用它了。想想Linux里一个最常用的命令:
ls *.tar
在这个命令里,*.tar 便是一个正则表达式,它是一个描述了这样的字符串的字符串:以任意数量的任意字符开头(*),后面紧接一个句点(.),然后再跟一个tar(这是在Unix Shell里表示的意思,在其他场合有不同的意思,后文将介绍)。星号(*)我们在DOS里常称之为“通配符(wildcard)”,而在正则表达式里我们称之为“元字符(metacharacter)”。下一章节我们将介绍正则表达式里常用的元字符。
2 正则表达式元字符和格式
在上一节里我们用一个例子介绍了什么是正则表达式的“元字符”。其实,元字符是一个或一组代替个或多个字符的字符。听起来有点拗口,但举一个例子也许你就明白了:元字符*用来匹配一个或多个的前一字符;而元字符 . 用来匹配一个任意的一个字符。正则表达式也可以不使用任何的元字符,一个简单的字符串 /piano/ (在Unix里正则表达式通常用一对斜线作为分隔符,后文在“正则表达式格式”部分中有介绍)也是一个正则表达式,只不过是准确匹配罢了。
下面列出了常用的正则表达式元字符,下一节将以实例的形式逐个介绍这些元字符的用法。需要注意的是,这些元字符并不是在所有的Unix (或Linux) 工具中都可以用,至于某个应用程序支持哪些元字符,可以参照该工具的用户手册。
表2.1 正则表达式元字符
元字符
功能
示例
匹配结果
^
行首定位符
/^supinfo/
匹配所有以supinfo开头的行
$
行尾定位符
/supinfo$/
匹配所有以supinfo结尾的行
\<
词首定位符
/\<supinfo/
匹配出现以supinfo为开头的词的行
\>
词尾定位符
/supinfo\>/
匹配出现以supinfo为结尾的词的行
.
匹配一个字符
/su…fo/
包含su,后面紧跟三个任意字符,然后紧跟着fo的行
*
匹配0个或多个前一字符
/_*supinfo/
supinfo前有0个或多个下划线的行
[]
匹配一组字符里的任意字符
/[Ss]pinfo/
包含Supinfo或supinfo的行
[x-y]
匹配指定范围内的字符
/[A-Z0-9]supinfo/
supinfo之前有一个A到Z或0到9的字符
[^ ]
匹配不在指定范围内的字符
/[^A-Z0-9]supinfo/
supinfo之前有一个既不是A到Z又不是0到9的字符
x\{m\}
x\{m,\}
x\{m, n\}
根据字符x出现的次数匹配:
m次;大于等于m次;大于等于m次但小于等于n次
/s\{2,5\}/
匹配有2到5个连续出现的s的行
\
转义元字符
/supinfo\. /
匹配包含supinfo,然后后面紧跟一个句点的行(没有 \ 的时候是匹配一个字符)
\(…\)
创建一个字符标签
/(SUPINFO):use\1NE/
括号中的字符被保存在标号为1的标签里,以后可以用\1来引用。标签编号从左到右依次为1,2,3……最多可以有9个标签。这个例子查找的是SUPINFO:后面跟着一个 use SUPINFONE的字符串
还有一点问题需要在这里提出,正则表达式有很多版本——至少存在两个版本:基本正则表达式和扩展正则表达式。在基本正则表达式中,原字符 | ,+ 和 ? 是不允许使用的。并且在使用含有圆括号和花括号的正则表达式时,圆括号和花括号都要用反斜线转义。在书写正则表达式时,比较好的做法是先写出整个正则表达式,然后再用反斜线将需要转义的字符转义。
正则表达式的格式
在Unix 中,正则表达式是被包含在一对斜线中的,斜线之间包含要匹配的字符(模式)。下面是几个例子:
/piano/ /[Pp]iano/ /*pinfo/ /s\{2,5\}oho/
3 正则表达式实例解析
下面我们就以具体的实例来看一下如何使用正则表达式。其中用黑体着重标出的是匹配到的字符串。
一个最简单的例子便是 /all/,比如下面一段文字:
John’s ball fell into the hole
John cried because it is all his life.
这个正则表达式不含任何的原字符,它查找的是字符串all,这个字符串all可以是独成一个单词,也可以是其它单词的一部分,因此正则表达式/all/既匹配ball里的all,也匹配完整的单词all。
下面我们着重讨论正则表达式里原字符的用法。
3.1 行首、行尾定位符
行首定位符^
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/^Bobby/
匹配位于行首的Bobby。
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/Bobby$/
匹配位于行尾的Bobby。
3.2 词首、词尾定位符
词首定位符 \<
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/\<Bo/
匹配位于词首的字符串Bo。
词尾定位符 \>
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby
Bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/ball\>/
匹配位于词尾的字符串ball。
在一个表达式中搭配使用词首定位符与词尾定位符
John’s ball fell into the hole
John cried because it is his whole life
/\<hole\>/
匹配以h作为单词开头并且以e作为单词结尾的模式hole。也就是说,字母h的前面是一个分隔单词的字符(比如空格或换行符),字母l的后面也是一个分隔单词的字符。这样,在这个例子中只有完整的单词hole会被匹配,而单词whole就不会被匹配。
3.3 匹配单个字符
匹配任意的一个字符 .
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/B…y/
匹配B开头后面紧跟三个任意字符,最后紧接着一个y的字符串。在这个例子中,Bobby和Bippy都会被匹配。
匹配0个或多个前一字符 *
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck balll
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/ al*/
这里的星号(*)匹配0个或多个在它前面的那个字符。前面曾提到过,正则表达式里的*和shell里的*作用是截然不同的。在shell里*表示任意个数的任意字符,而在正则表达式里,*只代表任意个数(包括0个)的前一字符,*可以看作和它前面那个字符是粘连在一起的,*只限制它前面那一个字符。这个正则表达式中的*匹配单独一个或多个连续的l,甚至也匹配一个l也没有的模式,所以,单个字符a也会被匹配。
3.4 匹配多个字符
匹配一组字符里的任意字符 [ ]
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck balll
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/[bw]all/
方括号匹配一组字符中的一个,这个正则表达式查找的是第一个字母是b或w,后面紧跟着all的字符串,因此在这个例子中,wall和ball都会被匹配。
匹配指定范围内的字符 [x-y]
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/B[a-z]p/
方括号里的短线(-)匹配某一范围内的一个字符,这个正则表达式将查找第一个字母是B,第二个字母是ASCII码介于a到z的字符(小写字母),第三个字母是p的字符串。
匹配不在指定范围内的字符 [^ ]
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/all[^A-Z0-9]/
方括号内的脱字符^是一个否定字符,这个正则表达式查找的是后面带一个特殊字符的all,这个特殊字符既不是小写字母又不是大写字母,也不是0到9的数字,比如它可以是一个标点符号或空格。
根据字符x出现的次数匹配 x\{m\} x\{m,\} x\{m, n\}
比如这个正则表达式:/Go\{2,5\}gle/将匹配G后面至少出现2个,最多有不超过5个o的模式。Google,Goooogle会被匹配,而Gogle和Goooooogle则不会被匹配。
3.5 转义字符
如果要匹配的字符串中含有正则表达式的原字符,需要用斜线将其转义,就像c语言里打印单引号 ’ 要写成 \’ 一样。这里有个例子:我们想要查找字符串google.com,要查找的字符串里含有正则表达式的原字符“.”,因此这个正则表达式要写成 /google\.com/,如果不用 \转义,找到的将是google后面跟一个任意的字符,然后跟一个com的字符串。这显然不一定是我们要找的。
3.6 字符标签
例如在下面一段文字里:
Occurence and happening are the most general. I mean, the words occurence and happening are most generally used.
在这段文字里有两个拼错的单词,Occurence和occurence,(其实应该是occurrence),我们可以在vi中用下面的表达式将其修改:
:1,$s/\([Oo]ccur\)ence/\1rence/
我们且不管这个vi命令的用法(其实它是一个替换命令,我们在后面介绍sed时还将提到)我们先拿出这个语句中的两个表达式:
/\([Oo]ccur\)ence/
\1rence
其中前一个是一个正则表达式。这个命令用后面的表达式内容替换前面的正则表达式匹配到的内容。vi编辑器将查找单词Occurence和occurence,如果找到,就把圆括号中的内容加上标签(Occur或occur被加上标签),因为这是第一个被标记的模式,所以被称为标签1。这个模式被保存在称为寄存器1的内存寄存器中。在第二个正则表达式中用\1引用寄存器1中的内容,\1被替换为寄存器中的内容,后面紧跟一个rence,于是,拼错的Occurence和occurence被改正为正确的Occurrence和occurrence。
3.7 原字符组合使用的例子
例1:/\<Bob.*all\>/
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/\<Bob.*all\>/
匹配以Bob开头,后面跟任意个数的任意字符,然后以all结尾的字符串。这里再次重复, *在Shell里表示任意个数的任意字符,而在正则表达式里表示任意个数的前一字符。与 . 配合使用表示任意个数(包括零个)的任意字符。实际上,* 也可以表示重复零次或任意次它前面的一组字符,我们称这一组(有时也可能是一个)字符为“原子”。当原子包括多个字符时,这多个字符要用圆括号括起来,并且需要将圆括号转义;当原子只含一个字符时,可以不用圆括号。在这个例子里,. 表示一个任意字符,紧跟着一个*表示重复0次或任意次前面的那个任意字符。而下面的例子
/\(sup\)*info/
则表示匹配在字符串info前有0个或多个sup的字符串,因此 supinfo, info, supsupinfo都会被匹配。
例2:/B[a-z][bp]*y$/
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/B[a-z][bp]*y$/
这个正则表达式匹配这样的字符串:开头字符是B,第二个字母是一个小写字母,后面紧跟0个或多个重复的b或p,最后跟一个y,并且这个字符串位于行的末尾。
4 sed原理及sed命令格式
4.1 Sed工作原理
sed是一个非交互式的流编辑器。所谓非交互式,是指使用sed只能在命令行下输入编辑命令来编辑文本,然后在屏幕上查看输出;而所谓流编辑器,是指sed每次只从文件(或输入)读入一行,然后对该行进行指定的处理,并将结果输出到屏幕(除非取消了屏幕输出又没有显式地使用打印命令),接着读入下一行。整个文件像流水一样被逐行处理然后逐行输出。
下面我们看一下sed的工作过程。
sed不是在原输入上直接进行处理的,而是先将读入的行放到缓冲区中,对缓冲区里的内容进行处理,处理完毕后也不会写回原文件(除非用shell的输出重定向来保存结果),而是直接输出到屏幕上。sed运行过程中维护着两个缓冲区,一个是活动的“模式空间(pattern space)”,另一个是起辅助作用的“暂存缓冲区(holding space)”。一般情况下,每当运行sed,sed首先把第一行装入模式空间,进行处理后输出到屏幕,然后将第二行装入模式空间替换掉模式空间里原来的内容,然后进行处理,以此类推。
插图
一般情况下暂存缓冲区是用不到的,但有特殊的命令可以在模式空间与暂存缓冲区之间交换数据,后文将有介绍。由于sed对文本的所有操作都是在缓冲区里进行的,所以不会对原文件造成任何破坏。
4.2 Sed命令格式
sed的命令格式如下:
sed [-Options] [‘Commands’] filename
其中,Command是一个sed命令,sed命令一定要被包含在一对单引号中,以免被shell解释,其格式如下:
[address-range][sed-command]或
[Pattern-to-match][sed-command]
address-range是指要处理的行的范围,又叫地址范围;pattern-to-match是一个要匹配的模式,是一个正则表达式,sed-command是一个sed命令,用来对指定的行进行处理。下面是一个简单的例子:
sed –n ‘1,3p’ students
这个命令将文件students中的第1到3行打印到屏幕。注意,地址范围和sed命令之间没有空格,如果加入空格,sed也会将其忽略。参数-n用来取消默认输出。默认情况下,sed每读入一行到模式空间,无论是否对其进行处理,在读入下一行之前多要将模式空间中的内容输出到屏幕上。参数-n可以用来取消这种默认的输出,只有当用户用命令p时才将指定的行输出到屏幕。如果没有用参数-n而又对指定行执行了p命令,那么这些行将会被打印两次。
地址范围可以是一个数字,这个数字代表了一个行号;也可以是一个用逗号分隔的两个数字表示的范围(包括这两行)。范围可以是数字,正则表达式,或是两者的组合。
pattern-to-match是一个要匹配的模式,sed将会对所有匹配的行执行sed-command。其实,这里的pattern-to-match也可以看作是一个地址,这个地址是所有与指定模式匹配的行的行号。因此sed的格式可以归纳为一种:
sed [-Options] ‘[address-range][sed-command]’ filename
5 sed命令与选项
5.1 Sed命令
常用的sed命令如下表所列:
命令
功能
a\
在当前行之后插入一行或多行
c\
用新文本替换当前行中的文本,并开始新的一轮sed命令的执行
d
删除行
i\
在当前行之前插入文本
h
将模式空间里的内容拷贝到暂存缓冲区并替换原来暂存缓冲区的内容
H
将模式空间里的内容追加到暂存缓冲区
g
将里暂存缓冲区的内容拷贝到模式空间并替换原来模式空间的内容
G
将暂存缓冲区里的内容追加到模式空间
p
打印模式空间的内容
n
读入下一行到模式空间,并接着从下一条命令开始执行
q
直接退出sed,不继续执行其后的命令
r
读入指定文件的内容
w
将行写入文件
!
对所选行以外的行进行处理
s/regexp/replacement/flag
用replacement替换模式空间由regexp匹配到的内容
x
交换模式空间与暂存缓冲区的内容
y/source-chars/dest-chars/
将source-chars的字符换成对应的的dest-chars中的字符,source-chars和dest-chars中的字符个数要相同。source-chars和dest-chars中都不能有正则表达式。
=
打印当前行的行号,行号是令起一行打印的
#
sed脚本文件中领起注释
替换命令s/regexp/replacement/flag 中的flag:
flag
功能
g
进行全局替换。不使用此选项将只对该行匹配到的第一个结果进行替换
p
打印模式空间中的内容(替换之后的内容)
w filename
将替换之后的内容写入文件filename
在后面的章节“sed实例解析”中我们将以实例的形式详细介绍各个命令的用法。
1 正则表达式简介
正则表达式(Regular Expression)是一种描述文本(或字符串)模式的工具。正则表达式常用于查找文本的场合。想想一下我们日常生活中的例子,假如你想从电话本里找一个联系人的电话,而你又想不起联系人名字的准确拼法,你可以把电话本从头到尾翻一遍,如果电话本里联系人少的话也许并不是什么麻烦事,但想象一下如果这是记录着公司所有员工的电话本,你面临的工作便复杂得多了,有可能是从几百甚至几千条记录中寻找你想要的那条电话记录!而利用正则表达式则为解决这一问题提供了一种简便有效的方法。
正则表达式是一种描述具有某些共同特征的文本的文本,在不同的环境里有不同的格式,但其大致格式是相似的。本文将介绍Unix系统下的正则表达式。
其实,也许在你不清楚正则表达式是什么的时候你已经开始使用它了。想想Linux里一个最常用的命令:
ls *.tar
在这个命令里,*.tar 便是一个正则表达式,它是一个描述了这样的字符串的字符串:以任意数量的任意字符开头(*),后面紧接一个句点(.),然后再跟一个tar(这是在Unix Shell里表示的意思,在其他场合有不同的意思,后文将介绍)。星号(*)我们在DOS里常称之为“通配符(wildcard)”,而在正则表达式里我们称之为“元字符(metacharacter)”。下一章节我们将介绍正则表达式里常用的元字符。
2 正则表达式元字符和格式
在上一节里我们用一个例子介绍了什么是正则表达式的“元字符”。其实,元字符是一个或一组代替个或多个字符的字符。听起来有点拗口,但举一个例子也许你就明白了:元字符*用来匹配一个或多个的前一字符;而元字符 . 用来匹配一个任意的一个字符。正则表达式也可以不使用任何的元字符,一个简单的字符串 /piano/ (在Unix里正则表达式通常用一对斜线作为分隔符,后文在“正则表达式格式”部分中有介绍)也是一个正则表达式,只不过是准确匹配罢了。
下面列出了常用的正则表达式元字符,下一节将以实例的形式逐个介绍这些元字符的用法。需要注意的是,这些元字符并不是在所有的Unix (或Linux) 工具中都可以用,至于某个应用程序支持哪些元字符,可以参照该工具的用户手册。
表2.1 正则表达式元字符
元字符
功能
示例
匹配结果
^
行首定位符
/^supinfo/
匹配所有以supinfo开头的行
$
行尾定位符
/supinfo$/
匹配所有以supinfo结尾的行
\<
词首定位符
/\<supinfo/
匹配出现以supinfo为开头的词的行
\>
词尾定位符
/supinfo\>/
匹配出现以supinfo为结尾的词的行
.
匹配一个字符
/su…fo/
包含su,后面紧跟三个任意字符,然后紧跟着fo的行
*
匹配0个或多个前一字符
/_*supinfo/
supinfo前有0个或多个下划线的行
[]
匹配一组字符里的任意字符
/[Ss]pinfo/
包含Supinfo或supinfo的行
[x-y]
匹配指定范围内的字符
/[A-Z0-9]supinfo/
supinfo之前有一个A到Z或0到9的字符
[^ ]
匹配不在指定范围内的字符
/[^A-Z0-9]supinfo/
supinfo之前有一个既不是A到Z又不是0到9的字符
x\{m\}
x\{m,\}
x\{m, n\}
根据字符x出现的次数匹配:
m次;大于等于m次;大于等于m次但小于等于n次
/s\{2,5\}/
匹配有2到5个连续出现的s的行
\
转义元字符
/supinfo\. /
匹配包含supinfo,然后后面紧跟一个句点的行(没有 \ 的时候是匹配一个字符)
\(…\)
创建一个字符标签
/(SUPINFO):use\1NE/
括号中的字符被保存在标号为1的标签里,以后可以用\1来引用。标签编号从左到右依次为1,2,3……最多可以有9个标签。这个例子查找的是SUPINFO:后面跟着一个 use SUPINFONE的字符串
还有一点问题需要在这里提出,正则表达式有很多版本——至少存在两个版本:基本正则表达式和扩展正则表达式。在基本正则表达式中,原字符 | ,+ 和 ? 是不允许使用的。并且在使用含有圆括号和花括号的正则表达式时,圆括号和花括号都要用反斜线转义。在书写正则表达式时,比较好的做法是先写出整个正则表达式,然后再用反斜线将需要转义的字符转义。
正则表达式的格式
在Unix 中,正则表达式是被包含在一对斜线中的,斜线之间包含要匹配的字符(模式)。下面是几个例子:
/piano/ /[Pp]iano/ /*pinfo/ /s\{2,5\}oho/
3 正则表达式实例解析
下面我们就以具体的实例来看一下如何使用正则表达式。其中用黑体着重标出的是匹配到的字符串。
一个最简单的例子便是 /all/,比如下面一段文字:
John’s ball fell into the hole
John cried because it is all his life.
这个正则表达式不含任何的原字符,它查找的是字符串all,这个字符串all可以是独成一个单词,也可以是其它单词的一部分,因此正则表达式/all/既匹配ball里的all,也匹配完整的单词all。
下面我们着重讨论正则表达式里原字符的用法。
3.1 行首、行尾定位符
行首定位符^
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/^Bobby/
匹配位于行首的Bobby。
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/Bobby$/
匹配位于行尾的Bobby。
3.2 词首、词尾定位符
词首定位符 \<
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/\<Bo/
匹配位于词首的字符串Bo。
词尾定位符 \>
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby
Bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/ball\>/
匹配位于词尾的字符串ball。
在一个表达式中搭配使用词首定位符与词尾定位符
John’s ball fell into the hole
John cried because it is his whole life
/\<hole\>/
匹配以h作为单词开头并且以e作为单词结尾的模式hole。也就是说,字母h的前面是一个分隔单词的字符(比如空格或换行符),字母l的后面也是一个分隔单词的字符。这样,在这个例子中只有完整的单词hole会被匹配,而单词whole就不会被匹配。
3.3 匹配单个字符
匹配任意的一个字符 .
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/B…y/
匹配B开头后面紧跟三个任意字符,最后紧接着一个y的字符串。在这个例子中,Bobby和Bippy都会被匹配。
匹配0个或多个前一字符 *
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck balll
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/ al*/
这里的星号(*)匹配0个或多个在它前面的那个字符。前面曾提到过,正则表达式里的*和shell里的*作用是截然不同的。在shell里*表示任意个数的任意字符,而在正则表达式里,*只代表任意个数(包括0个)的前一字符,*可以看作和它前面那个字符是粘连在一起的,*只限制它前面那一个字符。这个正则表达式中的*匹配单独一个或多个连续的l,甚至也匹配一个l也没有的模式,所以,单个字符a也会被匹配。
3.4 匹配多个字符
匹配一组字符里的任意字符 [ ]
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck balll
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/[bw]all/
方括号匹配一组字符中的一个,这个正则表达式查找的是第一个字母是b或w,后面紧跟着all的字符串,因此在这个例子中,wall和ball都会被匹配。
匹配指定范围内的字符 [x-y]
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/B[a-z]p/
方括号里的短线(-)匹配某一范围内的一个字符,这个正则表达式将查找第一个字母是B,第二个字母是ASCII码介于a到z的字符(小写字母),第三个字母是p的字符串。
匹配不在指定范围内的字符 [^ ]
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/all[^A-Z0-9]/
方括号内的脱字符^是一个否定字符,这个正则表达式查找的是后面带一个特殊字符的all,这个特殊字符既不是小写字母又不是大写字母,也不是0到9的数字,比如它可以是一个标点符号或空格。
根据字符x出现的次数匹配 x\{m\} x\{m,\} x\{m, n\}
比如这个正则表达式:/Go\{2,5\}gle/将匹配G后面至少出现2个,最多有不超过5个o的模式。Google,Goooogle会被匹配,而Gogle和Goooooogle则不会被匹配。
3.5 转义字符
如果要匹配的字符串中含有正则表达式的原字符,需要用斜线将其转义,就像c语言里打印单引号 ’ 要写成 \’ 一样。这里有个例子:我们想要查找字符串google.com,要查找的字符串里含有正则表达式的原字符“.”,因此这个正则表达式要写成 /google\.com/,如果不用 \转义,找到的将是google后面跟一个任意的字符,然后跟一个com的字符串。这显然不一定是我们要找的。
3.6 字符标签
例如在下面一段文字里:
Occurence and happening are the most general. I mean, the words occurence and happening are most generally used.
在这段文字里有两个拼错的单词,Occurence和occurence,(其实应该是occurrence),我们可以在vi中用下面的表达式将其修改:
:1,$s/\([Oo]ccur\)ence/\1rence/
我们且不管这个vi命令的用法(其实它是一个替换命令,我们在后面介绍sed时还将提到)我们先拿出这个语句中的两个表达式:
/\([Oo]ccur\)ence/
\1rence
其中前一个是一个正则表达式。这个命令用后面的表达式内容替换前面的正则表达式匹配到的内容。vi编辑器将查找单词Occurence和occurence,如果找到,就把圆括号中的内容加上标签(Occur或occur被加上标签),因为这是第一个被标记的模式,所以被称为标签1。这个模式被保存在称为寄存器1的内存寄存器中。在第二个正则表达式中用\1引用寄存器1中的内容,\1被替换为寄存器中的内容,后面紧跟一个rence,于是,拼错的Occurence和occurence被改正为正确的Occurrence和occurrence。
3.7 原字符组合使用的例子
例1:/\<Bob.*all\>/
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/\<Bob.*all\>/
匹配以Bob开头,后面跟任意个数的任意字符,然后以all结尾的字符串。这里再次重复, *在Shell里表示任意个数的任意字符,而在正则表达式里表示任意个数的前一字符。与 . 配合使用表示任意个数(包括零个)的任意字符。实际上,* 也可以表示重复零次或任意次它前面的一组字符,我们称这一组(有时也可能是一个)字符为“原子”。当原子包括多个字符时,这多个字符要用圆括号括起来,并且需要将圆括号转义;当原子只含一个字符时,可以不用圆括号。在这个例子里,. 表示一个任意字符,紧跟着一个*表示重复0次或任意次前面的那个任意字符。而下面的例子
/\(sup\)*info/
则表示匹配在字符串info前有0个或多个sup的字符串,因此 supinfo, info, supsupinfo都会被匹配。
例2:/B[a-z][bp]*y$/
Here is a tongue twister:
Bobby Bippy bought a bat.
Bobby Bippy bought a ball.
With his bat Bob banged the ball
Banged it bump against the wall
But so boldly Bobby banged it
That he burst his rubber ball, "Boo!" cried Bobby
Bad luck ball, Bad luck Bobby, bad luck ball
Now to drown his many troubles
Bobby Bippy's blowing bubbles.
/B[a-z][bp]*y$/
这个正则表达式匹配这样的字符串:开头字符是B,第二个字母是一个小写字母,后面紧跟0个或多个重复的b或p,最后跟一个y,并且这个字符串位于行的末尾。
4 sed原理及sed命令格式
4.1 Sed工作原理
sed是一个非交互式的流编辑器。所谓非交互式,是指使用sed只能在命令行下输入编辑命令来编辑文本,然后在屏幕上查看输出;而所谓流编辑器,是指sed每次只从文件(或输入)读入一行,然后对该行进行指定的处理,并将结果输出到屏幕(除非取消了屏幕输出又没有显式地使用打印命令),接着读入下一行。整个文件像流水一样被逐行处理然后逐行输出。
下面我们看一下sed的工作过程。
sed不是在原输入上直接进行处理的,而是先将读入的行放到缓冲区中,对缓冲区里的内容进行处理,处理完毕后也不会写回原文件(除非用shell的输出重定向来保存结果),而是直接输出到屏幕上。sed运行过程中维护着两个缓冲区,一个是活动的“模式空间(pattern space)”,另一个是起辅助作用的“暂存缓冲区(holding space)”。一般情况下,每当运行sed,sed首先把第一行装入模式空间,进行处理后输出到屏幕,然后将第二行装入模式空间替换掉模式空间里原来的内容,然后进行处理,以此类推。
插图
一般情况下暂存缓冲区是用不到的,但有特殊的命令可以在模式空间与暂存缓冲区之间交换数据,后文将有介绍。由于sed对文本的所有操作都是在缓冲区里进行的,所以不会对原文件造成任何破坏。
4.2 Sed命令格式
sed的命令格式如下:
sed [-Options] [‘Commands’] filename
其中,Command是一个sed命令,sed命令一定要被包含在一对单引号中,以免被shell解释,其格式如下:
[address-range][sed-command]或
[Pattern-to-match][sed-command]
address-range是指要处理的行的范围,又叫地址范围;pattern-to-match是一个要匹配的模式,是一个正则表达式,sed-command是一个sed命令,用来对指定的行进行处理。下面是一个简单的例子:
sed –n ‘1,3p’ students
这个命令将文件students中的第1到3行打印到屏幕。注意,地址范围和sed命令之间没有空格,如果加入空格,sed也会将其忽略。参数-n用来取消默认输出。默认情况下,sed每读入一行到模式空间,无论是否对其进行处理,在读入下一行之前多要将模式空间中的内容输出到屏幕上。参数-n可以用来取消这种默认的输出,只有当用户用命令p时才将指定的行输出到屏幕。如果没有用参数-n而又对指定行执行了p命令,那么这些行将会被打印两次。
地址范围可以是一个数字,这个数字代表了一个行号;也可以是一个用逗号分隔的两个数字表示的范围(包括这两行)。范围可以是数字,正则表达式,或是两者的组合。
pattern-to-match是一个要匹配的模式,sed将会对所有匹配的行执行sed-command。其实,这里的pattern-to-match也可以看作是一个地址,这个地址是所有与指定模式匹配的行的行号。因此sed的格式可以归纳为一种:
sed [-Options] ‘[address-range][sed-command]’ filename
5 sed命令与选项
5.1 Sed命令
常用的sed命令如下表所列:
命令
功能
a\
在当前行之后插入一行或多行
c\
用新文本替换当前行中的文本,并开始新的一轮sed命令的执行
d
删除行
i\
在当前行之前插入文本
h
将模式空间里的内容拷贝到暂存缓冲区并替换原来暂存缓冲区的内容
H
将模式空间里的内容追加到暂存缓冲区
g
将里暂存缓冲区的内容拷贝到模式空间并替换原来模式空间的内容
G
将暂存缓冲区里的内容追加到模式空间
p
打印模式空间的内容
n
读入下一行到模式空间,并接着从下一条命令开始执行
q
直接退出sed,不继续执行其后的命令
r
读入指定文件的内容
w
将行写入文件
!
对所选行以外的行进行处理
s/regexp/replacement/flag
用replacement替换模式空间由regexp匹配到的内容
x
交换模式空间与暂存缓冲区的内容
y/source-chars/dest-chars/
将source-chars的字符换成对应的的dest-chars中的字符,source-chars和dest-chars中的字符个数要相同。source-chars和dest-chars中都不能有正则表达式。
=
打印当前行的行号,行号是令起一行打印的
#
sed脚本文件中领起注释
替换命令s/regexp/replacement/flag 中的flag:
flag
功能
g
进行全局替换。不使用此选项将只对该行匹配到的第一个结果进行替换
p
打印模式空间中的内容(替换之后的内容)
w filename
将替换之后的内容写入文件filename
在后面的章节“sed实例解析”中我们将以实例的形式详细介绍各个命令的用法。