正则表达式学习笔记

1. 引言

以前我们用grep在一个文件中找出包含某些字符串的行,比如在头文件中找出一个宏定义。其实grep还可以找出符合某个模式(Pattern)的一类字符串。例如找出所有符合xxxxx@xxxx.xxx模式的字符串(也就是email地址),要求x字符可以是字母、数字、下划线、小数点或减号,email地址的每一部分可以有一个或多个x字符,例如abc.d@ef.com1_2@987-6.54,当然符合这个模式的不全是合法的email地址,但至少可以做一次初步筛选,筛掉a.bc@d等肯定不是email地址的字符串。再比如,找出所有符合yyy.yyy.yyy.yyy模式的字符串(也就是IP地址),要求y是0-9的数字,IP地址的每一部分可以有1-3个y字符。

如果要用grep查找一个模式,如何表示这个模式,这一类字符串,而不是一个特定的字符串呢?从这两个简单的例子可以看出,要表示一个模式至少应该包含以下信息:

  • 字符类(Character Class):如上例的x和y,它们在模式中表示一个字符,但是取值范围是一类字符中的任意一个。

  • 数量限定符(Quantifier): 邮件地址的每一部分可以有一个或多个x字符,IP地址的每一部分可以有1-3个y字符

  • 各种字符类以及普通字符之间的位置关系:例如邮件地址分三部分,用普通字符@.隔开,IP地址分四部分,用.隔开,每一部分都可以用字符类和数量限定符描述。为了表示位置关系,还有位置限定符(Anchor)的概念,将在下面介绍。

规定一些特殊语法表示字符类、数量限定符和位置关系,然后用这些特殊语法和普通字符一起表示一个模式,这就是正则表达式(Regular Expression)。例如email地址的正则表达式可以写成[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+,IP地址的正则表达式可以写成[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}。下一节介绍正则表达式的语法,我们先看看正则表达式在grep中怎么用。例如有这样一个文本文件testfile

192.168.1.1
1234.234.04.5678
123.4234.045.678
abcde

查找其中包含IP地址的行:

$ egrep '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' testfile
192.168.1.1
1234.234.04.5678

egrep相当于grep -E,表示采用Extended正则表达式语法。grep的正则表达式有Basic和Extended两种规范,它们之间的区别下一节再解释。另外还有fgrep命令,相当于grep -F,表示只搜索固定字符串而不搜索正则表达式模式,不会按正则表达式的语法解释后面的参数。

注意正则表达式参数用单引号括起来了,因为正则表达式中用到的很多特殊字符在Shell中也有特殊含义(例如\),只有用单引号括起来才能保证这些字符原封不动地传给grep命令,而不会被Shell解释掉。

192.168.1.1符合上述模式,由三个.隔开的四段组成,每段都是1到3个数字,所以这一行被找出来了,可为什么1234.234.04.5678也被找出来了呢?因为grep找的是包含某一模式的行,这一行包含一个符合模式的字符串234.234.04.567。相反,123.4234.045.678这一行不包含符合模式的字符串,所以不会被找出来。

grep是一种查找过滤工具,正则表达式在grep中用来查找符合模式的字符串。其实正则表达式还有一个重要的应用是验证用户输入是否合法,例如用户通过网页表单提交自己的email地址,就需要用程序验证一下是不是合法的email地址,这个工作可以在网页的Javascript中做,也可以在网站后台的程序中做,例如PHP、Perl、Python、Ruby、Java或C,所有这些语言都支持正则表达式,可以说,目前不支持正则表达式的编程语言实在很少见。除了编程语言之外,很多UNIX命令和工具也都支持正则表达式,例如grep、vi、sed、awk、emacs等等。“正则表达式”就像“变量”一样,它是一个广泛的概念,而不是某一种工具或编程语言的特性。

 

2. 基本语法

我们知道C的变量和Shell脚本变量的定义和使用方法很不相同,表达能力也不相同,C的变量有各种类型,而Shell脚本变量都是字符串。同样道理,各种工具和编程语言所使用的正则表达式规范的语法并不相同,表达能力也各不相同,有的正则表达式规范引入很多扩展,能表达更复杂的模式,但各种正则表达式规范的基本概念都是相通的。本节介绍egrep(1)所使用的正则表达式,它大致上符合POSIX正则表达式规范,详见regex(7)(看这个man page对你的英文绝对是很好的锻炼)。希望读者仿照上一节的例子,一边学习语法,一边用egrep命令做实验。

表 32.1. 字符类

字符含义举例
. 匹配任意一个字符 abc.可以匹配abcdabc9
[] 匹配括号中的任意一个字符 [abc]d可以匹配adbdcd
- []括号内表示字符范围 [0-9a-fA-F]可以匹配一位十六进制数字
^ 位于[]括号内的开头,匹配除括号中的字符之外的任意一个字符 [^xy]匹配除xy之外的任一字符,因此[^xy]1可以匹配a1b1但不匹配x1y1
[[:xxx:]] grep工具预定义的一些命名字符类 [[:alpha:]]匹配一个字母,[[:digit:]]匹配一个数字

 

表 32.2. 数量限定符

字符含义举例
? 紧跟在它前面的单元应匹配零次或一次 [0-9]?\.[0-9]匹配0.02.3.5等,由于.在正则表达式中是一个特殊字符,所以需要用\转义一下,取字面值
+ 紧跟在它前面的单元应匹配一次或多次 [a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+匹配email地址
* 紧跟在它前面的单元应匹配零次或多次 [0-9][0-9]*匹配至少一位数字,等价于[0-9]+[a-zA-Z_]+[a-zA-Z_0-9]*匹配C语言的标识符
{N} 紧跟在它前面的单元应精确匹配N次 [1-9][0-9]{2}匹配从100999的整数
{N,} 紧跟在它前面的单元应匹配至少N [1-9][0-9]{2,}匹配三位以上(含三位)的整数
{,M} 紧跟在它前面的单元应匹配最多M [0-9]{,1}相当于[0-9]?
{N,M} 紧跟在它前面的单元应匹配至少N次,最多M [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}匹配IP地址

 

再次注意grep找的是包含某一模式的行,而不是完全匹配某一模式的行。再举个例子,如果文本文件的内容是

aaabc
aad
efg

查找a*这个模式的结果是三行都被找出来了

$ egrep 'a*' testfile 
aabc
aad
efg

a*匹配0个或多个a,而第三行包含0个a,所以也包含了这一模式。单独用a*这样的正则表达式做查找没什么意义,一般是把a*作为正则表达式的一部分来用。

表 32.3. 位置限定符

字符含义举例
^ 匹配行首的位置 ^Content匹配位于一行开头的Content
$ 匹配行末的位置 ;$匹配位于一行结尾的;号,^$匹配空行
\< 匹配单词开头的位置 \<th匹配... this,但不匹配ethernettenth
\> 匹配单词结尾的位置 p\>匹配leap ...,但不匹配parentsleepy
\b 匹配单词开头或结尾的位置 \bat\b匹配... at ...,但不匹配catatexitbatch
\B 匹配非单词开头和结尾的位置 \Bat\B匹配battery,但不匹配... attendhat ...

 

位置限定符可以帮助grep更准确地查找,例如上一节我们用[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}查找IP地址,找到这两行

192.168.1.1
1234.234.04.5678

如果用^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$查找,就可以把1234.234.04.5678这一行过滤掉了。

表 32.4. 其它特殊字符

字符含义举例
\ 转义字符,普通字符转义为特殊字符,特殊字符转义为普通字符 普通字符<写成\<表示单词开头的位置,特殊字符.写成\.以及\写成\\就当作普通字符来匹配
() 将正则表达式的一部分括起来组成一个单元,可以对整个单元使用数量限定符 ([0-9]{1,3}\.){3}[0-9]{1,3}匹配IP地址
| 连接两个子表达式,表示或的关系 n(o|either)匹配noneither

 

以上介绍的是grep正则表达式的Extended规范,Basic规范也有这些语法,只是字符?+{}|()应解释为普通字符,要表示上述特殊含义则需要加\转义。如果用grep而不是egrep,并且不加-E参数,则应该遵照Basic规范来写正则表达式。

 

3. sed

sed意为流编辑器(Stream Editor),在Shell脚本和Makefile中作为过滤器使用非常普遍,也就是把前一个程序的输出引入sed的输入,经过一系列编辑命令转换为另一种格式输出。sedvi都源于早期UNIX的ed工具,所以很多sed命令和vi的末行命令是相同的。

sed命令行的基本格式为

sed option 'script' file1 file2 ...
sed option -f scriptfile file1 file2 ...

sed处理的文件既可以由标准输入重定向得到,也可以当命令行参数传入,命令行参数可以一次传入多个文件,sed会依次处理。sed的编辑命令可以直接当命令行参数传入,也可以写成一个脚本文件然后用-f参数指定,编辑命令的格式为

/pattern/action

其中pattern是正则表达式,action是编辑操作。sed程序一行一行读出待处理文件,如果某一行与pattern匹配,则执行相应的action,如果一条命令没有pattern而只有action,这个action将作用于待处理文件的每一行。

表 32.5. 常用的sed命令

/pattern/p 打印匹配pattern的行
/pattern/d 删除匹配pattern的行
/pattern/s/pattern1/pattern2/ 查找符合pattern的行,将该行第一个匹配pattern1的字符串替换为pattern2
/pattern/s/pattern1/pattern2/g 查找符合pattern的行,将该行所有匹配pattern1的字符串替换为pattern2

 

使用p命令需要注意,sed是把待处理文件的内容连同处理结果一起输出到标准输出的,因此p命令表示除了把文件内容打印出来之外还额外打印一遍匹配pattern的行。比如一个文件testfile的内容是

123
abc
456

打印其中包含abc的行

$ sed '/abc/p' testfile
123
abc
abc
456

要想只输出处理结果,应加上-n选项,这种用法相当于grep命令

$ sed -n '/abc/p' testfile
abc

使用d命令就不需要-n参数了,比如删除含有abc的行

$ sed '/abc/d' testfile
123
456

注意,sed命令不会修改原文件,删除命令只表示某些行不打印输出,而不是从原文件中删去。

使用查找替换命令时,可以把匹配pattern1的字符串复制到pattern2中,比如:

$ sed 's/bc/-&-/' testfile
123
a-bc-
456

pattern2中的&表示原文件的当前行中与pattern1相匹配的字符串,再比如:

$ sed 's/\([0-9]\)\([0-9]\)/-\1-~\2~/' testfile
-1-~2~3
abc
-4-~5~6

pattern2中的\1表示与pattern1的第一个()括号相匹配的内容,\2表示与pattern1的第二个()括号相匹配的内容。sed默认使用Basic正则表达式规范,如果指定了-r选项则使用Extended规范,那么()括号就不必转义了。

如果testfile的内容是

<html><head><title>Hello World</title>
<body>Welcome to the world of regexp!</body></html>

现在要去掉所有的HTML标签,使输出结果为

Hello World
Welcome to the world of regexp!

怎么做呢?如果用下面的命令

$ sed 's/<.*>//g' testfile

结果是两个空行,把所有字符都过滤掉了。这是因为,正则表达式中的数量限定符会匹配尽可能长的字符串,这称为贪心的(Greedy)[39]。比如sed在处理第一行时,<.*>匹配的并不是<html><head>这样的标签,而是

<html><head><title>Hello World</title>

 

4. awk

sed以行为单位处理文件,awksed强的地方在于不仅能以行为单位还能以列为单位处理文件。awk缺省的行分隔符是换行,缺省的列分隔符是连续的空格和Tab,但是行分隔符和列分隔符都可以自定义,比如/etc/passwd文件的每一行有若干个字段,字段之间以:分隔,就可以重新定义awk的列分隔符为:并以列为单位处理这个文件。awk实际上是一门很复杂的脚本语言,还有像C语言一样的分支和循环结构,但是基本用法和sed类似,awk命令行的基本形式为:

awk option 'script' file1 file2 ...
awk option -f scriptfile file1 file2 ...

sed一样,awk处理的文件既可以由标准输入重定向得到,也可以当命令行参数传入,编辑命令可以直接当命令行参数传入,也可以用-f参数指定一个脚本文件,编辑命令的格式为:

/pattern/{actions}
condition{actions}

sed类似,pattern是正则表达式,actions是一系列操作。awk程序一行一行读出待处理文件,如果某一行与pattern匹配,或者满足condition条件,则执行相应的actions,如果一条awk命令只有actions部分,则actions作用于待处理文件的每一行。比如文件testfile的内容表示某商店的库存量:

ProductA  30
ProductB  76
ProductC  55

打印每一行的第二列:

$ awk '{print $2;}' testfile
30
76
55

自动变量$1$2分别表示第一列、第二列等,类似于Shell脚本的位置参数,而$0表示整个当前行。再比如,如果某种产品的库存量低于75则在行末标注需要订货:

$ awk '$2<75 {printf "%s\t%s\n", $0, "REORDER";} $2>=75 {print $0;}' testfile
ProductA  30    REORDER
ProductB  76
ProductC  55    REORDER

可见awk也有和C语言非常相似的printf函数。awk命令的condition部分还可以是两个特殊的conditionBEGINEND,对于每个待处理文件,BEGIN后面的actions在处理整个文件之前执行一次,END后面的actions在整个文件处理完之后执行一次。

awk命令可以像C语言一样使用变量(但不需要定义变量),比如统计一个文件中的空行数

$ awk '/^ *$/ {x=x+1;} END {print x;}' testfile

就像Shell的环境变量一样,有些awk变量是预定义的有特殊含义的:

表 32.6. awk常用的内建变量

FILENAME 当前输入文件的文件名,该变量是只读的
NR 当前行的行号,该变量是只读的,R代表record
NF 当前行所拥有的列数,该变量是只读的,F代表field
OFS 输出格式的列分隔符,缺省是空格
FS 输入文件的列分融符,缺省是连续的空格和Tab
ORS 输出格式的行分隔符,缺省是换行符
RS 输入文件的行分隔符,缺省是换行符

 

例如打印系统中的用户帐号列表

$ awk 'BEGIN {FS=":"} {print $1;}' /etc/passwd

awk还可以像C语言一样使用if/elsewhilefor控制结构,此处从略。

 

5. awk用法补充

1. awk 中的 if 语句

逐行读取文件,每行以空格分开的字符串当做参数,从 $1 开始。

(1) 语法

awk '{if (condition) {statement} }' [input_file]

awk '{
  if (condition) 
    {command1} 
  else 
    {command2}
}' [input_file]


awk '{
    if(condition1){
        command1
    }
    else if(condition2) {
        command2
    }
    else if(condition3) {
        command3
    }
    .
    .
    .
    else{
        commandN
    }
}' [input_file]

(2) 例子

awk '{                                        
if ($1=="100")

  {
    print "Name : " ,$2;
    print "Age : ",$3;
    print "Department : " ,$4;
  }
  
}' linuxmi.txt

----------执行结果-----------------

Name :  Robert
Age :  23
Department :  Commerce

写成1行:

awk '{ if ($1=="100")  { print "............... \n"; print "Name : " ,$2; print "Age : ",$3; print "Department : " ,$4; } }' linuxmi.txt

if..else的例子:

//脚本中只能用空格,不能用tab键
awk '{
    if ($3<20) {
       print "Student "$2,"of department", $4, "is less than 20 years old"
    } else {
        print "Student "$2,"of department", $4, "is more than 20 years old"
    }
}' linuxmi.txt

----------执行结果-----------------

 is more than 20 years old Subject
 is less than 20 years oldent DevOps
 is more than 20 years old SecOps
 is more than 20 years old IT
 is more than 20 years oldnt Commerce
 is more than 20 years oldment linuxmi
 is more than 20 years oldMaths
 is less than 20 years oldnt Arts

这个导致第一行"ID Name Age Subject" 也被显示出来了。下面使用正则表达式进行过滤。正则表达式 /[0-9]+$/ 表示数值。~表示参数有一个数字。在它们前面的反运算符意味着字段不应该有数字。

awk '{
    if (! ($3 ~ /[0-9]+$/)) {
        print "Age is just a number but you do not have a number"
    } else if ($3<20) {
        print "Student "$2,"of department", $4, "is less than 20 years old"
    } else {
        print "Student "$2,"of department", $4, "is more than 20 years old"
    }
}' linuxmi.txt

----------------------------执行结果-----------------------------
Age is just a number but you do not have a number
 is less than 20 years oldent DevOps
 is more than 20 years old SecOps
 is more than 20 years old IT
 is more than 20 years oldnt Commerce
 is more than 20 years oldment linuxmi
 is more than 20 years oldMaths
 is less than 20 years oldnt Arts

2. 使用 awk 程序文件

# cat linuxmi.awk

{
    if (! ($3 ~ /[0-9]+$/)) {
        print "Age is just a number but you do not have a number"
    } else if ($3<20) {
        print "Student "$2,"of department", $4, "is less than 20 years old"
    } else {
        print "Student "$2,"of department", $4, "is more than 20 years old"
    }
}

# awk -f linuxmi.awk linuxmi.txt
Age is just a number but you do not have a number
 is less than 20 years oldent DevOps
 is more than 20 years old SecOps
 is more than 20 years old IT
 is more than 20 years oldnt Commerce
 is more than 20 years oldment linuxmi
 is more than 20 years oldMaths
 is less than 20 years oldnt Arts

3. 使用三元操作符代替

//语法:
(condition) ? Command1 : Command2

$ awk '{print ($3 <=20)? "Age less than 20: " $2 : "Age over 20: " $2}' linuxmi.txt
Age over 20: Name
Age less than 20: Farhaan
Age over 20: Alex
Age over 20: Ronn
Age over 20: Robert
Age less than 20: Samantha
Age over 20: Bob
Age less than 20: Rihaan

 

4. 使用awk进行过滤操作

(1) 查看kworker线程的绑核情况

# for P in `ps -elf | grep -v grep | grep kworker | awk '{print $2}'`; do echo $P; cat /proc/$P/comm; cat /proc/$P/status | grep Cpus_allowed_list; done;

(2) 查看rcu相关线程的优先级

# for P in `ps -AT | grep rcu | awk '{print $2}'`; do echo $P; cat /proc/$P/comm; cat /proc/$P/sched | grep prio; done;

 

 

 

参考:

1. 正则表达式:https://akaedu.github.io/book/ch32.html

2. Linux 下强大的 awk 命令: https://mp.weixin.qq.com/s/WEE702IgzgUbRI7u3K39gg

 

posted on 2020-02-06 23:12  Hello-World3  阅读(231)  评论(0编辑  收藏  举报

导航