正则表达式之

前端之家收集整理的这篇文章主要介绍了正则表达式之前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

正则表达式的应用之grep、sed

一:前言

本文主要介绍正则表达式在grep、sed下的应用。我所用的是GNU grep 2.5.4(grep -V 查看版本信息)、GNU sed 4.2.1(sed --version)。

grep很多人很熟悉,sed这个工具可能会不熟悉,不过没关系,下面重点在正则表达式,下面的测试中我都用sed 's/···/···/' 类似这种结构,其中s代表替换,第一个/···/之间的是正则表达式,第二个/···/之间是要替换的文本。

GNU版本的都差不多,但是如果不是GNU版本的话,对于元字符的支持和解释可能是不同的,这里要大概说一下正则表达式的流派(流派不同对元字符的定义和解释不同),在POSIX规范中,涉及到BRE、ERE流派,而本文所要介绍的grep和sed 都属于 BRE。 不过现在的BRE和ERE功能上并没有多大的区别,只是差异在元字符的转义上,比如在grep中是不能直接使用+元字符的,但是经过转义后(即:\+)就和ERE中的+元字符表达同样的意思了(匹配一到多个字符)。关于流派之类的详细说明可以参考《精通正则表达式》一书。下图为关于POSIX正则表达式流派概览:(基于我自己使用的工具)

正则表达式特性 BRE ERE
点号、^、$、[···] 、[^···] 支持 支持
任意数目 量词 * *
+和? 量词 需要转义 \+ 和 \? 直接使用 + ?
区间量词 需要转义\{min,max\} 直接使用{min,max}
分组 需要转义\(···\) 直接使用(···)
量词可否作用于括号 支持 支持
反向引用 \1到\9 不支持
多选结构 需要转义 \| 直接使用 |



二:grep和sed 的应用实例

1)点号、^、$、*、[…] 、[^…]

这些在正则表达式中的意思大都一样,不过也要依据具体的实现,大部分的实现点号都是匹配除了换行符之外的所有字符,至于*的意思就很明确了,表示的是0-多个字符,无上限。也就是说 正则表达式"s*" 是一定可以匹配的,因为s可以出现也可以不出现,".*"就可以代表任意多的任意字符。
echo "zydovech" | grep 'd*'>输出为 zydovech(其中颜色部分为匹配部分,>表示后面的是输出
echo "zydddddovech" | grep 'd*'>输出zydddddovech
echo "zyddovech" | sed 's/d*/sss/'>zyssovech
echo "zydovech" | sed 's/d./zy/' >zyzyvech
echo "zydovech" | grep 'd.' >zydovech
这里要强调的一点是在精通正则表达式书中86页(中文版,第三版)中说道在grep中^和$只有在正则表达式的开头和结尾是元字符,所以用户无法使用 end$|^start 这样的正则表达式,还说明了grep是不支持多选结构的,但是在我所使用的GNU grep中是可以的,如:
echo "start start end end" | grep 'end$\|^start' >start start endend
sed也是支持
echo "start start end end" | sed 's/^start\|end$/***/' >*** start end end 看到这个可能会问最后一个end为什么没有匹配?这和sed本身有关,sed会替换一行文本中第一次匹配的字符串,然后结束,可以加上g全部替换:

echo "start start end end" | sed 's/^start\|end$/***/g'>*** start end end ***

[…]表达的是字符组的概念,不过我们要记得第一个规则就是[…]值匹配一个字符,无论[…]里写了多少字符。

echo "zydovech" | grep 'd[ovech]' >zydovech 这里代表d后面是o、v、e、c、h中的一个,是任何一个都可以。但是d[ovech]一共只能匹配两个字符。

第二个规则是:在[…]内元字符的定义是不一样的,我只遇到过-(连字符)^(排除字符,这个字符在字符组外面表达的意思是不一样的) 而且必须在特定的位置才是元字符,-在正则表达式的开头的话就不是元字符了,^必须在开头,表示的是除了字符组内的字符以外的字符。

echo "zydovech" | grep 'd[a-z]' >zydovech 其中[a-z]表示从a-z之间的一个字符,是按照ASCII的顺序。[0-9]表示的是数字[A-Z]表示的是大写字母,注意不要写成一些奇怪的范围,比如[1-z]等,这要的范围不仅不好理解,而且匹配的范围也很难说清楚。但是-在字符组前面就不再是元字符[-sa]则-只是普通的字符,

echo "zy-s" | grep '[-sa]' >zy-s可以看出-不再是元字符

[^…]称为排除型字符组,匹配单个未列出的字符。但是必须匹配一个字符

echo "dsssd" | grep '[^s]' >dsssd其中d是没有列出的字符

echo "saaaad" | grep '[^s]s' 无法匹配,因为s前面没有字符。

2) 分组()


grep和sed是不直接支持分组元字符的,他们可以当做普通字符去匹配。

echo "zy(dove)ch" | grep '(dove)' >zy(dove)ch

echo "zy(dove)ch" | sed 's/(dove)/**/' >zy**ch

这个分组称为捕获分组(?:)这个是非捕获分组,不过grep和sed不支持,这里就不介绍了。它会保存在括号里匹配的内容,供使用,不同的工具和语言有不同的使用方法,grep和sed通过 \1和\2等使用相应括号内的匹配文本。叫反向引用。

不过可以通过其他方法使用,主要有两种:

第一种:在grep后面加-E。即 grep -E 'e?' 这种形式,这样就是等效于使用egrep。在 sed后面加 -r 等效于使用扩展正则表达式,即ERE。在grep的man手册里 还提到可以在grep后面加-P以使用Perl流派的正则表达式,这里不再多说。可以自己查看对应的手册。

第二种:通过转义\(\)

echo "zy(dove)ch" | grep '\(dove\)' >zy(dove)ch

通过转义后()不再是普通字符,而是代表分组的字符。他会保存括号内的正则表达式所匹配的文本内容,供以后使用,grep 和sed通过\1使用。

括号的作用有两个,一个就是单纯的分组,而就是捕获括号内正则表达式所匹配的内容,当然这两个功能不是独立,是同时起作用的。假如希望zydodododove 中的多个do就要用到分组。

echo "zydododovech" | grep '\(do\)*' >zydododovech 这里分组把括号里的内容当做整体,通配符*作用于这个整体。

echo "zydododovech" | sed 's/\(do\)*/**/' >zy**vech

接下来看看反向引用的概念:

echo "zydododovech" | grep '\(d.\)\1' >zydododovech

这里的正则表达式,应该可以明白吧,\(d.\)这个代表分组,点号匹配的是任意字符,就是o,而\1就是反向引用,\1的值就是前面括号所匹配的文本,文本匹配到的内容就是do,所以此时的\1就是do,整个正则表达式的意思就是d后面跟着一个字符,然后紧跟着相同的序列,这在匹配HTML的标签时挺有用的,因为大多数HTML标签都是成对的出现)。

不知道会不会有人有这个疑问,为啥子匹配结果是zydododovech而不是zydododovech?我刚开始学习的时候就有这个疑问,后来知道正则表达式是从左向右扫描字符串,匹配,但匹配到之后,会从匹配结束的位置开始从新匹配。上面的例子就是,从左向右匹配,当匹配到第二do时第一次匹配结束,接下来就开始从第二个do结束的位置开始从新匹配。剩余的字符串没有可以匹配的内容了。

在sed中可以更加清楚的看到括号和反向引用的使用

echo "zydodovech" | sed 's/\(d.\)\(.o\)/\1,\2/' >zydo,dovech

单独看\(d.\)\(o.\) 我们知道他们匹配的是dodo。然后sed 用\1,\2去替换文本中的dodo,通过上面的分析知道\1就是第一个括号匹配的内容do,\2是第二个括号匹配的内容do 然后\1,\2就代表了do,do 。则输出结果就明显了。

3) +、?

grep和sed是不直接支持这两个元字符的,也就是说在grep和sed中+和?是普通字符,可以直接匹配文本,如:
echo "zydove?ch" | grep 'e?'> 输出为zydov e? ch。
echo "zydove+ch" | grep 'e+' >输出为zydov e+ ch
echo "zydove+ch" | sed 's/e+/**/' >zydov**ch (?也同样的道理)

通过转义后可以使用:
echo "zydove?ch" | grep 'e\?' > 输出为zydov e ?ch 通过转义\?之后,\?就有元字符?的意义了(匹配0-1个字符)
echo "zydoveeeech" | grep 'e\+' > 输出 zydoveeeech 现在可以看到+具有元字符的意义了。可以看出+匹配

所有可以匹配的字符。这里的意思是有几个e我就匹配几个,事实上就是如此。有一种情况是判断如下的匹配: echo "zydoveeeeeech" | grep '\(e\+\)\(e\+\)' 中第一个分组和第二个分组分别匹配的是什么?这个可以通过反向引用验证:第一个分组匹配尽可能多的e。只会留下一个e给第二个分组匹配。至于为什么这样,+和*都是匹配优先的,就是指:他们会一直匹配到不能匹配为止,如果后面需要再进行回溯。上面例子的匹配过程如下:

1:e和第一个e匹配。

2:\+匹配一到多个e,匹配到最后一个e。

3:因为(e\+)至少需要匹配一个e则,经过第二步的匹配必须回溯,交换一个字符给e\+使用,交换一个字符后e\+已经可以匹配,不会再强迫之前的匹配交还字符。

4:匹配完毕。

那么是如果是下面的表达式两个括号分别匹配的是什么?

echo "zydoveeeeeech" | grep '\(e\+\)\(e*\)'

(第一个括号匹配所有的e,最后一个括号啥都不匹配。详细的解释请参考《精通正则表达式》)

在sed中道理也是一样的,可以自己验证。

echo "zydoveeeech" | sed 's/e\?/*/' >zydov*eeech

echo "zydoveeeech" | sed 's/e\+/**/' >zydov**ch

在sed中很好验证前面说的那个匹配多少个e的问题,这里提前使用一下反向引用。\1和\2分别代表正则表达式中第一个括号和第二个括号所匹配的文本:

echo "zydoveeeech" | sed 's/\(e\+\)\(e\+\)/\1,\2/' >zydoveee,ech

其中\1就是代表第一个括号之间匹配的三个eee,\2代表了第二个括号间代表的一个e。如此和上面所说一致

echo "zydoveeeech" | sed 's/\(e\+\)\(e*\)/\1,\2/' >zydoveeee,ch

可以看到第一个括号匹配了所有的e。第二个括号啥都没匹配到。

4)区间量词{}

grep和sed也不是直接支持区间量词的,也要通过转义使用,区间量词{min,max}就是表示最少出现min次,最多出现max次。

echo "zydoveeech" | grep 'e\{1,3\}' >zydoveeech {}也是匹配优先的,会尽可能匹配多的字符。

echo ''zydoveeeeech" | sed 's/\(e\{1,4\}\)\(e\{1,4\}\)/\1,\2/' >zydoveeee,ech

这个正则表达式看起来比较复杂,仔细分析一下就很简单,主要是因为转义\符多了,看起来不直观。

echo "zydoveeeech" | grep 'e\{1,3\}' >zydoveeeech

可以看到所有的e都匹配了,有人说区间量词{1,3}不是匹配一到三个e吗?的确是这样,在匹配了前三个e之后,grep 会继续匹配后面的内容

echo "zydoveeech" | grep '\(e\{1,3\}\)\1'

可以猜测一下匹配的内容是什么,可以自己尝试一下,具体的匹配过程可能不清楚,大致解释如下:

1:e\{1,3\}匹配尽可能多的e,即把三个e都匹配完。

2:\1代表括号里的内容,现在是eee,然后匹配,发现没有e可以匹配了,则匹配失败。

3:匹配失败后e\{1,3\} 需要进行回溯,交还一个e,此时\1 代表ee,因为e\{1,3\}已经匹配了两个e了,所以只有一个e给\1匹配所以还是匹配失败。

4:再回溯,再交还一个e,此时e\{1,3\}值匹配到一个e,此时\1也就代表一个e,在e\{1,3\}匹配到一个e后,还有两个e提供给\1进行匹配,所以此时可以匹配。

5:上一次正则表达式成功匹配了两个ee,还有一个e,接下来按照上面的步骤继续进行尝试,但是匹配不到更过的单词了。。整个匹配结束。所以结果只匹配了前两个e。

5)多选结构 |

还有在grep和sed中多选结构|也不是元字符,所以,不经过转义的|只是普通的字符。

echo "zydove|ch" | grep 'dove|' >zydove|ch

echo "zydove|ch" | sed 's/|/*/' >zydove*ch

echo "zydovech" | grep 'do\|ch' >zydovech

echo "zydovech" | sed 's/do\|ch/**/' >zy**vech

再次强调,sed在完成第一次匹配后,会结束本次匹配,所以ch没被替换。

多选结构需要注意的是|把正则表达式分成两部分,把左右两部分分别看做一个整体,可以通过分组改变。即:zydo|vech 匹配的是zydo 或者vech 。zydo和vech是整体。但是若想改变加分组限制。

zy(do|ve)ch 则表示的是zy后面跟do或者ve

echo "zydovech" | grep 'zydo\|vech' >zydovech

可以匹配,zydo 先匹配成功,继续匹配的时候vech又匹配成功,此时表达的是匹配zydo或者vech

echo "zydovech" | grep 'zy\(do\|ve\)ch'

匹配失败。因为此时表达的意思是zy后跟do或者ve 然后再跟ch。这里的括号就进行了分组。在使用多选结构的时候这点要注意。

6)单词分界符

正则表达式中有很多表示单词分界的。比如:\< \> 单词的开始和结束。\b就是单词分界符(一侧是单词字符,一侧不是,基本上[0-9a-zA-Z]都是单词字符,空白属于非单词字符,这里只是一般情况下),\B和\b表达的意思相反。在我所使用的grep和sed中是支持这两种方式的。

echo "zycats cat dscat catdf" | grep 'cat' >zycatscat dscat catdf
echo "zycats cat dscat catdf" | grep '\bcat\b' >zycats cat dscat catdf
echo "zycats cat dscat catdf" | grep '\<cat\>' >zycats cat dscat catdf
echo "zycats cat dscat catdf" | grep 'cat\b' >zycats cat dscat catdf
echo "zycats cat dscat catdf" | grep '\Bcat\B' >zycats cat dscat catdf
echo "zycats cat dscat catdf" | sed 's/\Bcat\b/**/' >zycats cat ds** catdf
echo "zycats cat dscat catdf" | sed 's/\<cat\>/**/' >zycats ** dscat catdf

7)\w \W \s \S \d \D

其中,\w表示单词字符一般情况下表示数字和字符[0-9a-zA-Z_],\W表示的和\w相反。sed和grep是支持这两个元字符序列的。
\s表示的一般包括空格 、Tab、换行符。\S则相反。sed 支持这两个操作,但是grep不支持。为了测试\s包含空格、Tab、换行符,所以写一个测试文件data data里的内容为:

this is as

hhhhhhhhhhhhhh

其中第一个s后面是一个空格,第二个s后面是Tab。第三个s是行的结束,不知道为什么我在编辑的时候显示很好,已查看就乱了。

sed 's/s\s/**/g' data >输出
thi**i**as
hhhhhhhhhhh
结果并不是我们所想的那样,空格和Tab是正确匹配替换了,但是换行符并没有。但是如果按照下面这样:
sed '{N;s/s\s/**/g}' data >thi**i**a**hhhhhhhhhh
这里匹配了换行符。 其中N是sed中的命令。这里不展开说了。
\d和\D。grep和sed都不支持。不举例说了。不过我们可以用[0-9]代替。


至此一些常用的总结了一下,不过也不是很全面。还要涉及到很多其他东西,匹配模式呀,编码呀,\w 、\s、还有^、$所代表的意义不是那么简单的,这方面精通正则表达式一书说的更加详细。我也只是学习没多久,很多地方理解的还不是很好,可能有错误。欢迎指正。

猜你在找的正则表达式相关文章