正则表达式与shell在Linux中是基础中的基础。
正则表达式就是处理字符串的方法,它是以行为单位来进行字符串的处理行为。正则表达式通过一些特殊符号的辅助,可以让用户轻易达到查找,删除,替换某特定字符串的处理程序。
正则表达式是一种“表示方法”,只要工具程序支持这种表示法,那么就可以通过该工具用正则表达式处理字符串,如vi,grep,awk,sed等工具。而例如cp,ls等命令不支持正则表达式,所以只能用bash自身的通配符。
使用正则表达式可以在大数据量的情况下快速筛选出自己需要的信息。目前很多软件都支持正则表达式,最常见的如“邮件服务器”。如果我们要做到在服务器端就将垃圾邮件和广告邮件剔除过滤,就需要将来信的标题和内容进行特殊字符串对比。目前两大邮件服务器软件sendmail和postfix以及支持邮件服务器的相关分析软件都支持正则表达式的对比功能。
注:正则表达式和通配符是完全不同的两种东西。通配符是bash接口的功能,而正则表达式是一种字符串处理的表示方式。
正则表达式的字符串表示方式根据不同的严谨度分为基础正则表达式和扩展正则表达式。扩展正则表达式除了简单的一组字符串处理之外还可以做组的字符串处理。
语系会影响字符排序,从而影响正则表达式的结果。例如,在英文大小写的编码顺序中,zh_CN.big5及C这两种语系编码顺序分别如下:
上面两种语系的编码顺序不同,如果要选取大写字符而使用[A-Z]时,LANG=C可以仅找到大写字符(因为大写字符在LANG=C的编码顺序中是连续的),而LANG=zh_CN时连同小写的b-z都会被选取出来,所以使用正则表达式时,需要特别留意当时环境的语系为何,否则可能会得出与别人不相同的选取结果。
一般我们在练习正则表达式时,会选择使用兼容于POSIX标准的LANG=C这个语系数据。下面很多联系都是使用LANG=C这个语系数据来进行的。
首先我们得了解一下一些特殊符号:
特殊符号 | 代表意义 |
---|---|
[:alnum:] | 代表英文大小写字符及数字,即0-9,A-Z,a-z |
[:alpha:] | 代表英文大小写字符,即A-Z,a-z |
[:blank:] | 代表空格键和Tab键 |
[:cntrl:] | 代表键盘上的控制按键,如CR,LF,Tab,Del等 |
[:digit:] | 代表数字,即0-9 |
[:graph:] | 除了空格键和Tab键外的其他所有按键 |
[:lower:] | 代表小写字符,即a-z |
[:print:] | 代表任何可以被打印出来的字符 |
[:punct:] | 代表标点符号及特殊字符,即”,’,$,#,?,! |
[:upper:] | 代表大写字符,即A-Z |
[:space:] | 代表任何会产生空白的字符,包括空格键,[Tab],CR等 |
[:xdigit:] | 代表十六进制的数字类型,包括0-9,A-F,a-f的数字和字符 |
上表中尤其时[:alnum:],[:alpha:],[:upper:],[:lower:],[:digit:]这几个一定要知道是什么意思,因为它要比a-z,A-Z的用途更加确定。
回顾一下chapter11中学的grep指令,grep指令是分析整行数据,若其中有我们想要的字符串,取出字符串所在的整行数据。
grep的语法和参数
grep [-A] [-B] [–color=auto] ‘搜寻字符串’ filename
-A:后面可加数字n,为after的意思,表示列出该行外,后续的n行也列出来
-B:后面可加数字n,为before的意思,表示列出该行外,前面的n行也列出来
–color=auto:可将正确的那个选取数据列出颜色。
示例1:用dmesg指令获取内核产生的信息,再以grep指令找出网卡相关信息(包含eth字符串的信息),将目标信息和前4行和后3行数据都显示在屏幕上,并将搜寻的字符串用颜色标出
示例2:在示例1的题目中加一个要求:显示行号
练习的大前提是
语系已经使用export LANG=C的设置值
grep已经使用alias设置成为grep –color=auto
用来练习的文件下载链接
http://linux.vbird.org/linux_basic/0330regularex/regular_express.txt
如果Linux能联网,可以使用如下命令来获取该文件
wget http://linux.vbird.org/linux_basic/0330regularex/regular_express.txt
如图所示,下载完成后该文件在当前目录下
regular_express.txt文件内容如图所示
共22行,最下面一行为空白行,并包含断行字符^M。
示例1:从~/regular_express.txt中选取特定字符串the所在的行
示例2:反向选择,从~/regular_express.txt中选取无特定字符串the的行
如图所示,屏幕上出现了除8,12,15,16,18以外的其他行。
示例3:从~/regular_express.txt中选取不论大小写的字符串the所在的行
示例1:从~/regular_express.txt中找到test或taste所在的行
示例2:从~/regular_express.txt中找到有特殊字符串oo的行
示例3:从~/regular_express.txt中找到有特殊字符串oo但前面没有g的行
对比示例2和示例3的结果发现示例3比示例2少了1,9两行,而19行之所以在示例3中出现是因为”o[oo]”也是符合前面没有g的情况的。
示例4:从~/regular_express.txt中找到有特殊字符串oo但前面没有小写字符的行
示例5:从~/regular_express.txt中找到有数字的行
考虑到语系对编码顺序的影响,因此除了连续编码使用减号-之外,你也可以使用如下方法来取得示例4和示例5的结果。
如此就把前面表格中提到的特殊关键字用上了。
示例1:在练习一的示例1中,我们查找了带有the的行,如果我想要the只在行首的行呢
利用制表符^可以做到
示例2:列出开头是小写字符的行,如图,有两种方式
示例3:列出开头不是英文字符的行,如图,同样有2种书写方式
注:^符号在[]里面和外面的意义是不同的,^在[]外面代表行首,^在[]里面代表反向选择。
示例4:找出行尾是小数点.的行
我们发现结果是不对的,因为在.$的写法中,小数点.是特殊字符,有其他意义,因此需要用到转义字符\,正确的写法应该是下面这种
不过你或许会奇怪,为什么第5行到第9行不是.结尾,我们把第5-9行单独拿出来看
如图所示,第5-9行带有^M,这是windows的断行字符,在Linux中不算是断行字符,而被判断为一般字符,因此第5-9行算是以M结尾的行。
示例5:找出空白行
空白行只有行首和行尾(^$),中间无其他内容,因此下面这种写法是可行的
示例6:假设在一个配置文件中你知道空白行和以#开头的行为批注,将数据列出给别人参考时,可以将这些行省去,要怎么做呢,以~/.bashrc为例
输出~/.bashrc中除了以#开头的行和空白行之外的行
注:在上述管道命令中不能加行号了,不然第二个grep命令中stdin就是以行号为行首的,所以第二个命令中^#的写法就会失去效果。
在第11章的bash中我们知道通配符中*代表任意(0个或多个)字符,但正则表达式和通配符是完全不同的,在正则表达式中,这两个符号也有完全不同的意义。
示例1:从~/regular_express.txt中找出含有以g开头以d结尾一共4个字符的字符串的行
示例2:从~/regular_express.txt中列出含有连续两个或两个以上o字符的行
这里解释一下,因为*星号代表的是重复前一个字符0次到无数次的意思,因此在”ooo*”的写法中,前两个”oo”表示的是必须存在的两个连续oo,而剩下的结构”o*”表示的一次是重复o字符0次或无数次。
注:也可以一次都不重复,即最后一个o是可有可无的。
示例3:从~/regular_express.txt中列出含有开头和结尾都是g,中间至少有一个o的字符串的行
示例4:从~/regular_express.txt中列出含有开头和结尾都是g,中间字符可有可无的行
那么中间字符可有可无应该怎么写呢,这时我们必须想到,中间字符可以是任意字符,该字符也可以有0个和无数个,这样就意味着*星号前面不能是明确的某个字符。
如图所示,我们学过.小数点在正则表达式中代表1个任意字符。因此.*表示的是任意字符重复0次或无数次。
从上面的示例中我们知道,在*的正则表达式使用中,*不可能单独存在,前面必须要带一个字符,以表示该字符重复0次或无数次的意思,这是一个固定的结构。
示例5:从~/regular_express.txt中列出含有任意数字的行
这道题在前面的示例中做到过,当时的写法是grep –n ‘[0-9]’ regular_express.txt
但是我想用*来表示的话应该怎么写呢?
这道题可以被解读为,列出必含有一个数字而其他数字可有可无的行。
如图,分析一下,在上面的写法中[0-9][0-9]*,第一个[0-9]代表必然存在一个0-9中的数字,而后面的[0-9]*表示[0-9]中任意一个数字重复0次或无数次,也可以不存在,这就保证了必然含有一个数字而其他数字可有可无的意思。
当然我们也可以用正则表达式的特殊符号来表示相同的意思
如上图所示,这两种写法的结果和意思是一样的,之前列表中写过[:digit:]代表的就是0-9,因此可以互相替换。
注:[0-9]代表的是0-9中任意一个字符,符合*前面必然有1个字符的结构。
限定连续RE字符范围怎么理解?在上面的练习中,我们利用.小数点和*星号来表示0个或无数个任意字符。但如果我在题目中规定了重复字符的个数范围呢?比如我想找出2-5个o的连续字符串,该如何做?这时我们就能用到{}了。因为{}在shell中是有特殊意义的,因此使用时必须加转义字符\让它失去意义。
示例1:从~/regular_express.txt中列出含有两个连续o的字符串的行。
但是这道题我们之前做过,还有另外两种写法也可以达到目的。
这几种写法都有行中必然含有两个连续的o的意思。这样来看我们使用{}来查找字符串根本没有意义,其他写法也能达到目的,让我们来看示例2。
示例2:从~/regular_express.txt中列出含有以g开头,后面接2-5个连续的o,最后以g结尾的字符串的行
示例3:从~/regular_express.txt中列出含有以g开头,后面接2个或2个以上o且最后一个字符是g的字符串的行。
注:如果使用正则表达式中的{},那么命令行写法必须是x{a,b}这种结构,表示a个到b个连续的x字符。其中a可以不写,表示无下限个数,b也可以不写,表示无上限个数,x必须写,但x可以是.小数点,表示任意一个字符。x也可以用[…]的表示方法,表示在[]范围内的任意一个字符。
在上面的练习中,我们接触到了很多正则表达式字符,如^,$,.,*,{},[]等等,这些都是基础正则表达式字符,这里我们用列表的形式对他们做一个总结。
RE字符 | 意义与范例 |
---|---|
^word | 意义:待查找的字符串word在行首 范例:查找行首为#的行,并列出行号 grep –n ‘^#’ regular_express.txt |
word$ | 意义:待查找的字符串word在行尾 范例:查找行尾为!的行,并列出行号 grep –n ‘!$’ regular_express.txt |
. | 意义:代表一个任意字符 范例:查找e开头,e结尾,有3个字符的字符串的行,并列出行号 grep –n ‘e.e’ regular_express.txt |
\ | 意义:转义字符,将特殊符号的特殊意义去除 范例:查找含有单引号’的行,并列出行号 Grep –n \’ regular_express.txt |
* | 意义:重复0个到无数个前一个字符 范例:找出以g开头,中间重复无数个o且以g结尾的字符串的行,并列出行号 grep –n ‘goo*g’ regular_express.txt |
[n1-n2] | 意义:连续字符集合n1到n2中任意一个字符 范例:找到含有数字的一行,并列出行号 grep –n ‘[0-9]’ regular_express.txt |
[list] | 意义:字符集合list中任意一个字符 范例:找出含有test或tast的行,并列出行号 grep –n ‘t[ea]st’ regular_express.txt |
[^list] | 意义:除了字符集合list以外的所有字符 范例:找出含有以g开头,后面不是oa和空格的字符串的行,并列出行号 grep –n ‘g[^oa ]’ regular_express.txt |
x{a,b} | 意义:x字符连续重复a次到b次 范例:找到含有以g开头后面接2-5个连续o以g结尾的字符串的行,并列出行号 grep –n ‘go{2,5}g’ regular_express.txt |
注:正则表达式的特殊字符与一般在命令行输入命令的通配符并不相同
举例来说,在根目录中,如果用不支持正则表达式的ls命令,如我们要查找以s开头的目录并列出其中的所有档案,需要输入ls –al t*
但是用正则表达式的话需要输入ls|grep ‘^t’|xargs ls -al
sed支持正则表达式,本身也是一个管道命令,可以分析stdin,而且sed还可以将数据进行替换删除新增和选取特定行等功能。
sed [-nefri] [动作]
-n:使用安静(silent)模式,在一般sed的用法中,所有来自stdin的数据一般都会被列出到屏幕上,但如果加上-n参数后,则只有经过sed特殊处理的那一行(或者操作)才会被列出来
-e:直接在命令行模式上进行sed的动作编辑
-f:直接将sed的动作写在一个文件内,-f filename则可以执行filename内的sed动作
-r:sed的动作支持的是扩展型正则表达式的语法(默认是基础正则表达式语法)
-i:直接修改读取的文件内容,而不是由屏幕输出
动作说明:[n1[,n2]]function
n1,n2:不见得一定存在,一般代表选择进行动作的行数,如动作需要在10-20行之间进行,则“10,20[动作]”
function有以下参数:
a:新增,a后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)
c:替换,c后面可以接字符串,这些字符串可以替换n1到n2之间的行
d:删除,d后面通常不接任何参数
i:插入,i后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行)
p:打印,也就是将某个选择的数据打印出来,通常p会与参数sed –n一起运行
s:替换,可以直接进行替换工作。通常这个s的动作可以搭配正则表达式,例如1,20s/old/new/g就是
示例1:将/etc/passwd的内容列出并且打印行号,同时将第2-5行删除
注:nl指令为/etc/passwd档案内容添加了行号。
示例2:将/etc/passwd的内容列出并且删除第3行
示例3:将/etc/passwd的内容列出并且删除第3行到最后一行
如图所示以$符号代表最后一行。
示例4:将/etc/passwd的内容列出并且删除前十行
示例5:在/etc/passwd的第二行后加上drink tea?字样
示例6:在/etc/passwd的第二行后加上两行字,例如“Drink tea or ……”与“drink beer?”
如图所示,这里要注意的是,输入命令时,必须利用\符号将指令分成两行输入,结果中才会把加入的字显示成两行。即输入\后按回车,然后在第二行继续输入第二行要加入的字。
示例7:在/etc/passwd的第二行前加入两行字,例如“Drink tea or ……”与“drink beer?”
在第二行前加入,只要把2a的动作变成2i就可以了。
示例6和示例7的重点时我们可以新增不止一行,可以新增好几行,但每行之间都必须要在输入指令时以反斜杠\换行输入新增的行
接下来我们要实现整行替换的功能。
示例1:将/etc/passwd中第2-5行的内容替换成“No 2-5 number”
如图所示,用动作参数c来实现替换功能。
示例2:仅列出/etc/passwd的第5-7行
如果不使用sed工具,我们是这样做的
但是我们现在可以通过sed工具直接用行号提取
使用-n参数,只有经过sed工具处理的那几行才会被列出来,而指令中经过sed工具处理的无非就是第5~7行。动作参数p是打印的意思。
加上-n参数是很重要的,如果不加,结果如图所示
我们发现第5~7行被重复打印出来了,在一般sed的用法中,所有来自stdin的数据都会被打印出来,所以上图中没有用-n,把所有nl /etc/passwd的结果都打印了出来,然后再打印出5,7p的结果,因此,5-7行就重复显示了。
sed工具的查找与替换写法与vi类似,写法是sed ‘s/要被替换的字符串/新字符串/g’
示例1:利用/sbin/ifconfig指令获取IP数据并处理这些数据,打印出IP地址
如图,我们先获取IP数据,观察数据结构,红框中是我最终要打印出来的内容
那么我写了一道管线命令,总体思路是先去除IP地址所在行,然后删除inet字符串,删除netmask到最后的字符串,为了熟悉查找替换的写法,我这边把删除功能都写成替换成空字符了
解释一下上面的命令,用/sbin/ifconfig ens33取出IP数据,传给gerp ‘netmask’,这个指令利用netmask关键字找到了IP地址所在行,传给sed ‘s/inet//g’指令,这个指令的意思是将inet字符串替换成空字符,即删除。然后将剩下的结果传给sed ‘s/netmask.*$//g’,这个指令中用到了正则表达式,意思是将netmask到最后的字符串都替换成空字符,即删除。得出的结果中就只剩下IP地址。
sed工具还可以直接修改文件内容,而不必通过管道命令和数据流重定向
示例1:利用sed将regular_express.txt每行结尾的.小数点替换成!感叹号
如图所示,.小数点和!叹号由于是特殊字符,因此需要在前面加上转义字符\。使用-i参数可以直接修改文件内容,不会在屏幕上输出。
在练习三的最后一个示例中,我们要除去~/.bashrc中的空白行和#开头的注释行,用的是下面的语句:
grep -v ‘^$’ .bashrc|grep -v ‘^#’
这种语句属于基础型正则表达式,如果用扩展型正则表达式表示,下面的语句表达的是同一个意思
egrep -v ‘^$|^#’ .bashrc
在上面的语句中,egrep相当于grep -E。由于grep指令只支持基础正则表达式,因此无法应用于扩展型正则表达式,所以需要加参数-E,别名为egrep。而语句中|符号,表达的是“or或”的意思,体现了扩展型正则表达式的群组功能。上面的扩展型正则表达式把2个管道命令合并成一个了,更加简便。
扩展型正则表达式的字符与意义
字符 | 意义 |
---|---|
word+ | 有一个或一个以上的word字符 |
word? | 0个或1个的word字符 |
| | 用或(or)的方式找出数个字符串 |
() | 找出群组字符串 |
()+ | 多个重复群组的判别 |
示例1:从~/.regular_express.txt档案中搜寻god,good,goood……等等的字符串所在行,并显示行号。
输入egrep -n ‘go+d’ regular_express.txt
注:这里使用的是扩展型正则表达式,因此需要用egrep。
示例2:从~/.regular_express.txt中搜寻gd,god这两个字符串所在的行,并显示行号。
输入egrep -n ‘go?d’ regular_express.txt
示例3:搜寻gd或good这两个字符串所在的行,并显示行号。
输入egrep -n ‘gd|good’ regular_express.txt
示例4:搜寻glad或good这两个字符串所在的行,并显示行号。
此题的命令有两种写法
输入egrep -n ‘glad|good’ regular_express.txt
或输入egrep -n ‘g(la|oo)d’ regular_express.txt
如图所示
示例5:~./test.txt的内容如下图所示,查找EdefaultdefaultdefaultdefaultT字符串所在的行
输入egrep -n ‘E(default)+T’ test.txt
注:在指令中是区分大小写的,如果把上述指令修改成egrep -n ‘E(default)+t’ test.txt,那么查找出来的就是第3行了,如图所示
经过这几个示例,我们发现这些扩展正则表达式之间的使用都是有关联的,比如,+符号和()+符号,无非就是重复字符串和重复字符的关系,+的使用方法是一样的,如果重复字符串,就要在字符串外面加上括号。而括号代表的是群组,因为是一群字符组成一个字符串,因此要用括号包裹起来。
不通过vi和vim去重新编辑而将文件内容排版后做输出,打印等相关操作。
printf指令是打印格式管理员,可以帮我们将资料输出的结果格式化,并且支持一些特殊字符。
printf的语法和参数
printf ‘打印格式’ 实际内容
关于格式方面的几个特殊样式:
\a:警告声音输出
\b:退格键(Back Space)
\f:清屏(form feed)
\n:输出新的一样
\r:Enter按键
\t:水平的Tab按键
\v:垂直的Tab按键
\xNN:NN表示2位数的是数字,可以转换数字变成字符
关于C语言内常见的变量格式
%ns:n是数字,s是字符,代表多少个字符
%ni:n是数字,i是整数,代表多少个整数字数
%N.nf:N和n都是数字,f是浮点型小数,代表共N个位数但小数位有n位的浮点型小数
示例档案printf.txt内容如图所示
示例1:列出~/.printf.txt档案中的姓名和成绩(以Tab键分隔)
输入printf ‘%s\t%s\t%s\t%s\t%s\t\n’ $(cat printf.txt)
其中单引号中的%s\t%s\t%s\t%s\t%s\t\n是规定输出格式,%s代表字符串,\t代表水平的Tab键,\n代表回车换行。
注:$(cat printf.txx)这个结构在变量那节学到过,$(指令)是借由指令取得数据。因为printf并不是管道命令,所以要用这种写法。当然我们之前也在管道命令那节学过,有个指令叫xargs指令,该指令能使不是管道命令的指令被应用在管道命令中。所以,用下列指令也可以实现示例1的结果
输入cat printf.txt|xargs printf ‘%s\t%s\t%s\t%s\t%s\t\n’
示例2:将printf.txt档案中第二行以后的数据分别以字符串,整数,小数点形式来表示
输入printf ‘%10s\t%5i\t%5i\t%5i\t%8.2\n’ $(grep -v ‘Name’ printf.txt)
%10s\t%5i\t%5i\t%5i\t%8.2\n表示10位字符串类型,5位整数类型,5位整数类型,5位整数类型,8位浮点型小数类型且小数点后2位,中间以水平方向的Tab键分隔。
注:%8.2f中的8位是指包括小数点在内的整个小数的字符宽度。
如果将小数点位数变成1位,整数位数变成1位,字符串位数变成2位,又会如何呢?
如图所示,我设置的数值长度都比它们实际长度要短,但结果中,字符串类型和整数类型的数据并没有被截断,而浮点型小数因为小数点位数小于其实际长度,因此被截断了1位。
print除了格式化打印数据外,还可以根据ASCII的数字与字符对应来将数字转化成字符。
示例3:将45转化成ASCII中的对应字符。
如图所示,45对应字符是E,从图中两次指令的执行结果来看,printf指令引号中有格式的区别。
而且单引号中一次只能查询一个数字。
比起sed工具适用于以行为单位的数据处理,awk工具倾向于一行中分成数个字段来处理。awk可以读取前一个指令的stdout,因此也是一个管道命令。在awk指令中每个分段都有一个变量名称来指代它,上图中的 1指代的是第一分段, 1 指 代 的 是 第 一 分 段 , 2指代的是第二分段,以此类推。还有一个特殊的变量$0,它是一整行资料的意思。
awk的语法和参数
awk ‘条件类型1{动作1}条件类型2{动作2}……’ filename
示例1:取出前5个登录者的账号和终端
前五个登录者的详细信息如图所示,我们要对这些数据做处理,得出前两列数据。
输入last -n 5|awk ‘{printf $1“\t”$2”\n”}’,结果如图所示
如上图,通过printf功能将字段数据列出来,字段的分隔以空格键或Tab分隔。由于每行数据都需要处理,因此不需要条件类型这个参数。
但是在上面的结果中我们发现,输出的不只是前5个登录用户,还有一行wtmp的数据,这不是我们想要的结果,修改命令并执行如图。
输入last -n 5|grep -v ‘wtmp’|grep -v ‘^$’|awk ‘{printf $1”\t”$2”\n”}’
代码中连续用了4个管道命令,这里我在试验中发现,之前学过的扩展型正则表达式,可以把grep -v ‘wtmp’ .bachrc|grep -v ‘^$’写成egrep -v ‘wtmp|^$’ .bachrc,所以在这里,也可以把命令写成扩展型正则表达式,即last -n 5|egrep -v ‘wtmp|^$’|awk ‘{printf $1”\t”$2”\n”}’这个样子。
注:扩展型正则表达式要用egrep。
还有另外一种更为简单的写法如下图
输入last|sed -n ‘1,5p’|awk ‘{printf $1”\t”$2”\n”}’
last指令得到用户登录数据,再通过sed指令对行做处理,仅显示我要的1-5行数据,然后通过awk指令对段做处理,打印出这5行中我要的段。
示例2:$0的应用,将上面的代码last|sed -n ‘1,5p’|awk ‘{printf $1”\t”$2”\n”}’改成last|sed -n ‘1,5p’|awk ‘{printf $0”\n”}’试试看,看看如果把参数改成$0结果怎么样
如图所示,前面提到过$0表示的是一整行的意思,那么为什么所有数据都会被输出呢,这里不得不提到awk的执行机制:
读入第一行,并将第一行的分段数据填入$0,$1,$2,$3…等变量中。
根据条件类型的限制,判断是否需要进行后面的动作。
做完所有的动作和条件类型。
若还有后续的行的数据,重复上面的步骤直到所有数据都读完。
awk以行为一次处理的单位,以分段为最小处理的单位。awk中有一些内建变量,帮助awk直到一份数据有几行,有几段
变量名称 | 代表意义 |
---|---|
NF | 每一行($0)拥有的分段总数 |
NR | 目前awk处理的是第几行数据 |
FS | 目前的分隔字符,默认是空格键 |
示例3:以上面last|sed -n ‘1,5p’的结果为例,列出每一行的账号($1),列出目前处理的行数(NR),并且说明该行有多少分段(NF)
输入last|sed -n ‘1,5p’|awk ‘{printf “user:”$1”\tlines:”NR”\tcolumes:”NF”\n”}’
注:printf指令遵从C语言,要输出的文字和样式要用引号括起来,至于这边为什么用双引号,是因为单引号已经是awk的固定用法了,而指令中的引号必须要成双成对的,所以这边printf指令用双引号,和awk区别开来。
awk的逻辑运算符
运算符 | 代表意义 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
逻辑运算上面的等于,习惯上以“==”来表示,如果是直接给予一个值,例如设置变量,就直接使用“=”。
示例4:查找/etc/passwd档案中第三列小于10的数据,且仅列出账号与第三列。
输入awk ‘{FS=”:”}$3<10{printf $1”\t”$3”\n”}’ /etc/passwd
注:上面的语句中注意几点,awk指令最后要记得带上文件名。条件类型和动作要很清楚,FS=”:”语句属于动作因此要用{}括起来。FS=”:”中冒号要用引号括起来。
上图我们发现,第一行没有按照格式输出,回忆awk指令的执行机制,因为当我们读入第一行时,默认还是用空格符分段的,而我们定义的FS=”:”从第二行起才开始生效。这时可以利用BEGIN这个关键字。如下图所示。
示例5:计算~/pay.txt中每个人的薪资总额并格式化输出。
薪资数据表~/pay.txt数据如图所示
输入
awk ‘NR==1{printf
“%10s\t%3s\t%3s\t%3s\t%5s\n”,$1,$2,$3,$4,”Total”}NR>=2{printf “%10s\t%5i\t%5i\t%5i\t%8.2f\n”,$1,$2,$3,$4,$2+$3+$4}’ pay.txt
上面的语句中,有2个条件类型和2个动作,将打印分成两部分,第一部分是NR==1时,即awk读取第一行,并按指定格式打印第一行(因为第一行和后面几行数据类型是不一样的),第二部分是NR>=2时,即awk读取第二行开始,按指定格式打印后面的数据。
注:这里NR==1一定要用“==”,不能用“=”。
说明:
所有awk的动作,即在{}内的动作,如果需要多个命令辅助时,可以利用分号;间隔,或者直接按Enter键来隔开每个命令。
逻辑运算当中,如果是等于的情况,务必使用2个等号“==”,如上面命令中的NR==1。
格式化输出时,在printf的格式设置中,加上\n来达到换行的目的。
与bash、shell的变量不同,在awk当中,变量可以直接使用,不需要加上$符号
按照上述说明,我们将上面的指令换一种输入方式,也可以达到目的。
上述语句中,我利用了Enter键隔开命令,利用分号;间隔动作中的多个命令,并且定义了变量total=$2+$3+$4,最后在打印该变量的时候没有加上$符号,但执行结果是一样的。
awk的动作中是支持if语句的。所以上面的命令也可以写成这样:
同一个软件的不同版本之间,比较配置文件与源文件的区别,比较常用到文件比较工具。
diff工具以行为单位比较两个文件,一般用在ASCII纯文本文件的比较上,通常是用在同一文件的新旧版本区别上。
首先,我们将/etc/passwd文件处理成一个新版本,处理方式是将第四行删除,第六行则替代为no six line,并保存为/tmp/test/passwd.new。并且把/etc/passwd复制一份命名为/tmp/test/passwd.old
注:一定要先替换后删除,如果先删除第4行,第6行就会变成第5行,再去做替换第6行内容时其实替换的是原档案的第7行。
diff的语法和参数
diff [-bBi] from-file to-file
from-file:原始档名
to-file:目标档名
注:from-file和to-file可以用-代替,从stdin获取
-b:忽略一行中仅有多个空白的诧异,如read me和read me被视为相同。
-B:忽略空白行的差异
-i:忽略大小写的不同
示例1:比较/tmp/test/passwd.old和/tmp/test/passwd.new的差异
如图所示,4d3的意思是,左边第4行对比右边第3行,左边第4行被删掉了,6c5的意思是,左边第6行对比右边第5行,被替换了。<表示左边那行的数据,>表示右边那行的数据。
diff还可以用来对比目录。
示例2:比较/test/和/test.t/目录的差异
/test/目录内容和/test.t/目录内容如图所示
输入diff /test /test.t
cmp工具用途没有diff广泛,主要也是在对比档案,diff工具以行为单位对比,而cmp工具以字节为单位去对比。diff主要用于比较纯文本文件,而cmp主要用于比较二进制文件。(这点鸟哥的书上写的有错误)
cmp的语法和参数
cmp [-sl] file1 file2
-s:将所有不同点的字节处都列出来。不加这个参数默认只列出第一个不同点。
示例1:用cmp比较/tmp/test/passwd.old和/tmp/test/passwd.new
如图,未加-s参数的情况下cmp工具只列出档案第一个不同点。cmd也是可以用来比较纯文本文件的,但若加上参数,比较纯文本文件就不怎么好用了。
示例2:用cmp比较二进制文件/tmp/test/ta和/tmp/test/tb,用参数-l显示不同的字节数和字节值。
这个结果表示。第93个字节在ta档案中的八进制是60,在tb档案中的八进制是61。第160字节在ta档案中的八进制是60,在tb中的八进制是61。
-s参数比较两个档案相同返回0,不同返回1,发生错误返回2。-s参数通常被应用于shell语句中。
patch工具和diff工具的作用是密不可分的。简单来说,patch根据diff对比生成的补丁文件来对旧版本档案做升级或对新版本档案做还原。
如图,passwd.patch就是使用diff工具生成的补丁文件。
接下来,我要用passwd.patch把passwd.old内容更新成和passwd.new一样。
Patch的语法和参数
更新:patch -pN
-R:表示还原,将新版本还原成旧版本
示例1:利用上面生成的passwd.patch补丁文件给passwd.old升级
输入patch -p0 passwd.old passwd.patch
结果如图所示,passwd.old和passwd.new大小一样,内容也一样了。
注:这边要用p0,是因为建立的patch补丁和要更新的目标档案在同一个目录下。
示例2:利用上面生成的passwd.patch补丁文件将passwd.old还原。
输入patch -R -p0 passwd.old passwd.patch