第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】
一、什么是正则表达式
处理字符串的方法,以行为单位处理字符串,通过一些特殊符号的辅助,帮助使用者实现“寻找/替代/删除”的功能
二、基础正则表达式(一)语系对正则表达式的影响
(二)grep的一些高级参数
(三)基础正则表达式练习
例题一:查找特定字符串
例题二:利用中括号 [ ]来查找集合字符
这部分需要反复的练习,加深印象,无特别需要注意的地方,请看鸟哥链接
http://linux.vbird.org/linux_basic/0330regularex.php#basicre
例题三:行首与行尾 ^ $
/etc/rsyslog.conf里面有有很多空白行和以“#”开头的,我只想查看其正文部分,操作如下:
有一个需要注意的问题:管道前段,添加需要要慎重。
管道前段命令中加了-n,这时候后半段grep命令收到的数据是以序号开头的,所以当再次尽心字符串筛选,想去除“#”开头的数据时,不存在这样的数据啦,所有数据都是以数字开头的。
如图所示,第一列序号是连续的,为第二个grep命令中添加,第二列序号是间断的,是第一个grep筛选后的结果。
例题四:任意一个字符.与重复字符*
小数点".":代表存在任意字符
重复字符“*”:重复前一个字符,0或者无限多次。
例题五:限定连续RE字符范围{ }
(四)基础正则表达式字符
汇总:
RE 字符 | 意义与范例 |
^word |
意义:待搜寻的字串(word)在行首! 范例:搜寻行首为#开始的那一行,并列出行号 grep -n '^#' regular_express.txt |
word$ |
意义:待搜寻的字串(word)在行尾! 范例:将行尾为!的那一行列印出来,并列出行号 grep -n '!$' regular_express.txt |
. |
意义:代表『一定有一个任意字元』的字符! 范例:搜寻的字串可以是(eve) (eae) (eee) (ee),但不能仅有(ee) !亦即e与e中间『一定』仅有一个字元,而空白字元也是字元! grep -n 'ee' regular_express.txt |
\ |
意义:跳脱字符,将特殊符号的特殊意义去除! 范例:搜寻含有单引号'的那一行! grep -n \' regular_express.txt |
* |
意义:重复零个到无穷多个的前一个RE字符 范例:找出含有(es) (ess) (esss)等等的字串,注意,因为*可以是0个,所以es也是符合带搜寻字串。另外,因为*为重复『前一个RE字符』的符号,因此,在*之前必须要紧接着一个RE字符喔!例如任意字元则为『.*』 ! grep -n 'ess*' regular_express.txt |
[list] |
意义:字元集合的RE字符,里面列出想要撷取的字元! 范例:搜寻含有(gl)或(gd)的那一行,需要特别留意的是,在[]当中『谨代表一个待搜寻的字元』,例如『 a[afl]y 』代表搜寻的字串可以是aay, afy, aly即[afl]代表a或f或l的意思! grep -n 'g[ld]' regular_express.txt |
[n1-n2] |
意义:字元集合的RE字符,里面列出想要撷取的字元范围! 范例:搜寻含有任意数字的那一行!需特别留意,在字元集合[]中的减号-是有特殊意义的,他代表两个字元之间的所有连续字元!但这个连续与否与ASCII编码有关,因此,你的编码需要设定正确(在bash当中,需要确定LANG与LANGUAGE的变数是否正确!)例如所有大写字元则为[AZ] grep -n '[AZ]' regular_express.txt |
[^list] |
意义:字元集合的RE字符,里面列出不要的字串或范围! 范例:搜寻的字串可以是(oog) (ood)但不能是(oot) ,那个^在[]内时,代表的意义是『反向选择』的意思。例如,我不要大写字元,则为[^AZ]。但是,需要特别注意的是,如果以grep -n [^AZ] regular_express.txt来搜寻,却发现该档案内的所有行都被列出,为什么?因为这个[^AZ]是『非大写字元』的意思,因为每一行均有非大写字元,例如第一行的"Open Source"就有p,e,n,o....等等的小写字 grep -n 'oo[^t]' regular_express.txt |
\{n,m\} |
意义:连续n到m个的『前一个RE字符』 意义:若为\{n\}则是连续n个的前一个RE字符, 意义:若是\{n,\}则是连续n个以上的前一个RE字符! 范例:在g与g之间有2个到3个的o存在的字串,亦即(goog)(gooog) grep -n 'go\{2,3\}g' regular_express.txt |
(五)sed工具
1、以行为单位进行插入和删除
插入:
nl /etc/passwd | sed '2a drink tea'
(在第2行后,插入“drink tea”,新加入的这一行是没有序号的)
范例二:承上题,在第二行后(亦即是加在第三行)加上『drink tea?』字样! [[email protected] ~]$ nl /etc/passwd | sed '2a drink tea' 1 root : x : 0:0 : root:/root :/bin/bash 2 bin:x:1:1:bin:/bin:/sbin/nologin drink tea 3 daemon:x:2:2:daemon:/sbin:/sbin/nologin .....(后面省略).....
删除:
nl /etc/passwd | sed '2,5d'
(删除第2到5行)
加入两行
范例三:在第二行后面加入两行字,例如『Drink tea or .....』与『drink beer?』 [[email protected] ~]$ nl /etc/passwd | sed '2a Drink tea or . .....\ > drink beer ?' ----------------------------------------------------------注意:这里最后打后单引号 1 root : x : 0:0 : root:/root :/bin/bash 2 bin:x:1:1:bin:/bin:/sbin/nologin Drink tea or ...... drink beer ? 3 daemon:x:2:2:daemon:/sbin:/sbin/nologin .....(后面省略).....
2、以行为单位的取代与显示功能
取代:
nl /etc/passwd | sed '2,5c NO 2-5 change'
显示(仅显示某几行):
nl /etc/passwd | sed -n '2,5p'
-n是静音模式的意思,必须要有,不然全篇显示,原因不详。
3、部分资料的搜寻与取代功能
sed 's/要被取代的内容/新内容/g' |
想要最终获得三个IP地址,操作如下
/sbin/ifconfig | grep 'inet '| sed 's/inet //g'|sed 's/netmask.*$//g'
想要去掉#和空行,只显示有用的内容
第一步:去掉注释行(#)
cat /etc/man_db.conf | grep 'MAN' | sed 's/^#.*$//g'
这一步会把之前的注释行替换成空行
接下来要把空行去掉
cat /etc/man_db.conf | grep 'MAN' | sed 's/^#.*$//g' | sed '/^$/d'
4、直接修改文件内容(危险动作)
利用sed修改regular_express.txt中文件末尾的句号,改为感叹号
sed -i 's/\.$/\!/g' regular_express.txt
利用sed,在regular_express.txt最末处,添加一行#This is a test!
sed -i '$a # This is a test' regular_express.txt
三、延伸正则表达式
RE 字符 | 意义与范例 |
+ |
意义:重复『一个或一个以上』的前一个RE字符 范例:搜寻(god) (good) (goood)...等等的字串。那个o+代表『一个以上的o 』所以,底下的执行成果会将第1, 9, 13行列出来。 egrep -n 'go+d' regular_express.txt |
? |
意义:『零个或一个』的前一个RE字符 范例:搜寻(gd) (god)这两个字串。那个o?代表『空的或1个o 』所以,上面的执行成果会将第13, 14行列出来。有没有发现到,这两个案例( 'go+d'与'go?d' )的结果集合与'go*d'相同?想想看,这是为什么喔!^_^ egrep -n 'go?d' regular_express.txt |
| |
意义:用或( or )的方式找出数个字串 范例:搜寻gd或good这两个字串,注意,是『或』!所以,第1,9,14这三行都可以被列印出来喔!那如果还想要找出dog呢? egrep -n 'gd|good' regular_express.txt |
() |
意义:找出『群组』字串 范例:搜寻(glad)或(good)这两个字串,因为g与d是重复的,所以,我就可以将la与oo列于( )当中,并以|来分隔开来,就可以啦! egrep -n 'g(la|oo)d' regular_express.txt |
()+ |
意义:多个重复群组的判别 范例:将『AxyzxyzxyzxyzC』用echo叫出,然后再使用如下的方法搜寻一下! echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'上面的例子意思是说,我要找开头是A 结尾是C ,中间有一个以上的"xyz" 字串的意思~ |
总结: 小数点".":代表存在任意字符 重复字符“*”:重复前一个字符,0或者无限多次。 问号“?”:0个或1个
加号“+”:1个或一个以上 |
四、文件的格式化与相关处理
(一)格式化列印:printf
[[email protected] ~]$ printf '列印格式'实际内容 选项与参数: 关于格式方面的几个特殊样式: \a 警告声音输出 \b 倒退键(backspace) \f 清除萤幕(form feed) \n 输出新的一行 \r 亦即Enter 按键 \t 水平的[tab] 按键 \v 垂直的[tab] 按键 \xNN NN 为两位数的数字,可以转换数字成为字元。 关于C 程式语言内,常见的变数格式 %ns 那个n 是数字, s 代表string ,亦即多少个字元; %ni 那个n 是数字, i 代表integer ,亦即多少整数位数; %N.nf 那个n 与N 都是数字, f 代表floating (浮点),如果有小数位数, 假设我共要十个位数,但小数点有两位,即为%10.2f 啰!
两条命令的区别之处在于红色方框的内容,从显示中可以看出“%ns”和“\t”在格式上的作用
%ns代表的是,留白的全部宽度是6个字符,右对齐,前面用空白填齐。\t代表的是两个字符串之间的空格,比如第一条命令,name中的e和Chinese的字母e之间,差了2个字符(tab)+32个字符。
注:redhat 7在这个细节处理上和鸟哥私房菜(Redhat6)介绍的操作有所不同,7更友好,即上图演示例。
(二)awk:好用的资料处理工具
[[email protected] ~]$ awk '条件类型1{动作1}条件类型2{动作2} ...' filename
每一行的每个栏位都是有变数名称的,$1,$2就是代表的是第一列,第二列的意思;另外,$0代表的是一整行资料。注意看awk的正规表达,动作是在大括号{}里面的,因为每一行都要处理,所以没有条件类型的限制。
注意单引号和双引号的使用,单引号是awk的语法要求,
刚刚上面五行当中,整个awk的处理流程是:
- 读入第一行,并将第一行的资料填入$0, $1, $2.... 等变数当中;
- 依据"条件类型" 的限制,判断是否需要进行后面的"动作";
- 做完所有的动作与条件类型;
- 若还有后续的『行』的资料,则重复上面1~3 的步骤,直到所有的资料都读完为止。
图示为有条件限制的实例。
第二列大于第三列的时候,输出第一列数字序号,否则输出第四列英文序号。
内建变量信息如下:
变数名称 | 代表意义 |
NF | 每一行($0) 拥有的栏位总数 |
NR | 目前awk 所处理的是『第几行』资料 |
FS | 目前的分隔字元,预设是空白键 |
操作中的使用如下例:
[[email protected] ~]$ last -n 5| awk '{print $1 "\t lines: " NR "\t columns: " NF}' dmtsai lines: 1 columns: 10 dmtsai lines: 2 columns: 10 dmtsai lines: 3 columns: 10 dmtsai lines: 4 columns: 10 dmtsai lines: 5 columns: 9 # 注意喔,在awk 内的NR, NF 等变数要用大写,且不需要有钱字号$ 啦!而且~! 空格符号,如\t需要加双引号,否则无效
- 带条件类型判断
首先创建一个pay.txt文件,内容如下
每个子元之间是用空格隔开的,现在希望规范化输出格式并且计算出咩个人各科成绩的总值,操作如下:
- 第一行只是说明,所以第一行不要进行加总(NR==1 时处理);
- 第二行以后就会有加总的情况出现(NR>=2 以后处理)
上面的例子有几个重要事项应该要先说明的:
- awk 的指令间隔:所有awk 的动作,亦即在{} 内的动作,如果有需要多个指令辅助时,可利用分号『;』间隔, 或者直接以[Enter] 按键来隔开每个指令,例如上面的范例中,鸟哥共按了三次[enter] 喔!
- 逻辑运算当中,如果是『等于』的情况,则务必使用两个等号『==』!
- 格式化输出时,在printf 的格式设定当中,务必加上\n ,才能进行分行!
- 与bash shell 的变数不同,在awk 当中,变数可以直接使用,不需加上$ 符号。
当然,也可使用if这样的表达式。
(三)档案比对工具
1、diff
测试文件:/etc/passwd,在一个新的文件下,复制passwd文件,旧副本passwd.old,新副本passwd.new,新副本做删除第四行,第六行替换内容为no six line,操作如下:
[[email protected] ~]$ mkdir -p /tmp/testpw <==先建立测试用的目录 [[email protected] ~]$ cd /tmp/testpw [[email protected] testpw]$ cp /etc/passwd passwd.old [[email protected] testpw]$ cat /etc/passwd | sed -e '4d' -e '6c no six line' > passwd.new #注意一下, sed后面如果要接超过两个以上的动作时,每个动作前面得加-e才行! # 透过这个动作,在/tmp/testpw 里面便有新旧的passwd 档案存在了!
加下来通过diff,比较两个文件的区别:
[[email protected] ~]$ diff [-bBi] from-file to-file 选项与参数: from-file :一个档名,作为原始比对档案的档名; to-file :一个档名,作为目的比对档案的档名; 注意,from-file 或to-file 可以- 取代,那个- 代表『Standard input』之意。 -b :忽略一行当中,仅有多个空白的差异(例如"about me" 与"about me" 视为相同 -B :忽略空白行的差异。 -i :忽略大小写的不同。 范例一:比对passwd.old与passwd.new的差异: [[email protected] testpw]$ diff passwd.old passwd.new 4d3 <==左边第四行被删除(d)掉了,基准是右边的第三行 < adm:x:3:4:adm:/var/adm:/sbin/nologin <==这边列出左边(<)档案被删除的那一行内容 6c5 <==左边档案的第六行被取代(c)成右边档案的第五行 < sync:x:5:0:sync:/sbin:/bin/sync <==左边(<)档案第六行内容 --- > no six line <==右边(>)档案第五行内容 #很聪明吧!用diff就把我们刚刚的处理给比对完毕了!
- diff还可以用来比对目录下文件的差异
[[email protected] ~]$ diff /etc/rc0.d/ /etc/rc5.d/ Only in /etc/rc0.d/: K90network Only in /etc/rc5.d/: S10network
2、cmp
cmp用的范围比diff要小一些,cmp是以字符为单位进行比较,diff是以行为单位进行比较。
[[email protected] ~]$ cmp [-l] file1 file2 选项与参数: -l :将所有的不同点的位元组处都列出来。因为cmp 预设仅会输出第一个发现的不同点。 范例一:用cmp比较一下passwd.old及passwd.new [[email protected] testpw]$ cmp passwd.old passwd.new passwd.old passwd.new differ: char 106, line 4
3、patch
***...***...
(四)档案列印准备:pr
文件带标题打印等功能。
如上,档案名由“创建文件时间”,“文件路径”,“页码”
本章习题
情景模拟题一
知识储备:
1. /dev/null 是一个垃圾回收站,而且是个黑洞。
2. 不同命令,搜寻“深度”的区别
ls /home/* 可以递归列出子文件夹下的文件信息。上述示例已展示。但是,仅能找到子目录第一层,不能找子目录的子目录。
如上,通过find /etc/ -type f 可以查询到/etc下面所有子目录的资料。
-type f代表的是寻找的是普通文件类型。
grep -l 只列出文件名,不列出“内容”。“内容”也不是只文件的data内容,意会下吧...
3. 在实际使用过程中,若一个目录下文件或文件夹太多(这个是网上的说法,我觉得是指需要被操作的文件深度数目太多,这样讲似乎更合理些),在执行“*” 命令时 会提示Argument list too long,例如:
说明grep没有办法一下子对那么多行数据尽心该操作。
4. xargs
xargs是给其他命令传递参数的一个过滤器,或者说,可以自定义输出格式。如上所示,是其中的部分参数功能展示。
[[email protected] ~]$ find / -type f 2> /dev/null | xargs -n 10 grep -l '\*'
但是在使用时,不再加管道(|)了。
例题:透过grep 搜寻特殊字串,并配合资料流重导向来处理大量的档案搜寻问题。
- 目标:正确的使用正规表示法;
- 前提:需要了解资料流重导向,以及透过子指令$(command) 来处理档名的搜寻;
我们简单的以搜寻星号(*) 来处理底下的任务:
-
利用正规表示法找出系统中含有某些特殊关键字的档案,举例来说,找出在/etc底下含有星号(*)的档案与内容:
解决的方法必须要搭配万用字元,但是星号本身就是正规表示法的字符,因此需要如此进行:[[email protected] ~]$ grep '\*' /etc/* 2> /dev/null
你必须要注意的是,在单引号内的星号是正规表示法的字符,但我们要找的是星号,因此需要加上跳脱字符(\)。但是在 /etc/* 的那个* 则是bash 的万用字元!代表的是档案的档名喔!不过由上述的这个结果中,我们仅能找到/etc 底下第一层子目录的资料,无法找到次目录的资料, 如果想要连同完整的/etc 次目录资料,就得要这样做:[[email protected] ~]$ grep '\*' $(find /etc -type f ) 2> /dev/null #如果只想列出档名而不要列出内容的话,使用底下的方式来处理即可喔! [[email protected] ~]$ grep -l '\*' $(find /etc -type f ) 2> /dev/null
-
但如果档案数量太多呢?如同上述的案例,如果要找的是全系统(/) 呢?你可以这样做:
[[email protected] ~]$ grep '\*' $(find / -type f 2> /dev/null ) -bash: /usr/bin/grep: Argument list too long
-
真要命!由于指令列的内容长度是有限制的,因此当搜寻的对象是整个系统时,上述的指令会发生错误。那该如何是好?此时我们可以透过管线命令以及xargs 来处理。举例来说,让grep 每次仅能处理10 个档名,此时你可以这样想:
- 先用find 去找出档案;
- 用xargs 将这些档案每次丢10 个给grep 来作为参数处理;
- grep 实际开始搜寻档案内容。
[[email protected] ~]$ find / -type f 2> /dev/null | xargs -n 10 grep '\*'
-
从输出的结果来看,资料量实在非常庞大!那如果我只是想要知道档名而已呢?你可以透过grep 的功能来找到如下的参数!
[[email protected] ~]$ find / -type f 2> /dev/null | xargs -n 10 grep -l '\*'