参考:man grep, man sed, man awk, Regular-Expressions.info, One-line scripts for sed, awk guide, awk in bash-scripts, Handy one-line scripts for awk, sed: by Bruce Barnett
grep 是 Linux 中一个用来对文本内容进行搜索和匹配的命令行工具,是 “Global Regular Expression Print” 的简写,顾名思义,grep 与 “Regular Expression” 是密不可分的,学习 grep ,关键就是掌握正则表达式的用法。
grep是按行搜索的,且区分大小写,其格式为:
$ grep ‘需要匹配的字符串’ 文件名
下面我们就用这条命令来练习正则表达式。
正则表达式使用一系列特殊字符构建了一套规则,用于匹配字符串,这些特殊字符称为元字符。
. (一个点): 匹配除换行符之外的任意单个字符$ grep 'r..t' /etc/passwd
r..t可以匹配root, reet, r t等,但不能匹配rat, rot等。
*: 匹配前一个字符0次或任意多次$ grep 'r*t' /etc/passwd
输出含有t, rt, rrt等字符串的行。
“.*”是一个很常用的组合,表示匹配任意次“.”,也就是匹配任意长度的任意字符。
$ grep 'r.*t' /etc/passwd
r.*t表示“字母r+任意长度任意字符+字母t”。
{n,m}: 匹配前一个字符的至少n次,最多m次$ grep 'ro\{2\}t' /etc/passwd
匹配字母’o’两次。注意:花括号{}需要使用转义符。
$ grep 'ro\{2,\}t' /etc/passwd
匹配字母’o’至少两次。
$ grep 'ro\{,2\}t' /etc/passwd
匹配字母’o’最多两次。
^和$: 匹配行首和行尾字符串$ grep '^root.*h$' /etc/passwd
匹配以’root’开头,’h’结尾的行。^$匹配空行。
[ ]: 匹配括号内出现的任意单个字符[ABCD]匹配A、B、C、D任一字母;[A-Z]匹配任意一个大写字母;[^A-D]匹配一个不是大写字母的字符(^在[ ]内时表示取反)。
$ grep '^1[358][0-9]{9}' contact.txt
这条命令可以匹配手机号码。
\: 转义符,用于对特殊符号的“转义”$ grep '022[\ \-][1-9][0-9]{7}' contact.txt
匹配类似”022 87401033”、”022-87401033”这样的电话号码。
注意:”!”在正则表达式中不是特殊符号,不需要转义。
\<和\>: 界定单词的左边界和右边界\<hello匹配以“hello”开头的单词,hello\>匹配以“hello”结尾的单词,\<hello\>可以精确匹配“hello”。
上面列举的这些元字符通用性好,易读易写,配合grep的语法足够满足一般的应用。此外,还有匹配换行符的\n,匹配回车符的\r,匹配制表符的\t,匹配换页符的\f,匹配任意空白字符的\s以及匹配任意非空白字符的\S等等,用法都比较简单。
[TOP]
$ grep [选项] 表达式 [FILE...]
| 选项: | |
|---|---|
--color: |
对匹配的字符串标记颜色显示 |
-i: |
不区分大小写 |
-v: |
只显示没有被匹配到的行 |
-n: |
显示相应的行号 |
-c: |
统计含有匹配字符串的总行数 |
-r, -R: |
递归所有文件夹中的每一个文件 |
-l: |
显示含有匹配字符串的文件名 |
-L: |
显示没有匹配字符串的文件名 |
-H: |
显示匹配字符串及相应的文件名 |
-h: |
不显示字符串对应的文件名 |
-A N: |
显示匹配字符串所在行及后N行 |
-B N: |
显示匹配字符串所在行及前N行 |
-C N: |
显示匹配字符串所在行及前N行和后N行 |
-E: |
使用扩展的正则表达式 |
[TOP]
上文我们介绍的是基本的正则表达式,下面我们再简单了解一下扩展的正则表达式。
?: |
匹配前一个字符0次或1次 |
+: |
匹配前一个字符1次以上 |
|: |
“或” |
( ): |
字符串组,常配合|使用 |
$ grep -E ‘(nologin|false)$’ /etc/passwd
这是显示以”nologin”或”false”结尾的行。
[TOP]
单引号是完全的引用,其中的内容不会做任何处理,完全保持原样输出给grep;双引号是部分的引用,其中的命令、变量会被解析、替换,再与其它内容一并输出给grep;不加引号时,如果字符串中含有空格,输出会不完整,因此最好还是根据需要选用单引号或双引号。
[TOP]
需要匹配汉字时,有时候会涉及到编码问题,如“中文”,查到其utf-8编码和gb2312编码分别是E4B8ADE69687和D6D0CEC4,匹配命令可写作:
grep -rnP "\xE4\xB8\xAD\xE6\x96\x87|\xD6\xD0\xCE\xC4" file
关于汉字编码的知识,请看这里。
[TOP]
和grep一样,sed也是按行处理文件;不同的是,sed命令具有编辑功能,是一种“流编辑器”(stream editor),因此,除了使用正则表达式匹配目标字符,还需要明确编辑类型,并指定编辑位置,这些功能使得sed比grep命令的格式更复杂一些。
对文本的编辑动作,包括替换、插入、删除等,下面我们通过“替换”动作来了解sed命令的操作方法。
s: 文本替换$ sed 's/root/ROOT/' /etc/passwd
这条命令是将/etc/passwd中的“root”替换为“ROOT”,其中s是“替换”命令符,/是分隔符。
$ sed '1,5s/root/ROOT/' /etc/passwd
这是将第2行到第5行中的“root”替换为“ROOT”。需要指出的是,sed默认只替换一行中的第一个匹配项,如果要替换该行所有匹配项,需要在命令后加上g:
$ sed '1,5s/root/ROOT/g' /etc/passwd
如果只替换一行的第二个匹配项,应该这样写:
$ sed '1,5s/root/ROOT/2' /etc/passwd
如果替换一行中第二个和以后的匹配项,应该这样写:
$ sed '1,5s/root/ROOT/2g' /etc/passwd
如果替换一行中第一个和第三个匹配项,可以这样写:
$ sed '1,5s/root/ROOT/1;1,5s/root/ROOT/2' /etc/passwd
也可以这样写:
$ sed -e '1,5s/root/ROOT/1' -e '1,5s/root/ROOT/2' /etc/passwd
这里我们使用了一个选项-e,表示允许表达式有多个执行动作,动作之间用”;”分开。注意:第二个动作附加的是数字2而不是3!这表明多个动作是相继进行的,而不是同时进行的,因此,动作的顺序会影响最终的输出结果。
sed处理后文件后会全部输出到屏幕,这样不方便查看处理结果,有时候我们更需要sed只输出经过处理的行,这时应该使用选项-n。
上面几条命令中,sed仅将处理过的内容输出到屏幕,而不会影响文件本身,如果希望该结果覆盖原文件,应该使用选项-i。
[TOP]
$ sed [选项] 表达式 [FILE...]
| 选项: | |
|---|---|
-e: |
允许表达式有多个动作,动作之间以”;”分开 |
-n: |
只显示经过sed处理过的行 |
-i: |
将sed输出结果保存到原文件 |
-r: |
使用扩展的正则表达式 |
也可以先把动作编写成命令文件,然后调用该文件执行动作,此时需使用选项-f:
$ sed [选项] -f [scriptFILE] [FILE...]
[TOP]
和上文的替换文本动作相比,下面这些动作就比较容易理解了。
i和a: 添加行$ sed '1 i This is my passwd file.' /etc/passwd
i: insert,在匹配项所在行之前添加新行。
$ sed '$ a This is my passwd file.' /etc/passwd
a: append,在匹配项所在行之后添加新行。这里的$表示最后一行。
$ sed '/false/a Attention!\Attention!\Attention!' /etc/passwd
在含有匹配字符“false’的行之后添加新行。
c: 替换行$ sed '/false/c Attention 3 plese!\
Attention 2 plese!\
Attention 1 plese!' /etc/passwd
替换多行时,每行末尾应添加\表示命令尚未结束,上面的i命令和a命令中也有类似的用法。
d: 删除行$ sed '/false/d' /etc/passwd
p: 打印$ sed -n '1,/false/p' /etc/passwd
表示从第一行开始打印到匹配”false”成功的那一行。ps:p命令常与-n选项配合使用。
$ sed -n '/nobody/,/false/p' /etc/passwd
表示从匹配”nobody”成功的行开始打印,到匹配”false”成功的行时结束打印。
[TOP]
$ sed '1 i This is my passwd file.' /etc/passwd
$ sed '1,5s/root/ROOT/' /etc/passwd
$ sed '/false/d' /etc/passwd
$ sed '/root/s/root/ROOT/2' /etc/passwd
可使用正则表达式指定匹配项所在的行。注意:末尾的数字2表示对该行第2个匹配项执行s动作。
$ sed -n '/nobody/,/false/p' /etc/passwd
从含有”nobody”的行开始打印,到含有”false”的行时结束打印。
[TOP]
&: 引用$ sed 's/root/[&]/g' /etc/passwd
把”root”替换成”[root]”,&引用被匹配的字符串。
( ): 多项引用$ sed 's/\(^.*\):x:.*no\(.*\)$/\1:never\2/g' /etc/passwd
( )配合使用\1,\2…可实现多个项目的引用。注意:( )需要使用转义符。
!: 不执行$ sed '1!s/root/[&]/g' /etc/passwd
不执行!后的动作。
q: 退出$ sed '/false/q' /etc/passwd
发现含有”false”的行时,退出sed。
=: 标记行号$ sed '=' /etc/passwd | sed 'N;s/\n/\t/'
给文件加上行号。这里用到了一个新动作的命令N,要理解其动作方法,需要了解sed命令中“Pattern Space”和“Hold Space”的概念。
[TOP]
有时候我们需要合并两行文本,比如,把第1行和第2行合并成1行,而sed只能处理1行文本,怎么办?sed引入了一个能够暂存数据的区域,这个数据缓存区被称为Hold Space (辅缓存区,保持空间);相对的,正在处理数据的区域就被称为Pattern Space (主缓存区,模式空间)了。通常使用的都是主缓存区,而涉及到数据交换的时候,就需要用到辅缓存区了。
引入辅缓存区使sed的功能得到极大的拓展,常用的相关动作命令有:
n: |
复制下一行到pattern |
N: |
追加下一行到pattern,以\n分隔 |
d: |
清空pattern |
D: |
清空pattern中第一个\n之前的内容 |
p: |
打印pattern |
P: |
打印pattern中第一个\n之前的内容 |
g: |
复制hold到pattern |
G: |
追加hold到pattern,以\n分隔 |
h: |
复制pattern到hold |
H: |
追加pattern到hold,以\n分隔 |
x: |
交换pattern和hold |
注:n-next, d-delete, p-print, g-get, h-hold, x-exchange。
$ sed '=' /etc/passwd | sed 'N;s/\n/\t/'
在这条命令中,管道符左侧的命令为文本加上了行号,但是行号是自成一行,因此,我们使用N命令,首先将当前行的下一行追加到主缓存区,然后把换行符\n替换为制表符\t,这样就实现了删除奇数行换行符的功能。
如果需要删除所有换行符,使用sed 'N;s/\n/\t/g'并不能实现预期的效果,简单的,可以使用tr:
$ sed '=' /etc/passwd | tr -t '\n' '\t'
如果使用sed实现删除所有换行符的功能,就需要引入标签跳转功能进行流程控制:
$ sed '=' /etc/passwd | sed ':label;N;s/\n/\t/;b label'
这是一个循环结构,:label是一个标签,b label指示命令跳转到”label”标签,”标签”可以任意命名,且与b之间的空格也不是必需的。注意:标签后面不要有空格。
除了b跳转,还有t跳转和T跳转(b和t分别是branch和test的缩写),如:
$ sed 's/false/FALSE/;t label;s/nologin/nologin:[NotPeople]/;b;:label;s/$/:[ALERT]/' /etc/passwd
如果’s/false/FALSE/’替换成功,则跳转到:label,执行s/$/:[ALERT]/;否则,则按顺序执行s/nologin/nologin:[NotPeople]/,然后就会被无标签的b跳转指示跳过之后的所有指令,完成这一行的处理动作。
注意:b和t之后如果没有指定的标签,则会跳过此后的所有指令,开始下一行的处理任务。
$ sed ':again;$!N;s/\n/\t/;tagain' text.txt
这条命令也可以实现删除所有换行符的功能,这里需要使用t跳转,如果将tagain换成bagain就会陷入死循环。这是因为,t跳转是有条件的,即之前最后一个s///执行成功时跳转,而b则是无条件的,并不受s///执行结果的影响。与此类似,T也是有条件跳转,但与t相反,即当s///执行不成功时才跳转。
$ sed -e :again -e '$!N;s/\n-//;tagain' -e 'P;D' text.txt
[TOP]
grep和sed都是按行处理文本的,如果我们要读取文件的某一列,应该怎么做呢?最简单的方法是用cut。
$ cut -f1,5-7 -d':' /etc/passwd
这条命令指定“:”为分隔符,打印出第1,5,6,7列。cut不能指定空格和Tab作为分隔符,如df -h的输出结果,就无法使用cut进行截取,这时,就需要使用强大的awk了。
AWK是一种编程语言,具有完整的变量赋值、运算与判断、函数调用、流程控制、输入输出等等功能,awk也是与grep、sed并列的一个文本处理工具。
[TOP]
awk认为文件都是格式化的,每一行称为一个记录,一个记录由分隔符分成多个字段,$1,$2…分别表示左起第一个字段、第二个字段…,$0表示全部字段,也就是当前记录、当前行。
awk常用的内建变量有:
$0: |
当前记录,即当前行的全部字段及其分隔符 |
$n: |
当前记录的第n个字段 |
FS: |
字段分隔符,默认是空格和Tab |
NF: |
当前行中字段的个数,即列的个数 |
NR: |
当前行数,如果是多个文件,这是一个累加值 |
FNR: |
当前行数,仅指在当前单个文件中的行数 |
RS: |
记录的分隔符,默认是换行符 |
OFS: |
输出时,字段的分隔符,默认是空格 |
ORS: |
输出时,行的分隔符,默认是换行符 |
FILENAME: |
当前文件名 |
[TOP]
$ df -h | awk '{print $1}'
打印出df -h的第一列。
awk命令是这样的:
$ awk [选项] '条件1 {动作1} 条件2 {动作2} ...' [FILE]
首先是选项,然后是命令语句,命令语句用单引号扩起来,一个条件加一个动作,动作用花括号扩起来。上边的命令中,简单地打印指定的列,所以没有条件参数;awk可以通过管道符接收前一个命令的输出结果。
$ ps aux | awk '$1 !~ /ro*t/ || NR==1 {print NR,$1,$11}' OFS="\t"
$ ps aux | awk '$1 !~ /ro*t/ || NR==1 {printf("%d\t%s\t%s\n", NR,$1,$11)}'
这两条命令的输出结果是一样的,print是awk的内置函数,而printf是Linux的标准格式化输出函数,%d和%s标记了变量的类型分别是十进制整数和字符串,常用的还有%c-单个字符,%o-八进制整数,%x-十六进制整数,%f-浮点数等。$1 !~ /ro*t/ || NR==1是执行条件,$1 !~ /ro*t/ 是正则匹配,与后边的NR==1进行“或”运算。这条命令用到了两个运算符:!~,||。
[TOP]
$ awk -F: '{print $1}' /etc/passwd
也可以指定多个分隔符:
$ awk -F '[;:]'
还可以这样指定分隔符:
$ awk 'BEGIN {FS=":"} {print $1}' /etc/passwd
这是把文件的第一列提取出来。BEGIN是awk的一个特殊的条件表达式,表示在编辑语句{print $1 "\t" $3}开始执行之前,先执行FS=":"来指定分隔符。类似的还有END,表示在所有编辑语句完成之后执行。
为什么要使用BEGIN?如果把BEGIN去掉,可以发现输出结果中的第一行没有按照预期完成截取,这是因为,如果不进行流程控制,awk各个动作是同时执行的。
[TOP]
$ ps aux | awk 'NR!=1 {print > $1}'
这是把指定的行通过重定向输出到新建文件。
$ ps aux | awk '{print $1,$11}' > USERCOMMAND
这是把指定的列通过重定向输出到新建文件。
[TOP]
| 数学: | +,-,*,/,%(余数),^(指数) |
| 赋值: | =,+=,-=,*=,/=,%=,^= |
| 逻辑: | &&,||,! |
| 关系: | >,>=,<,<=,==,!=,~(匹配),!~ |
| 条件: | 判断条件 ? value1:value2 |
运算优先级:
$(字段标记) |
^ |
! |
*,/,% |
+,- |
>,>=,<,<=,==,!= |
~,!~ |
&& |
|| |
?(条件运算符) |
=,+=,-=,*=,/=,%=,^= |
$ ps aux | awk 'NR!=1{a[$1]+=$6;} END { for(i in a) print i ", " a[i]"KB";}'
这条命令中用到了数组的赋值运算,还用到了for循环,awk的功能强大,正是因为它具有完整的变量和流程控制,除此之外,awk还有非常丰富的内置函数,进一步学习这方面的知识,请点击这里。
[TOP]
$ awk [选项] -f [scriptFILE] [FILE...]
awk可以这样调用编写好的命令文件,也像shell脚本一样直接调用,awk脚本文件的格式为:
#!/bin/awk -f
#声明脚本调用方式,注意-f后有一个空格
BEGIN{
#这一行的"{"要紧跟在BEGIN之后,不能换行
#BEGIN用来完成初始工作
}
{
#这里是脚本的主体部分
}
END{
#这一行的"{"要紧跟在END之后,不能换行
#END用来完成变量的恢复工作
}
保存为script.awk,然后加上可执行权限:
$ chmod +x script.awk
这样就可以直接执行脚本了:
$ ./script.awk [FILE]
[TOP]