参考: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]