正则指引——Linux/UNIX

Linux/UNIX

1、POSIX

如果你用过vi、grep、awk、sed之类的工具,或许会记得:这些工具虽然支持正则表达式,但语法却很不一样,按照通常习惯的方法写的正则表达式往往不是无法识别((abc)竟然可能报错)就是匹配错误(\d无法匹配数字字符)​。而且,这些工具自身之间也存在差异,同样的结构,有时需要转义有时不需要转义。这究竟是为什么呢?

原因在于,Linux/UNIX下的工具大多采用POSIX规范,而POSIX规范又可分为两种流派(flavor)​。之前只是简单介绍了POSIX字符组,现在详细讲解POSIX规范。

1.1、POSIX规范

我们之前介绍的正则表达式的记法,其实都源于Perl,实际上,正则表达式从Perl衍生出一个显赫的流派,叫作PCRE(Perl Compatible Regular Expression)​,\d\w\s之类的字符组简记法是它的特征。但是在PCRE之外,正则表达式还有其他流派,比如POSIX规范的正则表达式。

POSIX的全称是Portable Operating System Interfacefor uniX,它由一系列规范构成,定义了UNIX操作系统应当支持的功能,所以"POSIX规范的正则表达式"其实只是 "关于正则表达式的POSIX规范"​,它定义了BRE(BasicRegular Expression,基本型正则表达式)ERE(Extended Regular Expression,扩展型正则表达式)两大流派。

在兼容POSIX的UNIX系统上,grep和egrep之类的工具都遵循POSIX规范,一些数据库系统中的正则表达式也符合POSIX规范。

1.1.1、BRE

grep、vi、sed属于BRE这一派,它的语法看起来比较奇怪,元字符(){}必须转义之后才具有特殊含义,比如:

  • (a)b只能匹配(a)b而不是ab
  • a{1,2}只能匹配字符串a{1,2}a\{1,2\}才能匹配字符串a或者aa

之所以这么麻烦,是因为正则表达式的许多功能是逐步出现的,一开始这些元字符并没有特殊的含义。为保证向后兼容,就只能使用转义。基于同样的原因,BRE不支持 +?量词,也不支持多选结构(...|...)和反向引用\1\2等。

缺少了这些功能,BRE使用起来确实不够方便,好在今天纯粹的BRE已经很少见了,毕竟大家已经认为正则表达式"理所应当"支持多选结构和反向引用等功能。所以虽然vi属于BRE流派,但仍然提供了这些功能。而且,GNU也对BRE做了扩展,支持+?|,只是使用时必须写成\+\?\|,而且也支持\1\2之类的反向引用。这样,GNU的grep等工具虽然名义上属于BRE流派,但更确切的名称是GNU BRE。

1.1.2、ERE

egrep、awk则属于ERE这一派,虽然从名字上来看,BRE是"基本"而ERE是"扩展"​,但ERE不要求兼容BRE的语法,而是自成一体。因此其中的元字符不用转义(在元字符之前添加反斜线会取消其特殊含义)​,所以(ab|cd)就可以匹配字符串ab或者cd,量词+?{n,m}可以直接使用。ERE并没有明确规定支持反向引用,但是不少工具都支持\1\2之类的反向引用。

GNU出品的egrep等工具就属于ERE流派(更准确的名字是GNU ERE)​,但因为GNU已经对BRE做了不少扩展,所谓的GNU ERE其实只是一个说法,它的功能GNU BRE都有了,GNU ERE的主要区别在于元字符不需要转义。

下表简要说明了几种POSIX流派的区别(其实,现在的BRE和ERE在功能上并没有什么区别,主要的差异是在元字符的转义上)​。

1.2、POSIX字符组

POSIX流派与PCRE流派的一大区别是字符组。

通常(也就是PCRE流派的做法)我们把[...]称为"字符组"​;但在POSIX中,它们叫作"方括号表达式"​(bracketexpression)​,其作用与规则同常说的"字符组"完全一样:

  • 在方括号中列出可以匹配的所有字符,比如[abc]
  • 也支持横线表示范围,比如[a-z]
  • 如果在方括号里的开头写脱字符^,则表示排除型字符组,比如[^0-9]

但是,POSIX中的"方括号表达式"都不支持使用\uxxxx的形式表示Unicode字符。也就是说,遵循POSIX规范的正则表达式无法用[\u4E00-\u9FFF]来匹配"任意一个中文字符"​。

POSIX自己也定义了字符组,准确的名称是"POSIX字符组"​(POSIX character class)​,这是一些事先定义好的组合,基本等价于我们常说的"字符组简记法"​。

  • 比如[:digit:]就是一个POSIX字符组,它等价于[0-9]
  • [:alpha:]也是POSIX字符组,它等价于[a-zA-Z]

另一方面,PCRE中常见的\d\w\s在POSIX中并没有定义。

下表详细介绍了常用的POSIX字符组(请注意,POSIX字符组只匹配ASCII字符)​。

了解了POSIX、BRE、ERE这些概念之后,下面简要介绍Linux/UNIX下常用工具中的正则表达式。

2、vi

vi/vim(vim是vi iMproved的缩写,以下统称vi)是Linux/UNIX下常用的文本编辑器,它提供了各种正则表达式功能。vi的正则表达式属于BRE流派,但是它也支持几乎所有的正则表达式功能,只是不少元字符都需要转义。如果使用Very Magic模式(后面会讲到)​,也可以不用那么麻烦地进行转义。

首先介绍查找所用的各种记法。在vi里进行查找,首先要切换到控制模式(按Esc键)​,然后输入/pattern/(结尾的/可以忽略)​,其中pattern是用于查找的模式字符串。在控制模式下输入/cat/再回车,就查找当前编辑的文本中所有的字符串cat(注意不是单词cat)​。

2.1、字符组及简记法

vi的字符组遵循POSIX规范,所以POSIX中的各种字符组和记法都可以直接在vi中使用。如果在开方括号之前写上\_,则这个字符组不但能匹配其中列出的任何字符,还可以匹配行终止符,比如\_[abc]就不但能匹配字符a、b、c,还能匹配换行符。

除去POSIX规范的字符组,vi也提供了类似Perl的字符组简记法。虽然vi在正确设定编码后能够识别中文等多字节字符(不会把一个中文字符当成"几个字符"来处理)​,但其对"数字字符"​"空白字符"和"单词字符"的识别仍然局限于ASCII字符,详见下表。

补充一点,表中列出的字符组简记法是不能在方括号内部使用的。比如要匹配一个十六进制的数字字符,可以用\x,也可以用[​[:digit:]a-fA-F],但[\da-fA-F]无法匹配[0-9]

值得注意的是,vi中没有单行模式,所以点号不能匹配行终止符,如果需要匹配包括行终止符在内的任何字符,可以使用\_.

2.2、量词

vi中的量词比较奇怪,大多需要转义,部分通行量词的记法也有区别(主要是{出现之后,}不需要转义)​,刚开始接触可能不习惯。下表列出了vi中的量词。

vi中没有现成的忽略优先量词+???,但是可以用\{-1,}\{-,1}表示。

2.3、多选结构和捕获分组

前面讲过,vi的正则表达式属于BRE流派,所以(|)都需要转义。通常我们所见的(...|...),要写作\(...\|...\)

捕获分组中的括号也需要转义,所以(...)要写作\(...\)

2.4、环视

vi也提供了环视功能,但是它的环视写法有所不同。vi的环视与PCRE的环视对照如表所示。

同时环视的写法也与常见的不同,PCRE将环视使用的表达式写在环视的后方(右方)​,而vi中必须将环视使用的表达式写在环视的左方,下表举了两个例子。

vi的环视功能没有任何限制,可以随意使用量词,所以可以很方便地用它查找"包含/不包含某个表达式"的行。

查找包含tom或者jerry的行。

bash 复制代码
^\(.*\(tom\|jerry\)\)\@=

查找不包含tom也不包含jerry的行。

bash 复制代码
^\(.*\(tom\|jerry\)\)\@!

2.5、锚点和单词边界

vi提供了常见的匹配行起始位置和结束位置的^$,但没有提供"通用"的单词边界\b,而是提供了分别匹配单词起始和结束位置的\<\>

匹配行开始的cat。

bash 复制代码
^cat

匹配行结束的cat。

bash 复制代码
cat$

匹配单词开始的cat。

bash 复制代码
\<cat

匹配单词结束的cat。

bash 复制代码
cat\>

回到开头提到的查找cat的例子,如果我们要查找的是"单词"cat,就应该写作\<cat\>

2.6、替换操作的特殊字符

vi中的替换与查找类似,先切换到控制模式(按Esc键)​,然后输入:x,ys/pattern/replacement/。其中xy用来限制替换操作所在的行数(从第x行开始,到第y行结束,如果不指定x和y,则只在当前行进行替换;如果只写一个%,则对所有行进行替换)​,s表示"替换"操作,pattern是用来查找的模式字符串,replacement则是要替换成的字符串,当然也不要忘记最前面的冒号。

在当前行将单词tom替换为jerry。

bash 复制代码
:s/\<tom\>/jerry/

在第1行到第10行将单词tom替换为jerry。

bash 复制代码
:1,10s/\<tom\>/jerry/

在所有行将单词tom替换为jerry。

bash 复制代码
:%s/\<tom\>/jerry/

为简明起见,下面的替换命令中不再写出:x,y,只保留s/pattern/replacement/,请注意。

在默认情况下,替换只会对第一次遇到的匹配进行,如果在某行中多次出现了正则表达式能够匹配的文本,需要将它们全部替换,可以使用s/pattern/replacement/g,最后的g表示"全部替换"模式。

在所有行中将出现的所有单词tom替换为jerry。

bash 复制代码
:%s/\<tom\>/jerry/g

在结尾的/之后,除了g,还有其他模式,如表所示。

下面再给出一些常见操作的写法。

在所有行中将所有出现的单词tom(不区分大小写)替换为jerry,并在替换前确认。

bash 复制代码
:%s/\<tom\>/jerry/igc

删除所有行开头的空白。

bash 复制代码
:s/^\s\+//

删除所有行结尾的空白。

bash 复制代码
:s/\s\+$//

删除所有只包含空白字符的行中的空白字符。

bash 复制代码
:%s/^\s\+$//g

行开头添加#(Python和Ruby注释)​。

bash 复制代码
:%s/^/#/

删除行末的注释#。

bash 复制代码
:%s/#s\+$//

将连续空行合并为一个空行。

bash 复制代码
:%s/^\_[[:space:]]\+$//g

删除HTML tag。

bash 复制代码
:%s#<[^>]\+>##g

删除所有包含tom或jerry的行。

bash 复制代码
:%s/^\(.*\(tom\|jerry\)\)\@=.*$//i

删除所有//注释。

bash 复制代码
:%s/\/\/.*$//g

2.7、replacement中的特殊变量

下表中列出了replacement中可以使用的特殊变量。

用<b>标签标注word。

bash 复制代码
:%s/\<word\>/<b>&<\/b>/g

删除重复单词。

bash 复制代码
:%s/\<\(\w\+\)\>\s\+\<\1\>/\1/g

找出所有形式的tom或者jerry,全部转换为小写。

bash 复制代码
:%s/\<\(tom\|jerry\)\>/\L&/gi

将所有的tom或jerry统一为首字母大写的形式。

bash 复制代码
:%s/\<\(tom\|jerry\)\>/\u&/gi

2.8、补充

习惯PCRE正则表达式的用户可能不习惯vi中频繁出现的转义。如果是这样,可以使用vi提供的Very Magic模式,在这种模式下,常用的$.*()|都不用转义,只有{}需要转义。

Very Magic模式的使用非常简单,在查找字符串的开始写上\v就可以。下表中给出了两个例子。

在vi中输入:h pattern可以查询到pattern的详细信息,也可以参考这两份文档。

3、grep

grep是Linux/UNIX下常用的工具,作用是从文件或标准输入中筛选出包含某个正则表达式能匹配文本的行,将其传送至标准输出。grep这个名字来自它所模拟的ed编辑器的一系列操作的名称缩写:Global RegularExpression Print,也有一种说法是Generalized RegularExpression Parser,最早出现在1973年的UNIX Version4中。目前大多数Linux/UNIX中都提供了grep(Windows下也有grep,但是需要自己下载)​,其中以GNU grep最为流行,下面的讲解就以GNU grep为准。

3.1、基本用法

grep的基本用法是:

bash 复制代码
grep [options] PATTERN [FILES]

其中的options在本节最后介绍,PATTERN是需要指定的 正则表达式,FILES是需要检索的文件,如果不指定FILES,则默认从标准输入(stdin)读取文本,下面举一个最简单的例子。

从sample.txt中找出所有包含word的行。

bash 复制代码
grep word sample.txt

为了避免正则表达式被Shell错误解释(正则表达式中的*[]之类可能先被Shell展开)​,保险的做法(也是好的习惯)是给正则表达式加上单引号'

bash 复制代码
grep 'word' sample.txt

如果在其中出现了变量插值,则应当使用双引号"

bash 复制代码
grep "$JAVA_HOME" sample.txt

3.2、字符组

grep的字符组遵循POSIX规范,所以POSIX的字符组和简记法都可以直接在grep中使用。grep中的点号可以匹配除换行符之外的任何字符,因为grep是按行处理的,每次处理一行,所以不需要关心点号能否匹配换行符。

从sample.txt中找出所有这样的行:包含字符a或b。

bash 复制代码
grep '[ab]' sample.txt

从sample.txt中找出所有这样的行:包含除a和b之外的任何字符。

bash 复制代码
grep '[^ab]' sample.txt

从sample.txt中找出所有这样的行:包含c开头,t结尾,长度为3(如cat、cut等)的字符串的文本行。

bash 复制代码
grep 'c.t' sample.txt

3.3、锚点和单词边界

在grep中能使用的锚点有4个:^$\<\>

  • ^用来匹配行的开始位置
  • $用来匹配行的结束位置(因为grep是面向行的处理工具,每次处理一行,所以不存在^$匹配文本内部的行起始/结束位置的问题,也就不需要多行模式)。
  • grep不支持常见的\b,而是用\<\>来匹配单词的起始位置和结束位置,它们所识别的"单词字符"是[​[:alnum:]​]能匹配的字符,也就是[0-9a-zA-Z],注意其中不包含下画线。

从sample.txt中找出所有以cat开头的行。

bash 复制代码
grep '^cat' sample.txt

从sample.txt中找出所有以cat结尾的行。

bash 复制代码
grep 'cat$' sample.txt

从sample.txt中找出所有包含单词cat的行。

bash 复制代码
grep '\<cat\>' sample.txt

3.4、量词

grep中能使用的量词有*+?{n,m}

  • 只有*可以直接使用,其他量词都必须转义:
  • +必须写成\+
  • ?必须写成\?
  • {n,m}要写成\{n,m\}

从sample.txt中找出所有包含a、ab、abb、abbb...的行。

bash 复制代码
grep 'ab*' sample.txt

从sample.txt中找出所有包含ab、abb、abbb...的行。

bash 复制代码
grep 'ab\+' sample.txt

从sample.txt中找出所有包含a或者ab的行。

bash 复制代码
grep 'ab\?' sample.txt

从sample.txt中找出所有包含ab、abb、abbb的行。

bash 复制代码
grep 'ab\{1,3\}' sample.txt

3.5、多选结构和捕获分组

grep支持多选结构,但是必须转义(|)三个字符。

  • 多选结构必须写成\(...\|...\)
  • 捕获分组的记法是\1\2

查找sample.txt中所有包含tom或者jerry的行。

bash 复制代码
grep '\(tom\|jerry\)' sample.txt

查找sample.txt中所有包含重复单词的行。

bash 复制代码
grep '\(\<[[:alpha:]]\+\>\)[[:space:]]\+\<\1\>' sample.txt

3.6、options

grep中可以使用options来指定匹配规则(类似匹配模式)​,常用的option介绍如下。

3.6.1、-i 不区分大小写

查找各种形式的cat,包括CAT、cat、Cat等。

bash 复制代码
grep -i 'cat' sample.txt
3.6.2、-v 不匹配

这是grep中特别有用的功能,正则表达式要"排除若干字符串"通常是非常麻烦的,但是有了grep -v就非常方便了,它会选出正则表达式不能匹配的所有行。

查找所有不包含cat的行。

bash 复制代码
grep -v 'cat' sample.txt

查找所有不包含404和302的行(在过滤日志时特别有用)​。

bash 复制代码
grep -v '\(404\|302\)' sample.txt

查找所有不包含jpg、png、gif的行(在过滤日志时特别有用)​。

bash 复制代码
grep -v '\(jpg\|png\|gif\)' sample.txt
3.6.3、-w 只匹配单词

查找只包含单词cat的行,等价于grep '\<cat\>'sample.txt

bash 复制代码
grep -w 'cat' sample.txt
3.6.4、-n 显示行数

找出所有包含cat的行,同时标明行数。

bash 复制代码
grep -n 'cat' sample.txt
3.6.5、-r 递归查找

这也是一个特别有用的功能,它可以对目录下的所有文件进行查找。

找出当前目录下所有文件中包含cat的行。

bash 复制代码
grep -r 'cat'
3.6.6、-A n 显示匹配行及之后的n行

找出所有包含cat的行,并显示每行之后的3行。

bash 复制代码
grep -A 3 'cat' sample.txt
3.6.7、-B n 显示匹配行及之前的n行

找出所有包含cat的行,并显示每行之前的3行。

bash 复制代码
grep -B 3 'cat' sample.txt
3.6.8、-C n 显示匹配行及之前之后各n行(等价于-A n -B n)

找出所有包含cat的行,并显示每行之前和之后的3行。

bash 复制代码
grep -C 3 'cat' sample.txt
3.6.9、-P 使用Perl风格的表达式

如果你很熟悉Perl风格的正则表达式,而不习惯grep的规定,开启这个选项可以直接使用Perl风格的正则表达式,比如像下面这样查找单词cat。

bash 复制代码
grep -P '\bcat\b' sample.txt

3.7、egrep和fgrep

egrep和grep的名字不同,但并没有本质区别。虽然egrep属于ERE流派,而grep属于BRE流派,但GNUegrep和GNU grep其实只有写法的差别,功能上并无不同。

实际上,在命令行下输入grep --E,就等价于egrep。使用egrep的理由或许在于,因为它属于GNUERE流派,元字符几乎不需要转义就可直接使用,写、读起来都要更简单一些。

fgrep也是grep家族中的成员,f代表fast,fgrep也确实比grep要快很多,因为它完全取消了正则表达式的功能,只进行最简单的字符串查找。在fgrep中查找end$,并不是找出行末为end的行,而是包含字符串end$的行。

3.8、补充

grep负责选出能匹配成功的整行文本,真正匹配到的内容 反而不好识别了。使用grep时最好加上一个参数grep --color,这样grep会把能匹配到的文本用不同颜色标注出来,方便识别。如果你愿意,不妨做一个alias,省去每次明确指定:

bash 复制代码
alias grep='grep --color'

关于grep的详细文档,可以输入info grep获得,也可以参考下面的文档:

4、awk

awk也是Linux/UNIX下常用的工具,它在20世纪70年代诞生于贝尔实验室,得名自三位作者Alfred Aho、PeterWeinberger、Brian Kernighan。awk的主要作用是对数 据重新组织和统计,它的功能非常多,语法也很复杂,如果使用得当完全可以实现非常复杂的逻辑。在本文,我们只介绍与正则表达式相关的内容。awk有许多版本,本文以gawk(GNU awk)为准。

4.1、基本用法

awk的基本使用方法是:

bash 复制代码
awk 'program' input-file1 input-file2

其中program是处理数据的程序代码,其基本形式为:

bash 复制代码
condition {action}

condition是判断条件,action是条件满足时所执行的操作,比如下面这条命令的意思就是,如果sample.txt中的某一行可以由正则表达式J匹配(也就是说,包含大写字母J)​,就输出这一整行($0的含义在下面讲解)​。

bash 复制代码
awk '/J/ {print $0}' sample.txt

为方便讲解,假设sample.txt的内容如下:

bash 复制代码
Jan 13 25 15 115
Feb 15 32 24 226
Mar 15 24 34 228
Apr 31 52 63 420
May 16 34 29 208
Jun 31 42 75 492
Jul 24 34 67 436

上面命令运行的结果就是:

bash 复制代码
Jan 13 25 15 115
Jun 31 42 75 492
Jul 24 34 67 436

如果仅仅这样使用,awk和grep并没有太大的区别,但awk的功能并不限于这些。awk提供了一系列变量$1$2$3...对应每一行的各个字段(在默认状态下,字段之间以空白分隔,也可以通过FS指定分隔)​,$0代表整行,NF代表该行的字段数目,因此$NF表示最后一个字段。

如果把上面的语句改一改,就可以同时实现对字段的过滤。

bash 复制代码
awk '/J/ {print $1, $2, $NF}' sample.txt

Jan 13 115
Jun 31 492
Jul 24 436

我们还可以在{action}中写上一些处理逻辑。

bash 复制代码
awk '{if($NF > 400) print $0}' sample.txt

Apr 31 52 63 420
Jun 31 42 75 492
Jul 24 34 67 436

下面介绍awk中的正则表达式。awk中的正则表达式虽然也遵循POSIX规范,但属于ERE流派,所以很多元字符不需要进行转义。

4.2、字符组及简记法

awk的字符组遵循POSIX规范,所以POSIX的字符组和简记法都可以直接在awk中使用。而且,awk中的点号可以匹配包括换行符在内的任何字符,不过一般来说,awk是按行处理的,所以这一点并不重要。

从sample.txt中找出所有包含a或者b的行。

bash 复制代码
awk '/[ab]/ {print $0}' sample.txt

Jan 13 25 15 115
Feb 15 32 24 226
Mar 15 24 34 228
May 16 34 29 208

从sample.txt中找出所有包含数字字符的行。

bash 复制代码
awk '/[0-9]/ {print $0}' sample.txt

Jan 13 25 15 115
Feb 15 32 24 226
Mar 15 24 34 228
Apr 31 52 63 420
May 16 34 29 208
Jun 31 42 75 492
Jul 24 34 67 436
bash 复制代码
awk '/[[:digit:]]/ {print $0}' sample.txt

Jan 13 25 15 115
Feb 15 32 24 226
Mar 15 24 34 228
Apr 31 52 63 420
May 16 34 29 208
Jun 31 42 75 492
Jul 24 34 67 436

例外的是,GNU awk支持两个PCRE的字符组\w\W,其中\w等价于[​[:alnum:]]\W则匹配\w所不能匹配的所有字符。

4.3、锚点和单词边界

awk中能使用的锚点有下面几个:^$\<\>\y\B

  • ^用来匹配行的开始位置
  • $用来匹配行的结束位置(因为awk是面向行的处理工具,每次处理一行,所以不存在^和$匹配文本内部的行起始和结束位置的问题,也就不需要多行模式)。

awk识别的"单词字符"是[​[:alnum:]​]能匹配的字符,也就是[0-9a-zA-Z]

  • \<\>匹配单词的起始和结束位置
  • \y匹配单词的起始或者结束位置
  • \B匹配\y无法匹配的位置

从sample.txt中找出所有以cat开头的行。

bash 复制代码
awk '/^cat/ {print $0}' sample.txt

从sample.txt中找出所有以cat结尾的行。

bash 复制代码
awk '/cat$/ {print $0}' sample.txt

从sample.txt中找出所有包含以cat开头单词的行。

bash 复制代码
awk '/\<cat/ {print $0}' sample.txt

从sample.txt中找出所有包含cat,但不是单词cat的行(比如truncate)​。

bash 复制代码
awk '/\Bcat\B/ {print $0}' sample.txt

4.4、量词

awk中能使用的量词有*+?{n,m}{n,}{n},因为awk属于ERE流派,所以这些量词可以直接使用,不用转义。

从sample.txt中找出所有包含a、ab、abb、abbb...的行。

bash 复制代码
awk '/ab*/ {print $0}' sample.txt

从sample.txt中找出所有包含ab、abb、abbb...的行。

bash 复制代码
awk '/ab+/ {print $0}' sample.txt

从sample.txt中找出所有包含a或者ab的行。

bash 复制代码
awk '/ab?/ {print $0}' sample.txt

从sample.txt中找出所有包含ab、abb、abbb的行。

bash 复制代码
awk '/ab{1,3}/ {print $0}' sample.txt

4.5、多选结构

awk中的多选结构也是(...|...)的形式,可以直接使用,不需要转义。

从sample.txt中找出包含tom或者jerry的行。

bash 复制代码
awk '/(tom|jerry)/ {print $0}' sample.txt

4.6、补充

awk不支持捕获分组和反向引用,不支持忽略优先量词,也无法使用匹配模式进行不区分大小写的查找。

关于awk的详细文档,可以输入info awk获得,也可以参考下面的文档:

http://www.gnu.org/manual/gawk/gawk.html

5、sed

sed也诞生于贝尔实验室,是Linux/UNIX下常用的流编辑工具。sed得名自stream editor,它主要用来进行文本的替换等操作。

因为sed进行流式处理(在默认模式下,sed每次读入一行,去掉换行符,处理,再加上换行符,输出)​,所以速度非常快。

sed有许多版本,这里讲解的以GNU sed为准(选择它的理由是因为它比较标准,也很方便,GNU sed提供了不区分大小写的i模式,其他的一些sed则没有提供)​。前面讲过,sed属于BRE流派,所以许多元字符之前需要加反斜线来转义。

5.1、基本用法

sed最主要的功能是进行文本的替换,替换的基本用法如下:

bash 复制代码
sed -e 's/pattern/replacement/options' filename

其中s表示"替换"​,pattern是要用来搜索替换文本的正则表达式,而replacement是要替换的结果,filename是要操作的文件,options是可选参数。如果必须同时执行多个替换操作,则每个操作之前都必须使用-e。

bash 复制代码
sed -e 's/pattern1/replacement1/' -e 's/pattern2/replacement2/' filename

在默认情况下,sed只会对每行文本进行一次替换,如果要替换所有的文本,则应该设定options参数为g。

bash 复制代码
sed -e 's/pattern/replacement/g' filename

5.2、字符组及简记法

sed的字符组遵循POSIX规范,所以POSIX的字符组和简记法都可以直接在sed中使用。需要指出的是,sed中的点号.可以匹配包括换行符在内的任何字符,不过一般来说,sed是按行处理的,所以这点区别并不重要。

从sample.txt中删除所有a或者b。

bash 复制代码
sed -e 's/[ab]//' sample.txt

从sample.txt中删除所有数字字符。

bash 复制代码
sed -e 's/[[:digit:]]//g' sample.txt
sed -e 's/[0-9]//g' sample.txt

5.3、锚点和单词边界

sed支持^$,其中^匹配行的起始位置,$匹配行的结束位置。

从sample.txt中替换掉所有在行首的cat

bash 复制代码
sed -e 's/^cat//g' sample.txt

从sample.txt中替换掉所有在行尾的cat

bash 复制代码
sed -e 's/cat$//g' sample.txt

sed认定的"单词字符"是\w,也就是[​[:alnum:]],在此基础上,共有4个单词边界:\<\>\b\B

  • \<匹配单词的起始位置
  • \>匹配单词的结束位置
  • \b匹配单词的起始或者结束位置
  • \B则匹配\b无法匹配的位置

从sample.txt中替换所有单词开头的cat。

bash 复制代码
sed -e 's/\<cat//g' sample.txt

从sample.txt中替换所有单词末尾的cat。

bash 复制代码
sed -e 's/cat\>//g' sample.txt

从sample.txt中替换所有的单词cat。

bash 复制代码
sed -e 's/\bcat\b//g' sample.txt

从sample.txt中替换所有单词内部的cat(比如truncate)​。

bash 复制代码
sed -e 's/\Bcat\B//g' sample.txt

5.4、量词

前面讲过,sed属于BRE流派,所以虽然能使用的量词有*+?{n,m}{n}{n,},但只有*可以直接使用,其他量词都必须转义:

  • +必须写成\+
  • ?必须写成\?
  • {}都需要转义

从sample.txt中删除所有的a、ab、abb、abbb等。

bash 复制代码
sed -e 's/ab*//g' sample.txt

从sample.txt中删除所有的ab、abb、abbb等。

bash 复制代码
sed -e 's/ab\+//g' sample.txt

从sample.txt中删除所有的a或者ab。

bash 复制代码
sed -e 's/ab\?//g' sample.txt

从sample.txt中删除所有的ab、abb、abbb。

bash 复制代码
sed -e 's/ab\{1,3\}//g' sample.txt

5.5、多选结构和捕获分组

sed中多选结构的(|)都需要转义,写成\(...\|...|)

从sample.txt中替换掉所有的tom或者jerry。

bash 复制代码
sed -e 's/\(tom\|jerry\)//g' sample.txt

如果出现了捕获分组,则无论在regex中,还是replacement中,都可以用\1\2的形式来引用。

从sample.txt中删掉重复的单词。

bash 复制代码
sed -e 's/\(\b[[:alnum:]]\+\b\)[[:space:]]\+\1//g' sample.txt

从sample.txt中把重复的单词替换为单个单词。

bash 复制代码
sed -e 's/\(\b[[:alnum:]]\+\b\)[[:space:]]\+\1/\1/g' sample.txt

sed中有一个特殊变量&,它表示正则表达式匹配的文本。这样,我们可以给所有的数字字符串加粗:

bash 复制代码
sed -e 's/[[:digit:]]\+/<b>&<\/b>/g' sample.txt

5.6、options

sed的主要option介绍如下。

5.6.1、g:在所有可能的地方进行替换

把sample.txt中所有的tom替换为jerry。

bash 复制代码
sed -e 's/tom/jerry/g' sample.txt
5.6.2、p:输出发生了替换的行。

它配合-n使用,可以只输出发生了替换的行。

把sample.txt中所有的tom替换为jerry,并且只输出发生了替换的行。

bash 复制代码
sed -n -e 's/tom/jerry/gp' sample.txt
5.6.3、I:查找-替换时不区分大小写(只有GNU sed支持此功能)

把sample.txt中所有的tom(不区分大小写)替换为jerry。

bash 复制代码
sed -e 's/tom/jerry/Ig' sample.txt
5.6.4、M:启用多行模式(只有GNU sed支持此功能)

在默认情况下,sed按行读入文本,然后处理,但我们也可以指定N参数,让sed一次读入多行文本,此时如果指定了M模式,^和$就能匹配多行文本中某一行的起始和结束位置。

一次读入两行,将行首的tom替换为jerry(不要忘记g,因为要处理"所有的行首"​)​。

bash 复制代码
echo -e 'tom\ntom' | sed 'N; s/^tom/jerry/Mg'

5.7、补充

如果觉得BRE的转义太麻烦,也可以指定使用ERE模式,办法就是在sed之后添加-r参数,如果之前使用了-e参数,可以把两个参数合并为-re(写成-er会报错)​。

sed不支持环视,也不支持忽略优先量词。

关于sed的详细文档,可以输入info sed获得,也可以参考下面的文档:

相关推荐
琥珀色糖8 小时前
Linux GDB调试
linux·运维·服务器·gdb·调试
k4m7v2pz8 小时前
Rust 长跑守护进程日志治理:切分、时区与结构化
开发语言·后端·rust·日志系统·日志轮转·ndjson
FfHUCisI8 小时前
Golang RESTful API 设计原则
开发语言·golang·restful
breeze jiang8 小时前
ESLint flat config 配置实战:五大字段、规则严重级别与 --fix 能力边界详解
开发语言·前端·javascript
weixin_307779138 小时前
PHP 大文件上传:内存占用、超时与最佳实践
linux·服务器·开发语言·nginx·php
m0_527034338 小时前
异步任务审核系统设计:消息队列、超时重试与失败补偿
java·大数据·开发语言
闲云野鹤在人间8 小时前
项目实战:LNMP-电商平台-ECshop
linux·运维·nginx·apache·lvs
李小白668 小时前
1-Shell编程和网络服务介绍
运维·云计算
吹什么轩9 小时前
linux网络:UDP套接字的业务实现:字典
linux·运维·udp
迷途之人不知返9 小时前
gcc编译器,以及源文件的翻译
linux