第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

一、什么是正则表达式

处理字符串的方法,以行为单位处理字符串,通过一些特殊符号的辅助,帮助使用者实现“寻找/替代/删除”的功能

二、基础正则表达式

(一)语系对正则表达式的影响

(二)grep的一些高级参数

(三)基础正则表达式练习

例题一:查找特定字符串
例题二:利用中括号 [ ]来查找集合字符

这部分需要反复的练习,加深印象,无特别需要注意的地方,请看鸟哥链接

http://linux.vbird.org/linux_basic/0330regularex.php#basicre

例题三:行首与行尾 ^ $

/etc/rsyslog.conf里面有有很多空白行和以“#”开头的,我只想查看其正文部分,操作如下:

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

有一个需要注意的问题:管道前段,添加需要要慎重。

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

管道前段命令中加了-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工具

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'

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

想要最终获得三个IP地址,操作如下

/sbin/ifconfig | grep 'inet '| sed 's/inet //g'|sed 's/netmask.*$//g'

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

想要去掉#和空行,只显示有用的内容

第一步:去掉注释行(#)

cat /etc/man_db.conf | grep 'MAN' | sed 's/^#.*$//g'

这一步会把之前的注释行替换成空行

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

接下来要把空行去掉

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 
egrep -n 'gd|good|dog' 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 啰!

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

两条命令的区别之处在于红色方框的内容,从显示中可以看出“%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

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

每一行的每个栏位都是有变数名称的,$1,$2就是代表的是第一列,第二列的意思;另外,$0代表的是一整行资料。注意看awk的正规表达,动作是在大括号{}里面的,因为每一行都要处理,所以没有条件类型的限制。

注意单引号和双引号的使用,单引号是awk的语法要求,

刚刚上面五行当中,整个awk的处理流程是:

  1. 读入第一行,并将第一行的资料填入$0, $1, $2.... 等变数当中;
  2. 依据"条件类型" 的限制,判断是否需要进行后面的"动作";
  3. 做完所有的动作与条件类型;
  4. 若还有后续的『行』的资料,则重复上面1~3 的步骤,直到所有的资料都读完为止。
所以,awk是以行为处理单位,以栏位为最小处理单元

图示为有条件限制的实例。

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

第二列大于第三列的时候,输出第一列数字序号,否则输出第四列英文序号。

内建变量信息如下:

变数名称 代表意义
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文件,内容如下

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

每个子元之间是用空格隔开的,现在希望规范化输出格式并且计算出咩个人各科成绩的总值,操作如下:

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

  • 第一行只是说明,所以第一行不要进行加总(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

文件带标题打印等功能。

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

如上,档案名由“创建文件时间”,“文件路径”,“页码”

本章习题

情景模拟题一

知识储备:

1. /dev/null 是一个垃圾回收站,而且是个黑洞。

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

2. 不同命令,搜寻“深度”的区别

ls /home/* 可以递归列出子文件夹下的文件信息。上述示例已展示。但是,仅能找到子目录第一层,不能找子目录的子目录。

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

如上,通过find /etc/ -type f 可以查询到/etc下面所有子目录的资料。

-type f代表的是寻找的是普通文件类型。

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

grep -l 只列出文件名,不列出“内容”。“内容”也不是只文件的data内容,意会下吧...

3. 在实际使用过程中,若一个目录下文件或文件夹太多(这个是网上的说法,我觉得是指需要被操作的文件深度数目太多,这样讲似乎更合理些),在执行“*” 命令时 会提示Argument list too long,例如:

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

说明grep没有办法一下子对那么多行数据尽心该操作。

4. xargs

第十二章 正则表达式与文件格式化处理【鸟哥linux私房菜学习笔记】

xargs是给其他命令传递参数的一个过滤器,或者说,可以自定义输出格式。如上所示,是其中的部分参数功能展示。

[[email protected] ~]$ find / -type f 2> /dev/null | xargs -n 10 grep -l '\*'

但是在使用时,不再加管道(|)了。

例题:透过grep 搜寻特殊字串,并配合资料流重导向来处理大量的档案搜寻问题。

  • 目标:正确的使用正规表示法;
  • 前提:需要了解资料流重导向,以及透过子指令$(command) 来处理档名的搜寻;

我们简单的以搜寻星号(*) 来处理底下的任务:

  1. 利用正规表示法找出系统中含有某些特殊关键字的档案,举例来说,找出在/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
  2. 但如果档案数量太多呢?如同上述的案例,如果要找的是全系统(/) 呢?你可以这样做:
    [[email protected] ~]$ grep '\*' $(find / -type f 2> /dev/null )
    -bash: /usr/bin/grep: Argument list too long
  3. 真要命!由于指令列的内容长度是有限制的,因此当搜寻的对象是整个系统时,上述的指令会发生错误。那该如何是好?此时我们可以透过管线命令以及xargs 来处理。举例来说,让grep 每次仅能处理10 个档名,此时你可以这样想:
    1. 先用find 去找出档案;
    2. 用xargs 将这些档案每次丢10 个给grep 来作为参数处理;
    3. grep 实际开始搜寻档案内容。
    所以整个作法就会变成这样:
    [[email protected] ~]$ find / -type f 2> /dev/null | xargs -n 10 grep '\*'
  4. 从输出的结果来看,资料量实在非常庞大!那如果我只是想要知道档名而已呢?你可以透过grep 的功能来找到如下的参数!
    [[email protected] ~]$ find / -type f 2> /dev/null | xargs -n 10 grep -l '\*'