grep 、sed、awk被称为linux中的'三剑客'。它们对文本处理和操作非常灵活和强大,其中,grep更适合单纯的查找或匹配文本,sed 更适合编辑匹配到的文本,而awk 更适合格式化文本,对文本进行较复杂格式处理,下面将进行一一讲述。awk是一种可以处理数据、产生格式化报表的语言,功能相当强大。awk的工作方式是读取数据文件,将每一行数据视为一条记录(record),每笔记录以字段分隔符分成若干字段,然后输出各个字段的值。awk擅长文本格式化,并且将格式化以后的文本输出,而对于文本的处理,awk是逐行处理的,逐行处理的意思就是说,当awk处理一个文本时,会一行一行进行处理,处理完当前行,再处理下一行,awk默认以'换行符'为标记,识别每一行,也就是说,awk每次遇到'回车换行',就认为是当前行的结束,新的一行的开始,awk会按照用户指定的分割符去分割当前行,如果没有指定分割符,默认使用空格作为分隔符。可以看到,$0表示显示整行,$NF表示当前行分割后的最后一列($0和NF均为内置变量)注意,$NF和NF要表达的意思是不一样的,对于awk来说,$NF表示最后一个字段,NF表示当前行被分隔符切开以后,一共有几个字段。也就是说,假如一行文本被空格分成了7段,那么NF的值就是7,$NF的值就是$7, 而$7表示当前行的第7个字段,也就是最后一列,那么每行的倒数第二列可以写为($NF-1)。awk只能处理结构化的文件,因此对输入文件要求是有规则和结构化的,awk将每个输入文件行定义为记录,行中的每个字符串定义为域,域之间用空格、Tab键或其他符号进行分割,分割域的符号就叫分隔符。这是文本中的一行内容,如果以空格符作为分隔符的话,那么这行内容有Li和“Hao 010-88181010”两个域。同理,如果以tab键作为分隔符的话,那么这行内容有“Li Hao”和“010-88181010”两个域。下面来看一个具体的例子:以下是执行 ps -ef 命令的输出片段: UID PID PPID C STIME TTY TIME CMD root 1 0 0 2016 ? 00:00:05 init [3] …..省略 如果要获取ps命令输出中所有进程的pid,那么仅用以下单一指令,就可轻松取得所有进程的PID:
[root@controller1 ~]# ps -ef | awk '{print $2}' awk对每一条记录,都会套用一个“条件类型 {操作动作}”,如果该行符合条件,就执行指定的操作。条件类型或操作动作之一,可以省略。如果只有条件类型,表示要显示符合条件的数据行;如果只有操作动作,表示对每一数据行都执行该动作操作。awk “条件类型” 文件:把符合条件类型的数据行显示出来。
awk '{操作动作}' 文件:对每一行都执行{}中的操作。 awk '条件类型{操作动作}' 文件:对符合条件类型的数据行,执行{}中的操作。 awk '条件类型1{操作动作1} 条件类型2{操作动作2} ...' 文件
1)读入第一行,并将第一行的数据填入 $0, $1, $2.... 等变量当中;
2)依据 '条件类型' 的限制,判断是否需要进行后面的 '动作'; 3)做完所有的动作与条件类型; 4)若还有后续的『行』的数据,则重复上面1~3的步骤,直到所有的数据都读完为止。 注意:awk是以“行”为一次处理的单位, 而以域为最小的处理单位。2、awk '{ print $1, $2 }' dataf3 显示 dataf3 每一行的第 1 和第 2 个字段。$1 代表第 1 个字段,$2 代表第二字段,其他类推。3、awk '/iivey/{ print $1, $2 }' dataf3 将含有iivey关键词的数据行的第 1 及第 2 个字段显示出来。4、awk -F: '/^iivey/{ print $3, $4 }' /etc/passwd 使用选项-F,指定:为分隔字符,账号iivey的uid(第 3 字段)及 gid(第 4 字段)显示出来。5、awk -F: 'BEGIN{OFS='+++'}/^root/{ print $1, $2, $3, $4, $5 }' /etc/passwd 以:为分隔字符,+++为输出字段分隔符,将账号root的第1~5栏显示出来。执行结果:本例中,BEGIN{}区域指示 awk 一开始先做初始化的操作,即设定 OFS='+++'。变量OFS 的作用是存储输出字段的分隔符。接着,寻找root账号所在行,找到后,使用 print 印出第 1 ~ 第 5个字段,且彼此用 +++ 隔开。ifconfig | grep 'inet addr:' | grep Bcast | awk '{print $2}' | awk -F: '{print $2}' 注意这个组合命令的写法,以及awk在里面起到的作用。cat /proc/net/dev | awk -F: '/eth.:|em.:|wlan.:/{print $1}' 在本例中,-F:把分隔字符设为:,而且,采用多选一的样式 /eth.:|ppp.:|wlan.:/。这个样式的意思是:设备名称可以是 eth0:、ppp1:、wlan1: 这 3 个其中之一。一旦找到符合样式的字符串后,去掉:,取其中的第一个域值,因此,可能的答案是 eth0 或 em或 wlan1。cat /proc/meminfo | awk '/MemTotal/{print $2}' 其中,/proc/meminfo 记载主机内存相关数据,其中 MemTotal 为内存大小,其样本值如下:因此,在 awk 的样式语法中,利用/MemTotal/ 找到这一行,再印出第二个字段,即可得到内存的大小。awk定义了很多内建变量用于设置环境信息,我们称它们为系统变量,这些系统变量可分为两种:第2种用于定义系统值,在处理文本时可以读取这些系统值,如记录中的域数量、当前记录数、当前文件名等,awk动态改变第2种系统变量的值。其中,“$n”表示记录的字段。比如,$1表示第1个字段,$2表示第2个字段,如此类推。而$0比较特殊,表示整个当前行。此外,awk还可以使用关系、布尔运算符、表达式等,含义如下所示:awk 后续的所有动作是以单引号『 ' 』括起来的,由于单引号与双引号都必须是成对的, 所以,awk的格式内容如果想要以print打印时,注意,非变量的文字部分,都需要使用双引号来定义出来,因为单引号已经是awk的固定用法了。[root@localhost ~]# last -n 5| awk '{print $1 '\t lines: ' NR '\t columns: ' NF}' root lines: 1 columns: 10 root lines: 2 columns: 10 root lines: 3 columns: 10 root lines: 4 columns: 10 root lines: 5 columns: 10 lines: 6 columns: 0 wtmp lines: 7 columns: 7 这个例子是获取最后登录系统的5条记录,然后以默认的空格键作为域分隔符,打印每条输出内容中域的个数,重点注意NF和NR的作用。同时,还需要注意,在awk内的 NR, NF 等变量要用大写,且不需要有$符号。
[root@localhost ~]# cat /etc/passwd |awk ' BEGIN{FS=':'} $3 < 10 {print $1 '\t ' $3}' root 0 bin 1 daemon 2 adm 3 lp 4 sync 5 shutdown 6 halt 7 mail 8 这个例子,是以“:”为域分隔符,读取/etc/passwd文件,然后判断第三列的值小于10的列有多少,最后打印出满足条件的第一列和第三列内容。这个例子中用到了算数判断,还用到了BEGIN,它表示在文件开始扫描前进行的操作,也就是在扫描/etc/passwd文件之前,将FS赋值为“:”,后面会专门再介绍下BEGIN和END。[root@localhost ~]# cat /etc/passwd |awk -F ':' ' $3 < 10 {print $1 '\t ' $3 }' root 0 bin 1 daemon 2 adm 3 lp 4 sync 5 shutdown 6 halt 7 mail 89. 这个例子和上面那个例子类似,只不过,这里是通过“-F”来指定域分隔符,其实,“-F”实际上就是设置FS,功能类似,但是写法不同,使用“-F”参数后,判断条件“$3 < 10”需要和后面的print组合在一起加单引号输出。[root@localhost ~]# cat /etc/passwd |awk 'BEGIN{FS=':'} NR==6{print $1 '\t ' $3}' sync 5 这个例子,仍然是读取/etc/passwd文件,然后判断的条件是“NR==6”,其中,NR表示当前记录数,也就是读到那行了,这里判断就是输出/etc/passwd文件第六行中第一个和第三个字段的内容。注意NR和NF的功能和区别。[root@localhost ~]# cat /etc/passwd |awk 'BEGIN{FS=':'} NR<=4{print $0}' 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 这个例子跟上面那个基本一样,不同之处是判断条件为“NR<=4”,也就是取/etc/passwd文件前四行文件的内容。最后的print输出的是“$0”,这个表示/etc/passwd文件中所有域的内容,也就是每行的内容全部输出。awk中两个特别的表达式,BEGIN和END,这两者都可用于pattern中(参考前面的awk语法),提供BEGIN和END的作用是给程序赋予初始状态和在程序结束之后执行一些扫尾的工作。任何在BEGIN之后列出的操作(在{}内)将在awk开始扫描输入之前执行,而END之后列出的操作将在扫描完全输入之后执行。简单来说,BEGIN指定了处理文本之前需要执行的操作,而END指定了处理完所有行之后所需要执行的操作。[root@localhost ~]#echo abc > test1.txt
[root@localhost ~]#awk 'BEGIN{ print 'BEGIN' } BEGIN 在上面例子中,我们并没有给定任何输入来源,awk就直接输出信息了,因为,BEGIN模式表示,在处理指定的文本之前,需要先执行BEGIN模式中指定的动作,而上述示例没有给定任何输入源,但是awk还是会先执行BEGIN模式指定的'打印'动作,打印完成后,发现并没有文本可以处理,于是就只完成了'打印 BEGIN'的操作。[root@localhost ~]#awk 'BEGIN{ print 'BEGIN' } {print $0}' test1.txt BEGIN abc 可以看到,虽然指定了test1.txt文件作为输入源,但是在开始处理test1.txt文本之前,需要先执行BEGIN模式指定的'打印'操作,然后才执行后面的“{print $0}”操作。[root@localhost ~]#awk 'BEGIN{ print 'BEGIN' } {print $0} END{print 'END'}' test1.txt BEGIN abc END 上面这个示例中返回的结果,就像一张'报表',有'表头' 、“表内容”、 “表尾”。我们通常将变量初始化语句(如 var=0 )以及打印文件头部的语句放入BEGIN语句块中。在 END{}语句块中,往往会放入打印结果等语句。最后,我们将BEGIN和END联合使用,看一个例子:[root@localhost ~]# cat bb.txt 一:60个:300.00 二:70个:400.00 三:80个:500.00
[root@localhost ~]# awk 'BEGIN { FS=':';print '统计销售金额';total=0} {print $3;total=total+$3} END{printf '销售金额总计:%.2f',total}' bb.txt 统计销售金额 300.00 400.00 500.00 销售金额总计:1200.00 sed 是一种非交互式的流编辑器,可动态编辑文件。所谓非交互式是说,sed 和传统的文本编辑器不同,并非和使用者直接互动,sed处理的对象是文件的数据流(称为 stream/流)。sed 的工作模式是,比对每一数据行,若符合样式,就执行指定的操作。基础用法如下: -n :使用安静(silent)模式。在一般 sed的用法中,所有来自STDIN的数据一般都会被列出到屏幕上。但如果加上-n参数后,则只有经过sed特殊处理的那一行(或者动作)才会被列出来。 -e :直接在命令列模式上进行sed的动作编辑; -f :直接将sed的动作写在一个文件内,-f filename则可以运行filename内的sed动作; -r :sed的动作支持的是扩展型正则表示法的语法。(默认是基础正则表示法语法) -i :直接修改读取的文件内容,而不是由屏幕输出。n1, n2 :非必须项,一般代表“选择进行操作的行数”,举例来说,如果我的操作是需要在10到20行之间进行的,则“10,20”为动作行为。 d:表示删除,因为是删除啊,所以 d 后面通常不接; i:表示插入,i的后面可以接字串,而这些字串会在新的一行出现(目前的上一行); p:表示打印,将某个选择的数据打印出来。通常p会与参数sed -n一起运行~ s:表示取代,可以直接进行替换,通常这s的动作可以搭配正则,例如 1,20s/old/new/gsed命令众所周知的一个用法是进行文本替换,这个使用量最大,来看一个例子:[root@localhost ~]# echo 'hello 123 abcd 666 linux' >sed.txt [root@localhost ~]# cat sed.txt hello 123 abcd 666 linux 然后通过sed替换sed.txt文本中的数字为空格,并打印出来:[root@localhost ~]# sed 's/[0-9]\+/ /' sed.txt hello abcd 666 linux 可以看到,输出结果中,第一个数字123被替换为空格了,但后面这个666数字没有替换,也就是说sed命令会将每一行中第一处符合模式的内容替换掉,那么要替换所有的话,但是如果要替换所有内容,需要在命令尾部加上参数“g”,看下面命令:[root@localhost ~]# sed 's/[0-9]\+/ /g' sed.txt hello abcd linux [root@localhost ~]# 这样,sed.txt 文件中所有行的数字就都替换为了空格。后缀“/g”意味着sed会替换每一处匹配。但是有时候我们只需要从第n处匹配开始替换。对此,可以使用“/Ng”选项,表示从第N处开始替换,例如:[root@localhost ~]# sed 's/[0-9]\+/ /2g' sed.txt hello 123 abcd linux sed经常使用一对//,表示寻找之意,这里的“/”在sed中被作为定界符使用,其实,我们可以像下面一样使用任意的定界符:sed 's:text:replace:g’ sed 's|text|replace|g’ 第一个是使用':'作为定界符,第二个是使用“|”作为定界符,当定界符出现在样式内部时,必须用前缀“\”对它进行转义,例如:[root@localhost ~]# echo 'he|lloworld' | sed 's|he\|llo|love |g' love world 这个功能使用sed频率最高,空白行可以用正则表达式“^$”进行匹配,在空白行中,行尾标记紧随着行首标记,例如:[root@localhost ~]# cat sed1.txt abc 123 dft
def 456 ccy
ghi 789 ddn [root@localhost ~]# sed '/^$/d' sed1.txt abc 123 dft def 456 ccy ghi 789 ddn 从输出可以看出, sed1.txt文件中的空白行都已经进行了删除。sed表达式通常用单引号来引用。不过也可以使用双引号。双引号会通过对表达式求值来对其进行扩展。当我们想在sed表达式中使用一些变量时,双引号就能派上用场了,例如:[root@localhost ~]# text=hello [root@localhost ~]# echo 123 world | sed 's/[0-9]\+/$text/' hello world
[root@localhost ~]# echo 'one TWO ' | sed 's/\([a-z]\+\) \([A-Z]\+\)/\2 \1/' TWO one 当模式被包括在使用斜线转义过的()中时,对于匹配到的第一个子串,其对应的标记是“\1”,匹配到的第二个子串是“\2”,这里([a-z]+) 匹配第一个单词one, ([A-Z]+) 匹配第二个单词TWO。\1对应的是one,“\2”对应的是TWO,后面可以直接用来引用它们。这种引用被称为向后引用。在sed后面的替换部分,可以看到次序被更改为“\2”和“\1” ,因此输出结果就是逆序的形式。当需要多个条件的时候,sed支持多个表达式,例如:[root@localhost ~]# echo abc | sed 's/a/A/' | sed 's/c/C/' AbC
[root@localhost ~]# echo aa22b11c | sed -e 's/a/A/' -e 's/c/C/' Aa22b11C sed 并不会更改文件内容。sed的工作方式是读取文件内容,经流编辑之后,把结果显示到标准输出。因此,如果想要存储sed 的处理结果,需要通过重定向输出将结果存成其他文件。[root@localhost ~]# sed '1,4d' dataf1 把第 1 到第 4 行数据删除,剩下的显示出来。d是sed的删除命令。[root@localhost ~]# sed '/La/d' dataf3 上面命令表示把含有La的行删除,剩下的显示出来。其中,“//”代表定界符。[root@localhost ~]# sed '/[0-9]\{3\}/d' dataf3 上面命令表示把含有“3 位数”的行删除,剩下的显示出来。在样式[0-9]{3}中,{3} 表/ /要寻找的是3个数字组成的字符串。[root@localhost ~]# sed '/^$/d' dataf5 上面命令表示删除 dataf5 的空白行。^ 表开头,$ 表尾部,这两者之间没有任何字符,代表该行是一空白行。[root@localhost ~]# sed '/La/!d' dataf3 上面命令表示把不含有La的行删除,剩下的显示出来。这里的!是否定的意思,表示不符合样式者。[root@localhost ~]# sed '/La/p' dataf3 上面命令表示把含有La的行显示出来。其中,p是sed的命令,它会把目前的数据显示出来,但因为sed默认也会显示不符合的数据行,所以,应改用以下指令:[root@localhost ~]# sed -n '/La/p' dataf3
[root@localhost ~]# sed -n 's/La/Oo/p' dataf3 这里的“s”是取代的意思,第一对//中含括的字符串(La)是搜索的目标,第二对//中是替换的字符串(Oo)。它会把dataf3文件中数据行中的字符串La换成Oo。请注意:上面这个指令,只会更换第一个出现的 La 而已,如要全部置换,应再加上全局的命令g,如下所示:[root@localhost ~]# sed -n 's/La/Oo/gp' dataf3
[root@localhost ~]# sed -n 's/La//p' dataf3 上面命令表示把每一行第一个出现的La删除(把La置换成空字符串,就是删除)。[root@localhost ~]# sed 's/^...//' dataf3
[root@localhost ~]# sed 's/...$//' dataf3
[root@localhost ~]# sed -n 's/\(La\)/\1Oo/p' dataf3 上面命令表示把找到的La存起来,用\1 取回来再使用。这个指令作用的结果:若数据行含有La字符串,则第一个出现的La会置换成LaOo,然后再显示这些含有La的数据行。[root@localhost ~]#sed -n '/AAA/s/234/567/p' dataf3 上面命令表示找到含有AAA的那一行之后,将234换成567。[root@localhost ~]# sed -n '/AAA/,/DDD/s/B/567/p' dataf3 上面命令表示将含有AAA到含有DDD的那几行,将其中的B换成 567。[root@localhost ~]# sed -n '2,4s/B/567/p' dataf3 上面命令表示由第2行到第4行,皆将其中的B换成567。由以上的说明可知:sed动态编辑的威力是相当强大的,它补足了 Bash 在修改文件方面能力的不足。grep 命令作为Unix中用于文本搜索的神奇工具,能够接受正则表达式,生成各种格式的输出。grep的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到标准输出,不影响原文件内容。[root@localhost ~]# grep root /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin 这就只显示了/etc/passwd文件中包含root的所有行。[root@localhost ~]# echo -e 'this is a word\nnext line' this is a word next line
[root@localhost ~]# echo -e 'this is a word\nnext line' | grep word this is a word grep可以使用“-E”选项,这意味着使用扩展正则表达式,或者也可以使用默认允许正则表达式的grep命令:egrep,也就是说:“grep -E”等价于“egrep”。[root@localhost ~]# cat nn.txt this is a test regular expression 1 2 3 4 5 last line [root@localhost ~]# grep -E [a-z] nn.txt this is a test regular expression last line [root@localhost ~]# egrep [a-z] nn.txt this is a test regular expression last line 可以看到,grep -E”和“egrep”实现的功能是一样的。都仅仅输出了英文字符,数字被过滤掉了。[root@localhost ~]# echo 'iivey linux zabbix mysql' >test1.txt [root@localhost ~]# echo 'nagios server redis linux hadoop' >test2.txt [root@localhost ~]# grep -l linux test1.txt test2.txt test1.txt test2.txt [root@localhost ~]# grep -l zabbix test1.txt test2.txt test1.txt [root@localhost ~]# grep -L zabbix test1.txt test2.txt test2.txt 从输出可知,“-l”选项用来返回匹配字符串对应的文件列表,test1.txt和test2.txt文件中都包含了linux这个字符串,所以都输出了,而zabbix仅在test1.txt中,最后,还有一个和“-l”相反的选项是“-L” ,它会返回一个不匹配的文件列表。xargs命令通常用于将文件名列表作为命令行参数提供给其他命令。当文件名用作命令行参数时,建议用'\0'值字节作为文件名终止符,而非空格。因为一些文件名中会包含空格字符,一旦它被误解为终结符,那么单个文件名就会被认为是两个文件名(例如,my file.txt被解析成my和file.txt两个文件名)。这个问题可以利用'\0'后缀来避免。我们使用xargs以便从诸如grep 、find中接收stdin文本。这些命令可以将带有'\0'后缀的文本输出到stdout 。为了指明输入的文件名是以'\0' 作为终止符,需要在 xargs中使用'-0'。[root@localhost ~]#echo 'linux' >file1 [root@localhost ~]#echo 'server' >file2 [root@localhost ~]#echo 'linux' >file3 下面这个命令是在file开头的文件中查找含有linux字符的文件,并删除:[root@localhost ~]#grep 'linux' file* -lZ| xargs -0 rm grep可用于shell脚本,因为grep通过返回一个状态值来说明搜索的状态,如果搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。我们利用这些返回值就可进行一些自动化的文本处理工作。#!/bin/bash # 文件名: greptest.sh # 用途: 测试文件是否包含特定的文本内容 if [ $# -ne 2 ]; #如果执行搅拌给的参数不够 就提示执行语法 then echo 'Usage: $0 content_text filename' exit 1 fi content_text=$1 filename=$2
grep -q '$content_text' $filename
if [ $? -eq 0 ]; then echo 'The text exists in the file' else echo 'text does not exist in the file' fi 这个脚本中,“$?”用于获取“grep -q '$content_text' $filename”返回的状态,如果这个grep命令执行成功,返回值为0,否则为1或2,然后通过返回状态值进行下面的if判断。其中,$1和$2是要求我们输入的参数,$1是匹配的内容,$2是指定从哪个文件中查找匹配的内容。将上面脚本保存为greptest.sh,然后执行如下操作:[root@localhost ~]# cat test1.txt iivey linux zabbix mysql [root@localhost ~]# sh greptest.sh linux test1.txt The text exists in the file [root@localhost ~]# sh greptest.sh redis test1.txt text does not exist in the file -----------------------------------
|