Linux中正则表达式的使用

本文深入浅出地介绍了正则表达式的应用场景与语法细节,包括元字符、字符类、量词修饰符等,并通过实际案例展示了如何利用grep等工具进行高效文本处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

正则表达式是用来处理文字信息的,这在类Unix系统中是很重要的。

Shell中要输入的命令是文本,脚本里写的也是文本,文件里读出来的每一行也是文本。

所以学了这个不仅仅是磨刀不误砍柴工,而是直挂云帆济沧海。

首先说一下正则表达式的适用范围,一般很多编程语言中有关于支持正则表达式的功能,比如C#中:

using System;

using System.Text.RegularExpressions;

public class Example

{

   public static void Main()

   {

      string input = "1851 1999 1950 1905 2003";

      string pattern = @"(?<=19)\d{2}\b";

      foreach (Match match in Regex.Matches(input, pattern))

         Console.WriteLine(match.Value);

   }

}

                 

上面代码就是C#中正则表达式引擎的使用例子。

还有就是很多命令行工具也支持正则表达式,比如我们熟知的Linux里Shell的grep命令。

那正则表达式究竟是什么?

直白的讲,正则表达式是一种符号标记,来标识文本模式。就好比用星号通配符在shell中来匹配文件名或路径名,这就是一种文本模式。而正则表达式则是一种在这简单功能上的极大扩展。

上面讲了很多不同的工具和环境下都可以使用正则表达式,但实际上不同场景下会有些微不同。

比如我们这里讨论的主要是POSIX标准的,也就是我们一般在命令行工具中使用的,类Unix系统中的。

而和Perl对比,就有很大不同,Perl里面会有更大和风丰富的正则表达式的标记。


元字符和字面字符

好,那让我们从grep命令开始。global regular expression print。

grep命令的功能是从许多文本文件中查找匹配某种文本模式的内容,然后把这行内容输出到标准输出。

通常我们直接使用固定字符串:

ls /usr/bin | grep zip

上面会输出所有包含zip这个子字符串的/usr/bin下面的文件名。

grep命令可以接受正则表达式作为参数:

grep [options] regex [file...]

关于参数详情,options的内容,请参照我的另一篇博文。

其实固定字符串bzip也是正则表达式的一种。表示文本内容匹配成功的条件是,这一行文本的内容里出现了4个连在一起的字符,依次是b,z,i 和p。

bzip这个字符串,包含的就是字面字符,是正则表达式中的一种。

字面字符就是要直接匹配的内容。

除了字面字符,正则表达式还包含一些元字符来识别更复杂的匹配条件。

包含的元字符如下: ^ $ .  [   ]   {   }   -   ?   *   +   (    )   |     \

那所有其他剩下的字符都是字面字符。

比较特别的是反斜线字符,一般用来创建元字符的转义字符, 比如 \( 表示的就是字面字符 ( ,否则单独出现( 就是元字符。

需要注意的是,上面的元字符也是shell扩展时会使用的,所以为了避免shell的扩展,我们在命令行上传递包含元字符的正则表达式时,要使用单引号包含起来。

单引号里面的字符串,在shell解析命令行时,不会被扩展。


匹配模式之任意字符

第一个是点字符,或者句点字符。英文dot or period character。

grep -h '.zip' dirlist*.txt

bunzip2

bzip2

bzip2recover

结果里zip的文件名是不符合的,因为点表示任意字符,要四个字符。

而扩展名是.zip的文件,也符合要求。

匹配模式之锚

美元符号$,和脱字符/插入符 ^,作为正则表达式中的锚功能,表示匹配条件满足时要发生在行尾($)或行首(^)。

grep -h '^zip' dirlist*.txt

zip

zipcloak

....

grep -h 'zip$' dirlist*.txt

gunzip

gzip

...

grep -h '^zip&' dirlist*.txt

zip

特别的是,直接使用 ^$查找,得到的结果是空行。

填字游戏上的应用

在linux中,有个字典应用: /usr/share/dict

比如在十字填字游戏中,找某个单词,第三个字符是j,第五个字符是r的,长度为5个字符的单词:

grep -i '^..j.r$' /usr/share/dict/words

括号表达式和字符类

上面的功能,是在指定位置匹配某个字符,同样可以在某个位置匹配一组字符中的某一个。

使用括号来指定一组字符,匹配时这一组字符中的一个被包含即满足条件。

grep -h '[bg]zip' dirlist*.txt

bzip2

gzip

表示查找匹配包含bzip和gzip的内容。

在中括号内是没有元字符存在的,比较特殊的是,^(caret 脱字符)表示不包含,- (dash 横线)表示范围。

grep -h '[^bg]zip' dirlist*.txt

在dirlist开头的文本文件中,查找不包含bzip和gzip内容的行;显示行内容,不包括文件名。

注意,^只有在括号表达式的开头出现才是不包含的语义,其他情况下是普通字符。

传统的字符范围表示,在我们想指定可以匹配某个字符集,如果字符比较多,可以使用范围。

grep -h '^[ABCDEFGHIJKLMNOPQRSTUVWXYZ]' dirlist*.txt

查询所有大写字母开头的行。

可以简化为:

grep -h '^[A-Z]' dirlist*.txt

可以一次使用多个范围:

grep -h '^[A-za-z0-9]' dirlist*.txt

查询字母和数字开头的行内容。

如果需要匹配字符'-'本身,可以下面这样写:

grep -h '[-AZ]' dirlist*.txt

表示匹配 -, A , Z这三个字符中的某一个。

POSIX Character Classes

随着Unix/Linux的普及,OS不仅要支持U.S. English还要支持其他语言,所以对ASCII语言进行了扩展,扩展后增加到8位。

随之而来,POSIX标准也进入了一个概念:locale。针对不同地区可以选择不同的字符集。

echo $LANG

en_US.UTF-8

所以使用字符范围时,就会受到影响。

为了临时绕过这个问题,POSIX标准定义了一系列字符类,来提供更方便的字符范围选择。

Character Class & Description

[:alnum:] The alphanumeric characters. In ASCII, equivalent to: [A-Za-z0-9]

[:word:] The same as [:alnum:], with the addition of the underscore (_) character.

[:alpha:] The alphabetic characters. In ASCII, equivalent to: [A-Za-z]

[:blank:] Includes the space and tab characters. 

[:cntrl:] The ASCII control codes. Includes the ASCII characters 0 through 31 and 127.

[:digit:] The numerals 0 through 9.

[:graph:] The visible characters. In ASCII, it includes characters 33 through 126.

[:lower:] The lowercase letters.

[:punct:] The punctuation characters. In ASCII, equivalent to: [-!"#$%&'()*+,./:;<=>?@[\\\]_`{|}~]

[:print:] The printable characters. All the characters in [:graph:] plus the space character.

[:space:] The whitespace characters including space, tab, carriage return, newline, vertical tab, and form feed. In ASCII, equivalent to: [ \t\r\n\v\f]

[:upper:] The uppercase characters.

[:xdigit:] Characters used to express hexadecimal numbers. In ASCII, equivalent to: [0-9A-Fa-f]

ls /usr/sbin/[[:upper:]]*

/usr/sbin/MAKEFLOPPIES

。。。。

注意,上面不是正则表达式的使用示例,而是一个shell的路径名的扩展。都可以使用。

更改排序规则

使用local命令,查看地区设置:

$locale

LANG=en_US.UTF-8

LC_CTYPE="en_US.UTF-8"

LC_NUMERIC="en_US.UTF-8"

LC_TIME="en_US.UTF-8"

LC_COLLATE="en_US.UTF-8"

LC_MONETARY="en_US.UTF-8"

LC_MESSAGES="en_US.UTF-8"

LC_PAPER="en_US.UTF-8"

LC_NAME="en_US.UTF-8"

LC_ADDRESS="en_US.UTF-8"

LC_TELEPHONE="en_US.UTF-8"

LC_MEASUREMENT="en_US.UTF-8"

LC_IDENTIFICATION="en_US.UTF-8"

LC_ALL=

这个设置初始是系统安装时指定的,如果更改,可以影响到排序规则。

比如,通过修改LANG变量:

$export LANG=POSIX

如果要永久的让这个设置生效, 可以可以把这行加到 .bashrc文件里面。

export LANG=POSIX

POSIX Basic vs. Extended Regular Expressions

上面介绍的都是POSIX兼容的正则表达式。

扩展正则表达式,支持更多的metacharacters和与其相关的功能:

(    )   {    }    ?    +    |

但在BRE中, ( ) { } ,这4个字符不是元字符,但前面加上 \ 反斜线 backslash后,仍被解释为常量字符。

如果要使用扩展正则表达,需要grep -E 。

POSIX的历史

上世界80年代,Unix是一个非常流行的商业操作系统,但到1988年时,整个Unix的世界变得混乱不堪。

因为很多计算机生产商都从Unix的所有者AT&T实验室那里获得了源码授权,然后就把不同版本的Unix集成到自家的系统中。

在制作差异化的产品时,每家都对Unix进行了相应的修改和扩展。这样就限制了软件的兼容性。

每家厂商都想通过手段来锁定自己的客户。这段时间成为Unix的巴尔干化。

进入到电子电器工程师机构IEEE(Institute of Electrical and Electronics Engineers),在80年代中期,就开始开发一系列关于Unix如何操作的标准。

这些标准最开始叫做IEEE 1003,定义了API,shell和一些功能,作为类Unix系统需要具备的。

命令为POSIX,叫做可扩展操作系统接口Portable Operating System Interface,最后的X表示留有一定的扩展余地。

IEEE所采纳的正是Richard Stallman的提议。

Alternation 多选

第一个扩展正则功能,功能是匹配条件为多个表达式。

类似上面的中括号内多个字符只需匹配一个即可。

而多选功能就是匹配多个正则表达式中的某一个即可。

为了演示,我们使用grep命令和echo命令的组合,先用一个简单的字符串匹配:

$echo "AAA" | grep AAA

AAA

$echo "BBB" | grep AAA

上面例子将echo的输出用管道传给grep命令。

$echo "AAA" | grep -E 'AAA|BBB'

AAA

$echo "BBB" | grep -E 'AAA|BBB'

BBB

$echo "CCC" | grep -E 'AAA|BBB'

这里我们看到了正则表达式,表示AAA或BBB的匹配条件。

注意使用单引号,避免被shell把竖线解析成管道操作符。

可以使用多次竖线表示多个选项。

可以和其他正则表达式元素组合使用:

$grep -Eh '^(bz|gz|zip)' dirlist*.txt

$grep -Eh '^bz|gz|zip' dirlist*.txt

量词修饰符

?  某个元素匹配0次或1次

其实就是表示?前面的元素是可选的,可有可无。

比如查询电话号码:

(nnn) nnn-nnnn

nnn nnn-nnnn

构造正则表达式来匹配上面两种电话号码:

^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$

有问题的是,如果只有一半括号的话, 这个表达式并不能识别。

*  一个元素匹配0次或多次

[[:upper:]][[:upper:][:lower:] ]*\.

这个例子表示以句号结尾,首字母是大写字母,后面跟着任意数量的大小写字母或空格。

+  一个或多个元素匹配

+前面的元素,要有一个或多个。

举例: ^([[:alpha:]]+ ?)+$

[me@linuxbox ~]$ echo "This that" | grep -E '^([[:alpha:]]+ ?)+$'

This that

[me@linuxbox ~]$ echo "a b c" | grep -E '^([[:alpha:]]+ ?)+$'

a b c

[me@linuxbox ~]$ echo "a b 9" | grep -E '^([[:alpha:]]+ ?)+$'

[me@linuxbox ~]$

那这个正则表达式的意思,开头和结尾的锚定符用上了,表示这一行要都符合中间的模式。

中间内容表示,是有一个或多个字符,后面可以跟0个或1个空格。

所以“a b 9”不满足,因为里面有数字。

{} 匹配某个元素指定的次数

有以下几种表示方式:

{n}  前面元素正好出现n次

{n, m} 前面元素出现至少n次,但不超过m次。

{n, } 前面元素出现n次或n次以上

{, m} 前面元素出现不超过m次

之前的那个例子,表示电话号码的:

^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$

可以改成:

^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$

让我们来验证一下:

[me@linuxbox ~]$ echo "(555) 123-4567" | grep -E '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$'

(555) 123-4567

[me@linuxbox ~]$ echo "555 123-4567" | grep -E '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$'

555 123-4567

[me@linuxbox ~]$ echo "5555 123-4567" | grep -E '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$'

[me@linuxbox ~]$

改进后的表达式,功能一样。

使用举例:

我们先生成一个随机的电话号码文件:

[me@linuxbox ~]$ for i in {1..10}; do echo "(${RANDOM:0:3}) ${RANDOM:0:3}-${RANDOM:0:4}" >> phonelist.txt; done

这里RANDOM命令产生一个0 - 32767的随机数。

${parameter:offset:length},比如${RANDOM:0:3},这个格式表示从0偏移位置开始,取3个字符长度的数字。

查看产生的文件,里面是电话号码,不过会有些格式不符合,所以我们使用上面的正则表达式把不符合的找出来:

[me@linuxbox ~]$ grep -Ev '^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$'

phonelist.txt

(292) 108-518

(129) 44-1379

[me@linuxbox ~]$

使用选项-v是反向显示,找出不匹配的内容行。

找出不规范的路径名:

[me@linuxbox ~]$ find . -regex '.*[^-_./0-9a-zA-Z].*'

这个就是找出包含空格的路径名。

使用locate查找文件:

locate程序支持基本正则表达式和扩展正则表达式:--regexp和--regex

[me@linuxbox ~]$ locate --regex 'bin/(bz|gz|zip)'

/bin/bzcat

/bin/bzcmp

使用less和vim搜索文本:

在less和vim支持相同的方法来搜索文本,按下 / 字符然后输入一个正则表达式就能够搜索,定位搜索项后按下p或n,继续搜索前一个或后一个。

[me@linuxbox ~]$ less phonelist.txt

(232) 298-2265

(624) 381-1078

(540) 126-1980

(874) 163-2885

(286) 254-2860

(292) 108-518

(129) 44-1379

(458) 273-1642

(686) 299-8268

(198) 307-2440

~

~

~

/^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$

回车以后,内容匹配的行会高亮,就很容易发现不匹配格式的内容行。

使用vim要注意的是,不支持扩展正则只支持基本正则表达式。

所以要在command模式下输入:/([0-9]\{3\}) [0-9]\{3\}-[0-9]\{4\}

注意一些元字符需要前面加上反斜线进行转义。

需要高亮显示的话,要进行设置,根据vim版本不同,命令不同:

:hlsearch  或者  :set hlsearch

查询压缩文件:

[me@linuxbox ~]$ cd /usr/share/man/man1

[me@linuxbox man1]$ zgrep -El 'regex|regular expression' *.gz

使用zgrep命令可以读取压缩文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜流冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值