缘起:上完培训课后,同学们对sed加正则处理问题的热情高涨,氛围相当好,但是,有个别同学还是有些晕乎,因此,撰写此文。此例如果搞熟练了,sed和正则的大部分常用技术你掌握起来就简单了。
如果还有不会的,老男孩就拿出最后的讲解必杀技(先保密,嘿!),同学们想不会那是不可能的,除非你们自己主动放弃,哈哈。来,开整~!
老男孩linux培训第三节课程课前第考试一题,问题如下
1.如何取得/etiantian文件的权限对应的数字内容,如-rw-r--r--为644,要求使用命令取得644或0644这样的数字。
解答:
这个问题的解答方法我们已经介绍了不下10种了,这里给大家在深入浅出的讲一下最难理解sed特殊用法加正则的实现。
由于难度比较大,为了让同学们能了解的更清楚明白,我们需要先搭几个台阶。
台阶1:sed相关命令集选项参数
表1-1 sed命令
命令
功能
g
与s联合使用时,表示对当前行全局匹配替换(与下一个g意义不同)
p
打印匹配行
s
常说的查找并替换,用一个字符串替换成另一个
表1-2 sed选项
选项
功能
-e
允许多项编辑
-n
取消默认输出
详细的参数,命令信息可以参考《老男孩linux实战培训教案-强大的流编辑器sed详解指南》
台阶2:sed替换基本命令
sed -n 's#▇#▲#g' oldboy.log
[root@oldboy ~]# cat oldboy.log
▇
执行下面命令把▇替换为▲,注意:原文件没变。
[root@oldboy ~]# sed 's#▇#▲#g' oldboy.log
▲
提示:其中分隔符#,可以使用/,%,@等替代,详细使用可参考老男孩的相关文档。
台阶3:正则表达式基本用法(三剑客grep,sed,awk都支持正则表达式)
特殊字符
含义与例子
^word
搜索以字符(word)开头的行。
例:grep -v '^#' oldboy.log
过滤掉以#行首(开始)的行,一般查看配置文件时会用到。
Word$
搜索以字符(word)结尾的行。
例:grep -n '!$' oldboy.log
搜索以!(叹号)结尾的行
.(点号)
代表切只能代表“任意一个”字符,“一个”是一个任意字符
例:grep -n 'e.e' oldboy.log
搜索的字符串可以是(eve),(eae),(eee),(e e),但不能仅有(ee)即e与e之间一定且仅有一个字符,而空格符也是字符
*
重复零个或多个的前一个重复字符
例:grep -n 'ess*' oldboy.log
找出含有(es),(ess),(esss)等的字符串,注意,因为(*)可以是0个,所以es 也是符合搜索字符串。另外,因为(*)为重复“前一个字符”的符号,因此,在(*)之前必须要紧接一个重复字符。任意字符则为(.*)
.*
根据前面的单个字符,我们知道.*匹配所有字符串
台阶4:sed中\( \)和\1的功能
1)sed的\( \)的功能可以记住正则表达式的一部分,其中,\1为第一个记住的模式即第一个小括号中的匹配内容,\2第二记住的模式,即第二个小括号中的匹配内容,sed最多可以记住9个。
例1:echo I am oldboy teacher.
如果你想保留一行的单词,删除剩下的部分,使用圆括号标记想保留的部分:
本例我们要保留oldboy把其他内容删除。
[root@oldboy ~]# echo I am oldboy teacher. >test.txt
[root@oldboy ~]# cat test.txt
I am oldboy teacher.
[root@oldboy ~]# sed 's#^.*am \([a-z].*\) tea.*$#\1#g' test.txt
oldboy
命令说明:#命令说明中的□代替空格
a)^.*am□ -->这句的意思是以任意字符开头到am□为止,匹配文件中的“I am□”字符串,
b)\([a-z].*\)□-->这句的外壳就是括号\(\),里面的[a-z]表示匹配26个字母的任何一个,[a-z].* 合起来就是匹配任意多个字符,本题来说就是匹配oldboy字符串,由于oldboy字符串是需要保留的,因此用括号括起来匹配,后面通过\1来取oldboy字符串。
c)□tea.*$ -->表示以空格tea起始任意字符结尾,实际就是匹配oldboy字符串后,紧接着的字符串“□teacher.”.
d)后面被替换的内容中的\1就是取前面的括号里的内容了,也就是我们要的oldboy字符串。
其实啊,你上了本台阶,本文的主题答案你就该会了。
台阶5:取字符串的技巧 ==>这个台阶的思路技巧是最重要的,也是老男孩linux培训课程深入浅出的一个小案例。
[root@oldboy ~]# stat /ett|sed -n '4p'
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
1)处理需要的目标(获取的字符串如本文开篇的644)前的字符串一般用以..开头(^.*)来匹配开头,匹配的目标前的结尾写上实际的字符,如:“^.*(0”表达式匹配“Access: (0”,说到这先给个例子:
[root@oldboy ~]# stat /ett|sed -n '4p'|sed 's#^.*(0##g'
644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
大家看到了吧,644前面的部分已经被删除了。
2)而处理目标后的内容一般在紧接着目标后匹配的开头写上实际的字符,而结尾是用以...结尾(.*$)来匹配,如“/-r.*$”表达式匹配“/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)”在来个例子:
[root@oldboy ~]# stat /ett|sed -n '4p'|sed 's#/-r.*$##g'
Access: (0644
哈哈,这次644后面的又被删除了。
有一点要注意,就是实际字符的选取最好要唯一,因为正则表达式是贪婪的,它总是尽可能的匹配更远的符合匹配的内容。另外不要落了字符串中的空格。
合起来就是答案了。
[root@oldboy ~]# stat /ett|sed -n '4p'|sed 's#^.*(0##g'|sed 's#/-r.*$##g'
644
先删除目标前面的,在删除目标后的,这是应用了两次sed,每次只匹配了一半,第一次匹配目标前,第二次匹配目标后,那么能不能直接匹配整行字符串呢?
所有的台阶都上来后,接下来就引出本文的主题。
关键人物出场:
有了前面的台阶,现在这道题就简单多了,好先抛出答案,然后在分析说明。
[root@oldboy ~]# stat /ett|sed -n 's#^.*(0\([0-7].*\)\/-.*$#\1#gp'
644
命令说明:
a)“^.*(0”匹配目标前的内容,这个不用再解释了吧,这里匹配的是“Access: (0
b)“\/-.*$” 匹配目标后的内容,这个也不用再解释了吧,这里匹配的是“/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)”
c)这中间的644就是我们要打印的结果,所以要用括号括起来呦,于是\(\)就不用再解释了,[0-7].*就是匹配644,匹配到斜线/前为止,虽然不是精确匹配但是这里够用了。因此,“\([0-7].*\)”匹配的就是“644”了,前面的台阶已经讲过了,匹配模式中\(\)的内容,可以用\1来取出(如果是第二个扩号就是\2,类推),所以啊,644的结果就出来拉。
后记小结:
当然了,匹配方法不只这一种,还可以写出很多种,但是老男孩老师给出的是偶自己总结的一个易于初学者记忆的思路,如果大家有更好的记忆的方法,可以告诉我啊。
同学们可以拿取网卡的IP地址练手,检验下,看看你们的刀磨快了么?哈哈!
附带其他解决答案汇总:
[root@oldboy ~]# touch /ett
1)
[root@oldboy ~]# stat /ett|sed -n '4p'|awk -F '[(/]' '{print $2}'
0644
2)
[root@oldboy ~]# stat /ett|sed -n '4p'|cut -d "/" -f1|cut -d "(" -f2
0644
3)
[root@oldboy ~]# stat /ett|head -4|tail -1|cut -d "/" -f1|cut -d "(" -f2
0644
4)
[root@oldboy ~]# stat /ett|head -4|tail -1|awk -F "/" '{print $1}'|awk -F "(" '{print $2}'
0644
5)
[root@oldboy ~]# stat -c %a /ett
644
6)
[root@oldboy ~]# stat /ett|sed -n '4p'|sed 's#^.*(0##g'|sed 's#/-.*$##g'
644
7)
[root@oldboy ~]# stat /ett|sed -n 's#^.*(0\([0-7].*\)\/-.*$#\1#gp'
644
8)
[root@oldboy ~]# stat /ett|sed -n '4p'|sed -ne 's/^.* (0\([^ ]*\)\/-.*$/\1/gp'
644
9)
[root@oldboy ~]# stat /ett|sed -n '/Access: (/p'|sed -r 's/^.*\(0(.*)\/-.*$/\1/'
644
[root@oldboy ~]# stat /ett|sed -n '4p'|sed -e 's/^Access: (\([0-9]*\)\/-rw-r.*$/\1/g'
0644
10)
[root@oldboy ~]# stat /ett|grep "Access: (0"|cut -c 10-13
0644
[root@oldboy ~]# stat /etc/hosts|sed -nr '4s#^.*\(0(.*)/-.*$#\1#gp'
644
老男孩语录⑧⑥-态度决定一切!
好的态度就是成功的一半!