Linux Shell核心编程指南
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.7 数据过滤与正则表达式

工作中经常需要使用脚本对数据进行过滤和筛选工作,Linux系统提供了一个非常方便的grep命令,可以实现这样的功能。

描述:grep命令可以查找关键词并打印匹配的行。

用法:grep [选项]匹配模式[文件]。

常用选项:-i忽略字母大小写。

-v取反匹配。

-w匹配单词。

-q静默匹配,不将结果显示在屏幕上。

[root@centos7~]#grep th test.txt             #在test.txt文件中过滤包含th关键词的行
[root@centos7~]#grep-i the test.txt          #过滤包含the关键词的行(不区分字母大小写)
[root@centos7~]#grep-w num test.txt          #仅过滤num关键词(不会过滤number关键词)
[root@centos7~]#grep-v the test.txt          #过滤不包含the关键词的行
[root@centos7~]#grep-q root/etc/passwd      #不在屏幕上显示过滤的结果

在实际工作中,公司需要对外招聘人才,但大千世界人才众多,并不一定每个人都适合该岗位,这时我们可以用很多方法找到公司需要的人。常用的方法有两种:一,通过朋友介绍直接精准地定位人才;二,写招聘简章(对需要的人才进行描述:学历、经验、技能、语言等),写完后,通过招聘会、网络招聘等方式广纳人才,通常描述写得越细,越能快速精准地定位所需人才。

而正则表达式就是一种计算机描述语言,通过正则表达式可以直接告诉计算机所需要的是字母A并精确匹配定位,也可以告诉计算机需要的是26个字母中的任意一个字母进行匹配,等等。现在很多程序、文本编辑工具、编程语言都支持正则表达式,比如使用grep过滤时就可以使用正则匹配的方式查找数据。但任何语言都需要遵循一定的语法规则,正则表达式也不例外。正则表达式的发展经历了基本正则表达式与扩展正则表达式两个阶段,扩展正则表达式是在基本正则表达式的基础上添加了一些更加丰富的匹配规则而成的。在Linux世界中有句古老的说法“Everything is a file(一切皆文件)”,而且很多配置文件是纯文本文件,工作中,我们时常需要对大量的服务器进行配置的修改,如果以手动方式在海量数据中进行查找匹配并最终完成修改,则其效率极低。此时,使用正则表达式是非常明智的选择。接下来,我们分别了解每种表达式的具体规则。注意,正则表达式中有些匹配字符与Shell中的通配符符号一样,但含义却不同。

1)基本正则表达式(Basic Regular Expression)

表1-6列出了基本正则表达式及其含义。

表1-6 基本正则表达式及其含义

注意

由于模板文件的内容在每个系统中略有差异,因此以下案例的输出结果可能有所不同。

下面看几个使用基本正则表达式的案例。

[root@centos7~]# cp  /etc/passwd  /tmp/                #复制素材模板文件
[root@centos7~]# grep "root" /tmp/passwd
#查找包含root的行(双引号内不是要匹配的内容,以下案例相同)
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@centos7~]# grep  ":..0:"  /tmp/passwd
#查找:与“0:”之间包含任意两个字符的字符串,并显示该行
root:x:0:0:root:/root:/bin/bash
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
games:x:12:100:games:/usr/games:/sbin/nologin
avahi-autoipd:x:170:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/
nologin
[root@centos7~]# grep  "00*"  /tmp/passwd
#查找包含至少一个0的行(第一个0必须出现,第二个0可以出现0次或多次)
root:x:0:0:root:/root:/bin/bash                    #该行有两处匹配
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin      #匹配0出现2次
gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
avahi-autoipd:x:170:170:Avahi IPv4LL
Stack:/var/lib/avahi-autoipd:/sbin/nologin
avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
[root@centos7~]# grep  "o[os]t"  /tmp/passwd
#查找包含oot或ost的行
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
[root@centos7~]# grep  "[0-9]"  /tmp/passwd
#查找包含0~9数字的行(输出内容较多,这里为部分输出)
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
sync:x:5:0:sync:/sbin:/bin/sync
[root@centos7~]# grep  "[f-q]"  /tmp/passwd
#查找包含f~q字母的行(f到q之间的任意字母都可以,输出内容较多,这里为部分输出)
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
[root@centos7~]# grep  "^root"  /tmp/passwd
#查找以root开头的行
root:x:0:0:root:/root:/bin/bash
[root@centos7~]# grep  "bash$"  /tmp/passwd
#查找以bash结尾的行
root:x:0:0:root:/root:/bin/bash
[root@centos7~]# grep  "sbin/[^n] "  /tmp/passwd
#查找sbin/后面不跟n的行
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
[root@centos7~]# grep  "0\{1,2\}"  /tmp/passwd
#查找数字0出现最少1次、最多2次的行
root:x:0:0:root:/root:/bin/bash
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
avahi-autoipd:x:170:170:Avahi IPv4LL
Stack:/var/lib/avahi-autoipd:/sbin/nologin
avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
[root@centos7~]# grep  "\(root\).*\1"  /tmp/passwd
#查找两个root之间可以是任意字符的行。注意,这里使用\(root\)将root保留,后面的\1再
次调用root,类似于前面复制root,后面粘贴root
root:x:0:0:root:/root:/bin/bash
[root@centos7~]# grep  "^$"  /tmp/passwd
#过滤文件的空白行
[root@centos6 test]# grep -v  "^$"  /tmp/passwd
#过滤文件的非空白行

2)扩展正则表达式(Extended Regular Expression)

表1-7列出了扩展正则表达式及其含义。

表1-7 扩展正则表达式及其含义

再看几个使用扩展正则表达式的案例,由于输出信息与基本正则表达式类似,这里仅写出命令而不再打印输出信息。另外grep命令默认不支持扩展正则表达式,需要使用grep -E或者使用egrep命令进行扩展正则表达式的过滤。

[root@centos7~]# egrep  "0{1,2}"  /tmp/passwd
#查找数字0出现最少1次最多2次的行
[root@centos7~]# egrep  "0+"  /tmp/passwd
#查找包含至少一个0的行
[root@centos7~]# egrep  " (root|admin) "  /tmp/passwd
#查找包含root或者admin的行

3)POSIX规范的正则表达式

由于基本正则表达式会有语系的问题,所以这里需要了解POSIX规范的正则表达式规则。例如,在基本正则表达式中可以使用a~z来匹配所有字母,但如果需要匹配的对象是中文字符怎么办呢?或是像“ن”这样的阿拉伯语字符怎么办?所以使用a~z匹配仅针对英语语系中的所有字母,POSIX其实是由一系列规范组成的,这里仅介绍POSIX正则表达式规范。POSIX正则表达式规范帮助我们解决语系问题,另外POSIX规范的正则表达式也比较接近于自然语言。表1-8列出了POSIX规范字符集。

表1-8 POSIX规范字符集

Linux允许通过方括号使用POSIX标准规范,如[[:alnu:]]将匹配任意单个字母数字字符,下面通过几个简单的例子来说明用法。由于过滤输出的内容较多,以下仅为部分输出。

[root@centos7~]# grep "[[:digit:]]"  /tmp/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@centos7~]# grep "[[:alpha:]]"  /tmp/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@centos7~]# grep "[[:punct:]]"  /tmp/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@centos7~]# grep [[:space:]] /tmp/passwd
ftp:x:14:50:FTP_User:/var/ftp:/sbin/nologin
vcsa:x:69:69:virtual_console_memory_owner:/dev:/sbin/nologin

4)GNU规范

Linux中的GNU软件一般支持转义元字符,这些转义元字符有:\b(边界字符,匹配单词的开始或结尾), \B(与\b为反义词,\Bthe\B不会匹配单词the,仅会匹配the在中间的单词,如atheist), \w(等同于[_[:alnum:]]), \W(等同于[^_[:alnum:]])。另外有部分软件支持使用\d表示任意数字,\D表示任意非数字。\s表示任意空白字符(空格、制表符等), \S表示任意非空白字符。

接下来看几个简单的例子。

[root@centos7~]# grep "i\b"  /tmp/passwd          #匹配i结尾的单词
avahi-autoipd:x:170:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/nologin
avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
[root@centos6 ~]# grep "\W"  /tmp/passwd
#匹配所有非字母、数字及下画线组合的内容
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@centos7~]# grep "\w"  /tmp/passwd
#匹配所有字母、数字及下画线组合的内容(内容太多,这里不再显示输出内容)
[root@centos7~]# /usr/bin/grep -P --color "\d" /etc/passwd
#默认grep仅支持基本正则表达式,使用-P让grep支持perl兼容的正则表达式(下面的结果仅
为部分输出内容)
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@centos7~]# /usr/bin/grep -P --color "\D" /etc/passwd
(省略输出内容)

1.8 各式各样的算术运算

Shell支持多种算术运算,可以使用$((表达式))、$[表达式]、let表达式进行整数的算术运算,注意这些命令无法执行小数运算;使用bc命令可以进行小数运算。表1-9列出了常用运算符号。

表1-9 常用运算符号

下面根据以上这些运算符号,结合具体命令,通过$(())和$[]的方式演示实际运算的效果,这两种计算方式都支持对变量进行计算。

[root@centos7~]# echo $((2+4))
6
[root@centos7~]# echo $((2-4))
-2
[root@centos7~]# echo $((2*4))
8
[root@centos7~]# echo $((2**4))           #2的4次幂
16
[root@centos7~]# echo $((10%3))             #10除以3后返回余数
1
[root@centos7~]# x=2                            #定义变量并赋值
[root@centos7~]# echo $((x+=2))             #x=x+2(x=2+2)
4
[root@centos7~]# echo $((x*=3))            #x=x×3(x=4×3)
12
[root@centos7~]# echo $((x%=2))             #x=x%2(x=8%2)
0
[root@centos7~]# x=2 ; y=3                      #定义2个变量并赋值
[root@centos7~]# echo $((x*y))
6
[root@centos7~]# echo $x $y                     #x, y自身的值不变
2 3
[root@centos7~]# echo $((3>2&&5>3))         #仅当&&两边的表达式都为真时,返回1
1
[root@centos7~]# echo $((3>2&&5>9))         #当&&两边的表达式任意为假时,返回0
0
[root@centos7~]# echo $((3>8&&5>9))
0
[root@centos7~]# echo $((1>2||5>8))         #仅当||两边的表达式都为假时,返回0
0
[root@centos7~]# echo $((3>2||5>9))    #当||两边的表达式任意一个为真时,返回1
1
[root@centos7~]# echo $((1>2||5>2))
1
[root@centos7~]# echo $[2+8]
10
[root@centos7~]# echo $[2**8]
256
[root@centos7~]# x=3 ; y=5
[root@centos7~]# echo $[x+y]
8
[root@centos7~]# echo $[x*y]
15
[root@centos7~]# echo $[1+2*3]          #先计算乘除法,再计算加减法
7
[root@centos7~]# echo $[(1+2)*3]      #使用()让计算机先计算加减法,再计算乘除法
9
[root@centos7~]# echo $[x>y?2:3]         #如果x大于y,返回2,否则返回3
3
[root@centos7~]# echo $[y>x?2:3]         #如果y大于x,返回2,否则返回3
2
[root@centos7~]# echo $[y>x?2+2:3*5]
4
[root@centos7~]# echo $[x>y?2+2:3*5]
15

接下来,学习使用内置命令let进行算术运算的案例。注意,使用let命令计算时,默认不会输出运算的结果,一般需要将运算的结果赋值给变量,通过变量查看运算结果。另外,使用let命令对变量进行计算时,不需要在变量名前添加$符号。

[root@centos7~]# let 1+2                       #无任何输出结果
[root@centos7~]# x=5                           #变量赋初始值
[root@centos7~]# let x++                       #x=x+1(x=5+1)
[root@centos7~]# echo $x
6
[root@centos7~]# let x*=2 ; echo $x           #x=x×2(x=6×2)
12
[root@centos7~]# let i=1+2*3; echo $i
7
[root@centos7~]# let i=(1+2)*3; echo $i
9
[root@centos7 ~]# let 2.2+5.5                   #注意,let无法进行小数运算
invalid arithmetic operator

最后,注意在使用++或--运算符号时,x++和++x的结果是不同的,x--和--x的结果也不同。x++是先调用x再对x自加1, ++x是先对x自加1再调用x; x--是先调用x再对x自减1, --x是先对x自减1再调用x。

[root@centos7~]# x=1                   #变量赋初始值
[root@centos7~]# echo $[x++]           #先调用x,屏幕显示1,再对x自加1
1
[root@centos7~]# echo $x               #此时x的值已经为2
2
[root@centos7~]# x=6                   #变量赋初始值
[root@centos7~]# echo $[x--]           #先调用x,屏幕显示6,再对x自减1
6
[root@centos7~]# echo $x               #此时x的值已经为5
5
[root@centos7~]# x=1
[root@centos7~]# echo $[++x]           #先对x自加1,再显示,结果为2
2
[root@centos7~]# x=6
[root@centos7~]# echo $[--x]           #先对x自减1,再显示,结果为5
5

Bash仅支持对整数的四则运算,不支持对小数的运算。如果我们需要在脚本中对任意精度的小数进行运算甚至编写计算函数,则可以使用bc计算器实现。bc计算器支持交互和非交互两种执行方式。

先看看在交互模式下的计算方式,一行代码为一条命令,可以进行多次计算。下面加粗的部分为手动输入的内容,斜体输出的内容是计算结果。

[root@centos7~]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation,
Inc.
1.5+3.2
4.7
# 2除以10的结果,默认仅显示整数部分的值
2/10
0
#通过bc计算器中的内置变量scale,可以指定需要保留的小数点位数
scale=2
2/10
.20
#在bc计算器中使用^计算幂运算
2^3
8
#使用quit命令退出bc计算器
quit

除了在交互模式下使用bc计算器,还可以通过非交互的方式进行计算。而且通过bc计算器的另外两个内置变量ibase(in)和obase(out)可以进行进制转换,ibase用来指定输入数字的进制,obase用来设置输出数字的进制,默认输入和输出的数字都是十进制的。

[root@centos7~]# x=$(echo "(1+2)*3" | bc)
[root@centos7~]# echo $x
9
[root@centos7~]# echo "2+3; scale=2;8/19" | bc
5
.42
[root@centos7~]# echo "obase=2;10" | bc    #输入十进制的10,输出对应的二进制
1010
[root@centos7~]# echo "obase=8;10" | bc    #十进制转八进制
12
[root@centos7~]# echo "obase=16;10" | bc   #十进制转十六进制
A
[root@centos7~]# echo "ibase=2;11" | bc    #输入二进制,输出对应的十进制
3
[root@centos7~]# echo "ibase=16; FF" | bc  #十六进制转十进制
255
[root@centos7~]# echo "obase=2;2+8" | bc   #输入十进制,输出二进制结果
1010
[root@centos7~]# echo "length(22833)" | bc    #统计数字的长度
5

通过计算我们可以解决现实中的很多问题,下面这个需要计算结果的脚本案例中的每个部分都可以独立出来单独运行,也可以合并在一个文件中统一执行。