l19.Linux正则表达式与三剑客知识应用实践

949 阅读23分钟

“ 本文正在参加「金石计划 . 瓜分6万现金大奖」 ”

11.1 正则表达式介绍

11.1.1 什么是正则表达式?

简单地说,正则表达式就是为处理大量的字符串及文本而定义的一套规则和方法。假设“@”代表“I am”,“!” 代表“neteagle”,则执行echo "@!"的结果就是输出“I am neteagle”。通过这些特殊符号的辅助,管理员可以快速过滤、替换或输出所需要的字符串,让Linux运维工作变得更高效。

Linux三剑客的正则表达式具有如下几个特点。

  • 其是为处理大量文本及字符串而定义的一套规则和方法。
  • 其工作时行为单位进行,即一次处理一行。
  • 正则表达式可以将复杂的处理任务化繁为简,以提高操作Linux的效率。
  • 仅受三剑客(grep/egrep、seq、awk)命令支持,其他命令无法使用。

11.1.2 为什么要学习正则表达式?

实际企业中,运维工程师在进行Linux运维工作时,通常都会面对大量带有字符串的内容,biubiu文本配置、程序、命令输出及日志文件等,而我们经常会有迫切的需求,比如,要从大量的字符串内容中查找符合工作需要的特定的字符串,这就需要借助正则表达式强大功能的帮助了。因此,可以说正则表达式就是为过滤这样特殊的字符串而生的!

例如,若要从ifconfig的输出中取出IP地址段的内容,则可以利用如下命令配合正则表达式字符匹配功能来实现。示例代码如下:

[root@centos6 ~]# ifconfig eth0 |sed -rn '2s#^.*addr:(.*)Bc.*$#\1#gp' 
10.0.0.202  
[root@centos7 ~]# ifconfig eth0 |sed -rn '2s#^.*inet (.*) net.*$#\1#gp'
10.0.0.201 

11.1.3 有关正则表达式容易混淆的事项

正则表达式的应用非常广泛,存在于各种语言之中,例如,Python、Java、Perl(PCRE)等。但是,本文所讲的是Linux系统运维工作中的正则表达式,即Linux正则表达式,应用正则表达式的命令就是grep(egrep)、seq、awk,换句话说,Linux三剑客要想工作得更高效,那么一定是离不开正则表达式的配合。注意,其他普通命令在正常情况下是无法使用正则表达式的。

正则表达式与前文讲解的通配符、特殊字符是有本质区别的,正则表达式在Linux中是通过三剑客命令在文件(或数据流)中过滤内容的。而通配符则是大部分普通命令都支持的,它主要可用来查找文件或目录,比如说查找以.txt结尾的文件时,就是使用"*.txt"这样的字符串进行匹配的。这一点请大家注意。

11.1.4 学习正则表达式的注意事项

在学习正则表达式之前,先来看一下几点说明,具体如下。

  • LInux正则表达式是以行为单位进行处理的。
  • 正则表达式仅适用于三剑客命令,为了方便讲解,本章更多使用grep和egrep命令进行演示,并且还会建议为它们加上一个别名配置,示例代码如下:
alias grep='grep --color=auto'
alias egrep='egrep --color=auto'    #配置后会将匹配上的内容用红色显示,仅CentOS 6需要配置。
  • 注意LC_ALL环境变量的设置,建议设置如下:
export LC_ALL=C

完整的处理及生效命令如下:

[root@centos6 ~]# cat >>/etc/profile<<EOF
> alias grep='grep --color=auto'
> alias egrep='egrep --color=auto'
> export LC_ALL=C
> EOF
[root@centos6 ~]# source /etc/profile

11.2 正则表达式的分类

Linux三剑客的正则表达式可分为两类,具体如下。

  • 基本正则表达式(BRE,baasic regular expression)

    BRE对应的元字符有“^$.[]*”。

  • 扩展正则表达式(ERE,extended regular expression)

    ERE在BRE的基础上增加了"() {} ? + |"等字符。

11.2.1 基本正则表达式(BRE)集合

为了让读者能够更加直观地掌握基本的正则表达式知识,表11-1针对所有常用的正则表达式特殊字符及功能进行了说明。

表11-1 基本正则表达式常用的特殊字符及功能

b11-1.png

提示: 后文将会对基本正则表达式字符实践进行测试。

11.2.2 扩展正则表达式(ERE)集合

为了让读者能够更加直观地掌握扩展正则表达式的知识,表11-2针对所有常用的扩展正则表达式特殊字符及功能进行了说明。

表11-2 扩展正则表达式常用的特殊字符及功能

b11-2.png

特别强调:支持扩展正则的3中方法

  • grep 命令加“-E”参数也可以使用扩展正则。
  • grep命令不加参数也可以使用扩展正则表达式的特殊字符,但有个条件,就是需要在使用的每个特殊的字符前面加“\”(反斜线)。
  • egrep命令可直接支持扩展正则。

11.3 基本正则表达式实践

11.3.1 实践环境准备

[root@centos7 ~]# mkdir test
[root@centos7 ~]# cat >~/test/neteagle.txt<<EOF
> I am neteagle teacher!
> I teach linux.
> 
> I like badminton ball,billiard ball and chinese chess!
> our site is http://www.neteagles.cn
> my qq num is 19661891.
> 
> not 190000661891.
> my god,i am not neteagee,but NETEAGLE!
> EOF

最终文件输出如下:

[root@centos7 ~]# cd ~/test/
[root@centos7 ~/test]# cat -n neteagle.txt 
     1  I am neteagle teacher!
     2  I teach linux.
     3  
     4  I like badminton ball,billiard ball and chinese chess!
     5  our site is http://www.neteagles.cn
     6  my qq num is 19661891.
     7  
     8  not 190000661891.
     9  my god,i am not neteagee,but NETEAGLE!

11.3.2 "^"(尖角号)功能实践

"^"称为尖角号,用法如“^neteagle”,表示匹配以neteagle单词开头的行,下面来看个具体示例。

范例11-1: 输出以m开头的所有行并打印行号。

[root@centos7 ~/test]# grep -n "^m" neteagle.txt #-n是打印过滤的内容在原文件中的行号。
6:my qq num is 19661891.
9:my god,i am not neteagee,but NETEAGLE!

"^m"表示找出以m字母开头的所有行,并显示出来。注意,若没特殊需求,请用双引号将要过滤的内容(单引号也可以)引起来,否则可能会遇到异常问题。

11.3.3 “$”(美元符)功能实践

”符合,称为美元符,用法如“neteagle”符合,称为美元符,用法如“neteagle”,表示匹配以neteagle单词结尾的行,下面来看个具体示例。

范例11-2: 显示以n结尾的所有行。

[root@centos7 ~/test]# grep -n "n$" neteagle.txt 
5:our site is http://www.neteagles.cn

“n”表示以字母n结尾的行。注意"”表示以字母n结尾的行。注意""符号在后面,表示以“...”结尾的行。

提示: 默认情况下,Linux下的所有文件结尾都有一个""符,可使用catA查看文件中每行结尾的“"符,可使用cat -A查看文件中每行结尾的“”符,表示每行结尾。

[root@centos7 ~/test]# grep -n "n$" neteagle.txt |cat -A
5:our site is http://www.neteagles.cn$

11.3.4 "^$"功能实践

范例11-3: 显示空行及对应的行号。

[root@centos7 ~/test]# grep -n "^$" neteagle.txt 
3:
7:

简单理解,“^”表示的空行可以看做“”表示的空行可以看做“”符开头,即以结尾开头,或者看作以“^”结尾,即以开头结尾,其实两者都是表示中间没有任何内容,即空行。

注意: 空行中什么都没有,没有任何符合或字符,有空格也不算空行。

11.3.5 “.”(点)功能实践

在正则表达式中,"."(点号)表示匹配任意一个字符(有且只有一个),但是不包含空行。

范例11-4: 匹配任意一个字符并输出对应文件中的行号。

[root@centos7 ~/test]# grep -n "." neteagle.txt 
1:I am neteagle teacher!
2:I teach linux.
#没有显示第3行空行。
4:I like badminton ball,billiard ball and chinese chess!
5:our site is http://www.neteagles.cn
6:my qq num is 19661891.
#没有显示第7行空行。
8:not 190000661891.
9:my god,i am not neteagee,but NETEAGLE!

"."(点号)匹配任意一个字符,所以文件的内容都出来了,但是上面找到的内容不包含空行。

范例11-5: 显示以“.”(点号)结尾的行。

[root@centos7 ~/test]# grep ".$" neteagle.txt 
I am neteagle teacher!
I teach linux.
I like badminton ball,billiard ball and chinese chess!
our site is http://www.neteagles.cn
my qq num is 19661891.
not 190000661891.
my god,i am not neteagee,but NETEAGLE!

在这里“.$”表示的是以任意单个字符结尾的行,因此结果并不是我们想要的以点结尾的行,因为“.”(点号)表示任意一个字符。正确的答案见下文。

11.3.6 “\”(转义符)功能实践

转移符合(\)也称撬棍,主要用途是让有特殊含义的符合(字符)脱掉马甲,现出原形。

比如前文中的范例11-5"显示文件中以“.”(点号)结尾的行"的正确答案为:

[root@centos7 ~/test]# grep ‘.$’ neteagle.txt #使用转义符(撬棍),去掉“.”(点号)的特殊含义变为小数点。
I teach linux.
my qq num is 19661891.
not 190000661891.

因为点号在正则表达式中具有特殊含义,如果你想使用点本身的含义,就需要使用撬棍(转义字符)处理特殊符合。

11.3.7 “*”(星号)功能实践

“*”表示匹配前一个字符0次或0次以上,下面通过实践来理解这个符号。

范例11-6: 输出文件中连续出现0次或0次以上的数字0的内容。

[root@centos7 ~/test]# grep0*’ neteagle.txt 
I am neteagle teacher!
I teach linux.
​
I like badminton ball,billiard ball and chinese chess!
our site is http://www.neteagles.cn
my qq num is 19661891.
​
not 190000661891.
my god,i am not neteagee,but NETEAGLE!

从结果可以看出,grep的确将连续的数字0都找到了,并且显示了它们所在的行。

提示: 这里的“0*”之所以会将文件所有行都匹配显示出来,是因为“*”表示前一个字符连续出现了0次。0次即什么也没有,实际上每一行也都被匹配了,整个文件内容都显示出来了。

11.3.8 “.*”组合符工具实践

“.”(点号)表示任意一个字符,“”(星号)表示匹配前一个字符0次或0次以上,所有“.”放在一起就表示匹配所有字符,包括空行。

范例11-7: 通过grep显示文件的所有内容,包括空行。

[root@centos7 ~/test]# grep ‘.*’ neteagle.txt 
I am neteagle teacher!
I teach linux.
​
I like badminton ball,billiard ball and chinese chess!
our site is http://www.neteagles.cn
my qq num is 19661891.
​
not 190000661891.
my god,i am not neteagee,but NETEAGLE!

这样就把整个文件的内容都显示出来了。

范例:测试“^.*o”能够匹配到的内容。

[root@centos7 ~/test]# grep ‘^.*o’ neteagle.txt 
I like badminton ball,billiard ball and chinese chess!
our site is http://www.neteagles.cn
not 190000661891.
my god,i am not neteagee,but NETEAGLE!

通过这个正则表达式我们可以发现,这里想要匹配任意多个字符开头一直到第一个字母为o为止的内容。

可是我们通过执行的结果可以看出,“^.*o”实际上是匹配到了每一行的最后一个字母o。这种匹配相同字符到最后一个字符的特点成为正则表达式的贪婪性。

熟悉正则表达式的这种特点,有助于我们以后对正则表达式的熟练使用。

11.3.9 “[abc]”(中括号)功能实践

中括号表达式,[abc]表示匹配一个"[]"中的任何一个字符,a或b或c。

常用的形式具体如下。

  • [a-z]表示匹配所有单个小写字母。

  • [A-Z]表示匹配所有单个大写字母。

  • [a-zA-Z]表示匹配所有单个大小写字母。

  • [0-9]表示匹配所有 数字。

  • [a-zA-Z0-9]表示匹配所有字母和数字。

    这些都是正则表达式中比较常见的形式。

    范例11-9: 匹配出文件中所有的大写字母。

    [root@centos7 ~/test]# grep '[A-Z]' neteagle.txt 
    I am neteagle teacher!
    I teach linux.
    I like badminton ball,billiard ball and chinese chess!
    my god,i am not neteagee,but NETEAGLE!
    

    范例11-10: 中括号表达式的误区示例。

t11-1.png

  • 图11-1 中括号正则表达式示例

    从图11-1所示的结果可以看出,[neteagle]这里实际是匹配到小写字母n或e或t或e或a或g或l或e中的任何一个字符。

    读者可以通过grep命令的-o(小写字母o)来显示grep命令到底匹配到了什么。

    [root@centos7 ~/test]# grep -o '[neteagle]' neteagle.txt 
    a
    n
    e
    t
    e
    a
    g
    l
    e
    t
    e
    a
    e
    t
    e
    a
    l
    n
    l
    e
    a
    n
    t
    n
    a
    l
    l
    l
    l
    a
    a
    l
    l
    a
    n
    n
    e
    e
    e
    t
    e
    t
    t
    n
    e
    t
    e
    a
    g
    l
    e
    n
    n
    n
    t
    g
    a
    n
    t
    n
    e
    t
    e
    a
    g
    e
    e
    t
    

    通过grep -o命令的执行结果中,每一行就是grep命令每一次找到的内容。可以发现grep "[neteagle]"实际上是一次找到了一个匹配的字符。

1.3.10 “[^abc]”(中括号内取反符)功能实践

[^abc]或[^a-c]这种命令中,“^”在中括号的第一位时表示排除,即排除字母a或字母b或字母c,这里在"[]"里面出现的尖角号就表示取反(非)。

范例11-11: 匹配除了小写字母以外的符号,如图11-2所示。

t11-2.png

图11-2 中括号内取反图示

11.4 扩展正则表达式实践

需要说明的是,这里的扩展正则表达式功能请用egrep或者grep -E进行测试。

11.4.1 “+”(加号)功能实践

“+”(加号)表示匹配前一个字符1次或1次以上,其与基础表达式里的""类似,只不过比“”少了一个匹配0次。

范例11-12: 匹配文件中的数字0一次或多次。

[root@centos7 ~/test]# egrep "0+" neteagle.txt 
not 190000661891.
[root@centos7 ~/test]# egrep -o "0+" neteagle.txt 
0000

注意,“+”是扩展正则表达式符合,而grep默认是不支持扩展正则的,需要使用grep -E或egrep,这里推荐使用egrep。

11.4.2 “?”(问号)功能实践

“?”(问号)表示匹配前一个字符0次或1次,这个符合在实际运维工作中使用得不多。

范例11-13: 显示文件中包含gd或god的行。

[root@centos7 ~/test]# cat >neteagle2.txt<<EOF
> good
> glad
> gd
> god
> goood
> EOF
[root@centos7 ~/test]# egrep 'go?d' neteagle2.txt 
gd
god

表示重复0次或表示连续出现0次的时候,就是什么都没有,这里就是将gd匹配出来了。

11.4.3 “|”(竖线)功能实践

竖线在扩展正则表达式中表示或者,下面来看一个具体示例。

范例11-14: 取出包含3306或1521的行。

[root@centos7 ~/test]# egrep '3306|1521' /etc/services 
mysql           3306/tcp                        # MySQL
mysql           3306/udp                        # MySQL
ncube-lm        1521/tcp                # nCube License Manager
ncube-lm        1521/udp                # nCube License Manager

11.4.4 “()”(小括号)功能实践

“()”小括号的功能之一是分组过滤被括起来的内容,括号内的内容表示一个整体,另外“()”的内容可以被后面的“\n”引用,n为数字,表示引用第几个括号的内容。

【分组功能实践】

范例11-15: 取出包含good或glad的行。

[root@centos7 ~/test]# cat >neteagle3.txt<<EOF
> good
> glad
> gd
> god
> goood
> EOF

这里我们希望使用的方法应是尽量简单的,因为good和glad的第一个和最后一个字母都一样。所以我们来看看,oo或la这样的形式是否能够找出我们想要的结果。

[root@centos7 ~/test]# egrep 'goo|lad' neteagle3.txt 
good
glad
goood

上述命令找出了包含goo或lad这两个内容的行,但从匹配的内容来看,结果并不完全是我们想要的,这里就可以使用"()"匹配到如下内容了:

[root@centos7 ~/test]# egrep 'g(oo|la)d' neteagle3.txt 
good
glad

【小括号的后向引用功能实践】

[root@centos7 ~/test]# egrep "(o)\1" neteagle3.txt  #\1重复第一个小括号内的内容,一起匹配两个o。
good
goood

提示: 使用egrep “(neteagle)(youngboy)(littleboy)\1\2\3”时,“\1”表示匹配第一个括号内的内容,即neteagle,“\2”表示匹配第二个括号内的内容,即youngboy,“\3”表示匹配第三个括号内的内容,即littleboy,以此类推,这个后向的应用实际上在sed命令中应用得更多一些。

11.4.5 {n,m}匹配次数功能实践

范例11-16: 重复前一个字符各种次数的例子。

[root@centos7 ~/test]# egrep "0{3,5}" neteagle.txt  #匹配数字035次。
not 190000661891.
[root@centos7 ~/test]# egrep "0{,5}" neteagle.txt   #匹配数字0,最多5次,全部输出了。
I am neteagle teacher!
I teach linux.
​
I like badminton ball,billiard ball and chinese chess!
our site is http://www.neteagles.cn
my qq num is 19661891.
​
not 190000661891.
my god,i am not neteagee,but NETEAGLE!
[root@centos7 ~/test]# egrep "0{3,}" neteagle.txt   #匹配数字0,最少3次。 
not 190000661891.
[root@centos7 ~/test]# egrep "0{3}" neteagle.txt    #匹配数字03次。 
not 190000661891.

11.5 预定义特殊中括号表达式

11.5.1 预定义特殊中括号表达式介绍

正则表达式中也存在一些已经定义好了的,可直接使用的中括号表达式,见表11-3。

表11-3 特殊预定义表达式

b11-3.png

11.5.2 预定义特殊中括号表达式实践

表11-3中的预定于特殊中括号表达式不好记忆,并且有被替代的方法,因此实际工作中使用得并不多,这里给出简单的实践,以供读者参考。

范例11-17: 正则表达式中括号表达式的使用范例。

not 190000661891.
[root@centos7 ~/test]# grep "[[:digit:]]" neteagle.txt #匹配所有数字。
my qq num is 19661891.
not 190000661891.
[root@centos7 ~/test]# grep "[[:upper:]]" neteagle.txt   #匹配大写字母。
I am neteagle teacher!
I teach linux.
I like badminton ball,billiard ball and chinese chess!
my god,i am not neteagee,but NETEAGLE!

注意: 在使用中括号表达式时,需要在外面加上一层"[]"。这里的[[:alnum:]]就相当于[a-zA-Z0-9]。

11.6 元字符表达式

11.6.1 元字符表达式介绍

元字符(meta character)是一种Perl风格的正则表达式,并不是所有的文本处理工具都支持,只有一部分文本处理工具支持元字符,具体见表11-4。

表11-4 元字符表达式

b11-4.png

特别说明:网上有很多特殊的元字符列表,大部分不能直接被grep等命令使用。

11.6.2 元字符表达式实践

[root@centos7 ~/test]# cat >test.txt<<EOF
> I am neteagle,not neteagle_10.
> EOF
[root@centos7 ~/test]# grep "\bneteagle\b" test.txt
        #匹配neteagle这个单词,可用<neteagle>替代。
I am neteagle,not neteagle_10.
[root@centos7 ~/test]# grep 'neteagle\B' test.txt
    #匹配neteagle字符串,但不匹配neteagle这个单词。
I am neteagle,not neteagle_10.
[root@centos7 ~/test]# grep '\w' test.txt
    #匹配字母、数字、下划线,不匹配其他字符。
I am neteagle,not neteagle_10.
[root@centos7 ~/test]# grep '\W' test.txt   #对\w的匹配取反。
I am neteagle,not neteagle_10.
[root@centos7 ~/test]# grep -P '\d' test.txt    #匹配数字,注意要加-P(大写)参数才行。
I am neteagle,not neteagle_10.
[root@centos7 ~/test]# grep -P '\D' test.txt    #匹配非数字,注意要加-P(大写)参数才行。
I am neteagle,not neteagle_10.

d11-1.png

11.7 sed:流编辑器

11.7.1 sed命令语法及参数说明

【命令星级】 ★★★★★

【功能说明】

sed是Stream Editor(字符流编辑器)的缩写,简称流编辑器。它是Linux三剑客之一。

sed是操作、过滤和转换文本内容的强大工具。sed的常用功能包含对文件实现快速增删改查(增加、删除、修改、查询),其中查询的功能中最常用的两大功能是过滤(过滤指定字符串)和取行(取出指定的行)。

【语法格式】

sed [选项] [sed内置命令字符] [输入文件]

说明:

1)为了避免混淆,本文称呼sed为sed或sed命令,将实现不同sed功能的内部命令参数,称为“sed内置命令字符”,以区别是sed命令还是sed内置命令选项。

2)“sed内置命令字符”既可以是单个命令,也可以是多个命令参数的组合。

3)“输入文件”为sed需要处理的文件,是可选项,sed还能从标准输入(如管道)获取输入。

【选项说明】

表11-5针对sed命令的参数选项进行了说明。

表11-5 sed常用参数选项

b11-5.png

sed内置命令字符可用来实现对文件的不同操作功能,例如对文件的增删改查等,表11-6为sed的内置命令字符说明。

表11-6 sed的常用内置命令字符功能说明

b11-6.png

11.7.2 sed命令应用实践

首先进行测试文件的准备,代码如下:

[root@centos7 ~/test]# cat >neteagle4.txt<<EOF 
> I am neteagle teacher!
> I like badminton ball,billiard ball and chinese chess!
> our site is http://www.neteagles.cn
> my qq num is 19661891.
> EOF
[root@centos7 ~/test]# cat -n neteagle4.txt 
     1  I am neteagle teacher!
     2  I like badminton ball,billiard ball and chinese chess!
     3  our site is http://www.neteagles.cn
     4  my qq num is 19661891.

问题1: 输出neteagle4.txt第2-3行的内容。 ※

解答: 这个问题考察的是sed的-n参数,及p功能字符的使用方法,该命令十分常用,在实际工作中,它会输出大文件中间的若干行。

[root@centos7 ~/test]# sed -n '2,3p' neteagle4.txt  #n表示默认不输出,根据要求输出第2-3行,输出用pI like badminton ball,billiard ball and chinese chess!
our site is http://www.neteagles.cn

问题2: 过滤出含有neteagle字符串的行。 ※

解答: 这里是考察sed命令的过滤功能,类似于grep的过滤,不同的是需要用双斜线将需要过滤的字符串包含在中间。

[root@centos7 ~/test]# sed -n '/neteagle/p' neteagle4.txt   #n表示默认不输出,利用p输出包含neteagle的行。
I am neteagle teacher!
our site is http://www.neteagles.cn

问题3: 删除包含neteagle字符串的行。 ※

[root@centos7 ~/test]# sed '/neteagle/d' neteagle4.txt  #用d符号删除包含neteagle的行。 
I like badminton ball,billiard ball and chinese chess!
my qq num is 19661891.

提示: 如果要修改文件内容请使用“sed -i '/neteagle/d' neteagle4.txt”命令,注意测试完改回来。

扩展:删除指定的行。

sed -i '3d' neteagle4.txt   #删除第3行。
sed -i '5,8d' neteagle4.txt #删除第5~8行。

问题4: 将文件中neteagle字符串全部替换为youngboy。※

解答: 这是考察sed命令的替换功能,默认不会修改文件,如果需要修改文件,则要用-i参数配合,这个命令在工作中比较常用。

[root@centos7 ~/test]# sed 's#neteagle#youngboy#g' neteagle4.txt    #sg表示全局替换,中间的间隔符可以用“#@/”等符合替代,前两个“#”号之间表示想要替换的内容,后两个“#”号之间表示替换后的内容。
I am youngboy teacher!
I like badminton ball,billiard ball and chinese chess!
our site is http://www.youngboys.cn
my qq num is 19661891.

问题5: 将文件中neteagle字符串全部替换为youngboy,同事将QQ号码19661891改为116301165。

解答: 这里考察sed命令的-e参数,多项编辑功能,这里不需要使用多次管道了。

[root@centos7 ~/test]# sed -e 's#neteagle#youngboy#g' -e "s#19661891#116301165#g" neteagle4.txt 
I am youngboy teacher!
I like badminton ball,billiard ball and chinese chess!
our site is http://www.youngboys.cn
my qq num is 116301165.

问题6: 在neteagle4.txt文件的第2行后追加文本。

解答: 这里考察sed命令的a字符功能,示例代码如下。

[root@centos7 ~/test]# sed -i '2a I teacher linux.' neteagle4.txt   #这里使用了sed内置命令a追加功能,在第二行后面增加一行,内容为“I teacher linux.”。
[root@centos7 ~/test]# cat neteagle4.txt 
I am neteagle teacher!
I like badminton ball,billiard ball and chinese chess!
I teacher linux.    #这里是增加的行,在第二行之后。
our site is http://www.neteagles.cn
my qq num is 19661891.

提示: 也可以同事增加多行,不同行之间使用"\n"间隔开,例如:sed '2a I teacher linux.\nand you?' neteagle4.txt。

问题7: 在neteagle4.txt文件的地行插入文本。

解答: 这里考察sed命令的i字符功能,示例代码如下。

[root@centos7 ~/test]# sed '2i I teacher linux,at 2i.' neteagle4.txt 
[root@centos7 ~/test]# cat neteagle4.txt 
I am neteagle teacher!
I teacher linux,at 2i.  #这里是增加的行,在第二行,与a不同。
I like badminton ball,billiard ball and chinese chess!
I teacher linux.
our site is http://www.neteagles.cn
my qq num is 19661891.

问题8: 在neteagle4.txt文件的第二行插入两行文本。

[root@centos7 ~/test]# sed '2i I teacher linux.\nYou are my student.' neteagle4.txt 
I am neteagle teacher!
I teacher linux.
You are my student.
I like badminton ball,billiard ball and chinese chess!
I teacher linux.
our site is http://www.neteagles.cn
my qq num is 19661891.

11.7.3 sed配合正则表达式的企业案例

前文讲解正则表达式大多使用的是grep和egrep命令,本节就来讲解下sed配合正则表达式的用法,让读者掌握正则表达式的综合运用技巧。

问题1: 取出Linux中执行ifconfig eth0后对应的IP地址(只能输出IP地址)。

这道题的解答方法非常多,本节主要讲解sed的组合方法,更多方法参加下文。

方法1:利用正则加sed替换功能获取IP。

步骤1: 示例网卡eth0信息如下,目标是只输出10.0.0.201这个IP地址。

[root@centos7 ~/test]# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.201  netmask 255.255.255.0  broadcast 10.0.0.255
        inet6 fe80::4bab:71b:82eb:80dc  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:0e:fd:51  txqueuelen 1000  (Ethernet)
        RX packets 8497  bytes 779235 (760.9 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3864  bytes 485185 (473.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

步骤2: 首先找到第2行,然后去掉目标10.0.0.201这个IP地址之前的内容,可使用sed的替换功能来实现。

正则表达式的匹配技巧:处理所需要的目标(获取的IP)前东德字符串一般以“..”开头(^.*)来匹配,最后将目标IP前的结尾写上实际的字符串(这里的字符串必须要唯一,否则就会贪婪匹配到最后一个相同的字符串),匹配的正则和实际要处理的第二行其匹配关系如图11-3所示。

t11-3.png

图11-3 正则表达式匹配目标前面的内容图示

[root@centos7 ~/test]# ifconfig eth0 |sed -n '2s#^.*inet##gp'   #-n默认不输出,2s表示处理第2行,“#^.*inet#”表示匹配IP地址前的所有内容(这里的空格暂且忽略),“##gp”表示全局替换为空内容,并打印输出替换后的内容。
 10.0.0.201  netmask 255.255.255.0  broadcast 10.0.0.255    #IP地址前的所有字符都已经替换完毕。

步骤3: 在步骤2的基础上,再去掉目录10.0.0.201这个IP后的所有内容,这里也是使用sed的替换功能来实现。

正则表达式匹配技巧:匹配目标后的字符串一般紧接目标(获取的IP)之后,匹配的正则表达式开头影协上实际的字符串(这里的字符串必须要唯一,否则就会贪婪匹配到最后一个相同的字符串),再结尾则是以“...”结尾(.*$)来匹配。匹配的正则表达式与实际要处理的第二行的匹配关系如图11-4所示。

t11-4.png

图11-4 正则匹配目标后面的内容图示

[root@centos7 ~/test]# ifconfig eth0 |sed -n '2s#^.*inet##gp' |sed  -n 's#netm.*$##gp'
#注意最后一个管道后面的才是这次匹配的命令,-n默认不输出,“#netm.*$#”匹配了IP后面的所有字符(紧接着IP后的两个空格先暂且忽略),“##”表示替换为空,然后输出剩下的内容(就只有IP地址了)。
 10.0.0.201     #此时的IP地址,实际上结尾和开头都是有空格的,也可以匹配这些空格并删掉。
[root@centos7 ~/test]# ifconfig eth0 |sed -n '2s#^.*inet ##gp' |sed  -n 's#  netm.*$##gp'   #inet后面匹配一个空格,netm前面匹配两个空格。
10.0.0.201  #结果就只有IP,没有空格了。

提示: 这里有个疑问,那就是为什么要用“.*”,而不用其他的更细致匹配的正则表达式呢?工作中的操作都是极简主义,只要能解决问题使用的越简单越好。

方法2: -e多项编辑可以减少管道的使用。

[root@centos7 ~/test]# ifconfig eth0 |sed -ne '2s#^.*inet ##g' -ne 's#  netm.*$##gp'
10.0.0.201

方法3: 利用正则的"()小括号和“\n”后向引用功能加上替换来实现。

语法如下:

sed -rn 's#(neteagle)(youngboly)(littleboy)#\1\2\3#gp' neteagle.txt

其中,“/1”表示第一个括号的内容,即“\1”表示第一个括号内的内容,即neteagle,“\2”表示第二个括号内的内容,即youngboy,“\3”表示第三个括号内的内容,即littleboy,以此类推,就是利用这个命令全匹配解决问题,“-r”表示支持扩展的正则表达式。匹配关系如图11-5所示。

b11-5.png

图11-5 正则表达式匹配要获取的目标IP图示

需要特别注意的是,将要获取的目标IP(这里是IP地址),用小括号里的内容进行匹配,这样输出时就可以用"\1"输出了,具体命令如下:

[root@centos7 ~/test]# ifconfig eth0 |sed -nr '2s#^.*inet (.*)  netm.*$#\1#gp'  #-r支持小括号功能。
#"\1"用于获取小括号的内容输出,为什么(.*)就匹配到了IP呢?这是因为它前面明确匹配到字符串了,后面的开头也给出了固定匹配的字符串,因此中间的“.*”就只能是匹配IP了,也就是说根据目标前后匹配的结果就能知道中间的内容了。
10.0.0.201

提示: 这种获取IP地址的方式实际上还是很复杂的,实际工作中还有很多更好的方法,这里仅仅是带大家练习一下正则表达式的使用。

下面留给读者一道类似的题,供读者练习:请取出stat /etc/hosts文件中第四行中的文件权限对应的644三个数字。

方法1:

[root@centos7 ~/test]# stat /etc/hosts
  File: ‘/etc/hosts’
  Size: 158         Blocks: 8          IO Block: 4096   regular file
Device: 803h/2051d  Inode: 16808060    Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:net_conf_t:s0
Access: 2020-10-13 20:03:33.709384367 +0800
Modify: 2013-06-07 22:31:32.000000000 +0800
Change: 2020-10-01 16:03:34.926040702 +0800
 Birth: -
[root@centos7 ~/test]# stat /etc/hosts | sed -n '4p'
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
[root@centos7 ~/test]# stat /etc/hosts | sed -n '4s#^Access: (0##gp' 
644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
[root@centos7 ~/test]# stat /etc/hosts | sed -ne '4s#^Access: (0##g' -ne '4s#/-.*$##gp'
644

方法2:

[root@centos7 ~/test]# stat /etc/hosts | sed -nr '4s#^Access: (0(.*)/-.*$#\1#gp'
644     #注意"(0"这里的括号要转义"(0"。

11.8 awk命令

awk是Linux运维工作中最重要最强大的工具之一,如果读者掌握了awk的运用方法,那么必然能够在运维工作中得心应手。

11.8.1 awk命令语法及参数

【命令星级】 ★★★★★

【功能说明】

awk不仅仅是Linux系统中的一个命令,而且其还是一种编程语言,可以用来处理数据和生成报告(excel)。处理的数据可以是一个或多个文件,它是Linux系统最强大的文本处理工具,没有之一。awk的常用功能具体见表11-7。本书将主要讲解awk命令行的文本处理常见的功能和技巧运用。

表11-7 awk命令常用功能

b11-7.png

【语法格式】

awk [option] 'pattern{action}' file ...
awk [参数] '条件{动作}' 文件 ...

语法格式的说明如图11-6所示。

t11-6.png

图11-6 awk语法格式

说明: 这里的模式就相当于是条件。在awk命令及后面的选项和文件里。

【选项说明】

表11-8 awk命令的参数选项及说明

b11-8.png

【常用功能】

表11-9针对awk命令的常见变量进行了说明

表11-9 awk命令的常见变量及其属性

b11-9.png

11.8.2 常用实践技巧案例实战

首先准备好测试文件,取passwd文件的前5行,示例代码如下:

[root@centos7 ~]# sed -n '1,5p' /etc/passwd >test.txt
[root@centos7 ~]# cat -n  test.txt
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

问题1: 取test.txt文件的第2行道第3行的内容。

解答: 这道题是sed功能的特长(见前文),不过awk也具有取行的功能,考察的是NR变量和“&&”(并且)逻辑符及“==”(相等)号的用法。

方法1:

[root@centos7 ~]# awk 'NR>1&&NR<4' test.txt     #NR表示行号,&&表示并且。
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

提示: 如果只取一行,就使用awk 'NR==2' test.txt这样的写法。

方法2:

[root@centos7 ~]# awk 'NR==2,NR==3' test.txt    #NR表示行号,用逗号表示从第2行到第3行。
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

问题2: 过滤含有root字符串的行。

解答: 这道题考察的是awk的过滤功能,这个功能与sed的过滤功能类似,需要将要过滤的内容用两个斜线包含起来。

[root@centos7 ~]# awk '/root/' test.txt     #类似于sed的过滤功能,但结尾不需要p符号了。
root:x:0:0:root:/root:/bin/bash

问题3: 删除含有root字符串的行。

解答: 这道题考察的是awk的删除功能,删除功能不是awk的特长,但可以通过正则表达式组合匹配达到间接解答这道题的目的。

步骤1: 过滤出非r字符的行。

实际处理问题时,特别是那种带取反功能才能出结果的,逻辑上很绕,不好理解,因此,最好是分步骤先正向处理,然后再加取反符就简单了。

[root@centos7 ~]# awk '/[^r]/' test.txt     #过滤非r字符的行,因为所有行都含有非r字符,因此全部打印了。
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

步骤2: 过滤出以非r字符开头的行,也就是结果要求的。

[root@centos7 ~]# awk '/^[^r]/' test.txt    #匹配以非r字母开头的行,因为第一行是以root开头,所以没匹配。
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

提示: 当然,要想实现文件删除功能,还需要配合使用重定向符号,这道题还是使用sed来实现更好,这里提出该题意在帮助读者拓展一些解题思路。

问题4: 取文件的第一列、第三列和最后一列的内容,病打印行号。

解答: 这道题考察的是awk的取列功能,这是awk的本行,即考察"&n"即NF、NR的用法。

[root@centos7 ~]# awk -F ":" '{print NR,$1,$3,$NF}' test.txt    #注意语法格式。
#-F ":"表示以冒号为分隔符,print是打印,$1是取分隔符后的第一列,NF是取最后一列,NR表示行号。
1 root 0 /bin/bash
2 bin 1 /sbin/nologin
3 daemon 2 /sbin/nologin
4 adm 3 /sbin/nologin
5 lp 4 /sbin/nologin

问题5: 取出Linux中执行ifconfig eth0后对应的IP地址(只能输出IP地址)。

这道题的解法非常多,前文讲解了sed方法,本节将主要讲解awk的组合方法,更多方法,读者可以参考老男孩教育的其他图书或文章。

CentOS 7取IP所在的行:
        inet 10.0.0.201  netmask 255.255.255.0  broadcast 10.0.0.255
CentOS 6取IP所在的行:
          inet addr:10.0.0.202  Bcast:10.0.0.255  Mask:255.255.255.0

CentOS 7下ifconfig输出的IP与CentOS 6下有一些区别,因此使用命令获取IP的思路就会有所不同了,下面分别在CentOS 7和CentOS 6下联系,未标记的一般来说是都适合。

方法1: 利用多管道获取IP(CentOS 7)。

步骤1:

[root@centos7 ~]# ifconfig eth0 |awk 'NR==2{print $2}'  #NR==2表示输出第二行,默认以空格作为分隔符,$2表示取第二列CentOS 7下取IP变得很简单了
10.0.0.201

方法2: 利用设置多分隔符的功能来获取IP(该技巧方法适合于CentOS 6下获取IP)。

[root@centos6 ~]# ifconfig eth0 |awk 'NR==2'
          inet addr:10.0.0.202  Bcast:10.0.0.255  Mask:255.255.255.
[root@centos6 ~]# ifconfig eth0 |awk -F "[: ]+" 'NR==2{print $4}'   #设定多分隔符的方法。
#-F "[: ]+"指定冒号或者空格作为分隔符,“+”号就是匹配冒号或者空格1次或多次,即多个分隔符靠到一起算一个分隔符。
10.0.0.202

设置多分隔符的功能来获取IP的基本原理如图11-7所示,冒号或者空格都是分隔符,多个连续的分隔符算一个。

t11-7.png

图11-7 awk获取IP的正则表达式匹配方法

方法3:利用设置不同的多分隔符的功能来获取IP(该技巧适合于在CentOS 6下获取IP)。

[root@centos6 ~]# ifconfig eth0 |awk 'NR==2'
          inet addr:10.0.0.202  Bcast:10.0.0.255  Mask:255.255.255.
[root@centos6 ~]# ifconfig eth0 |awk -F "(addr:)|(Bcast:)" 'NR==2{print $2}'    #竖线符号的意思表示匹配或者左边或者右边,小括号是分组,其将作为一个整体,即以“addr:”或“Bcast:”做分隔符。
10.0.0.202  

说明:

-F "(addr:)|(Bcast:)" 还是比较容易理解的,我们的目标是取得IP,本例是10.0.0.202,IP的左边是“addr:”,右边是“Bcast:”,即左边将“addr:”做为分隔符,右边将“Bcast:”做为分隔符,剩下中间的IP就是我们想要获取的内容,具体见表11-10的解析。

但是还需要一个条件,因为IP地址在第二行,所以要使用NR==2来表示。

表11-10 IP地址所在的行分隔图解

b11-10.png

最后的命令形式就是awk -F "(addr:)|(Bcast:)" 'NR==2{print $2}'了,注意“-F”的指定多分隔符的写法。

问题6: 过滤文件中第一列的内容所匹配root的字符串,将符合条件的行的最后一列输出。

解答: 这道题在过滤日志信息的时候经常使用,示例代码如下。

[root@centos7 ~]# awk -F ":" '$1-/root/ {print $NF}' test.txt   #$1-/root/表示第一列内容匹配root条件,$NF表示最后一列
/bin/bash

问题7: 过滤下列test1.txt文件中第三列内容分数大于70,并且小于95的人名和性别。

[root@centos7 ~]# cat test1.txt 
张三 男 80
李四 女 70
王五 男 90
赵六 女 100

解答: 这是一个根据特定条件取列的问题,答案如下:

[root@centos7 ~]# awk '$3>70&&$3<95{print $1,$2}' test1.txt 
张三 男
王五 男

11.9 本章重点

1)必须数量掌握Linux基础正则表达式字符,扩展正则表达式、预定义中括号表达式和元字符了解即可。

2)重点掌握Linux三剑客命令grep、 sed、awk,以及熟练解答相关的常见练习题。