shell基础 -- grep、sed、awk命令简介
在 shell 编程中,常需要处理文本,这里介绍几个文本处理命令。
一、grep 命令
grep 命令由来已久,用 grep 命令来查找 文本十分方便。在 POSIX 系统上,grep 可以在两种正则表达式风格中选择一种(BRE 和 ERE),或是执行简单的字符串匹配。传统上,有三种程序可以用来查找整个文本文件:
1)grep:最早的文本匹配程序。使用 POSIX 标准定义的基本正则表达(Basic Regular Expression,BRE);
2)egrep:扩展 grep。使用扩展正则表达式(Extended Regular Expression,ERE);
3)fgrep:快速 grep。匹配固定字符串而非正则表达式,它使用优化的算法,能更有效地匹配固定字符串。
在目前的 POSIX 标准中,这三个程序已经被整合成为一个程序 grep,通过对 grep 命令加以不同的选项进行选择控制。
grep 命令由一个选项、一个要匹配的模式和要搜索的文件组成,语法如下:
grep [options] PATTERN [FILES]
如果没有提供文件名,则 grep 命令将搜索标准输入。grep 命令将会根据所提供的模式对文件进行匹配,发现匹配查找模式的行时,将该行显示出来。当 grep 命令同时搜索多个文件时,将会在搜索结果每一行前面加上文件名与一个冒号。grep 命令的主要选项如下:
选项 | 功能 |
-E | 使用扩展正则表达式进行匹配(取代传统的 egrep 命令) |
-F | 使用固定字符串进行匹配(取代传统的 fgrep 命令) |
-c | 输出匹配行的数目,而不是输出匹配的行 |
-h | 取消每个输出行的普通前缀,即匹配查询模式的文件名 |
-i | 忽略大小写 |
-l | 只列出包含匹配行的文件名,而不输出真正的匹配行 |
-v | 对匹配模式取反,即搜索匹配不到的行 |
-n | 输出行号 |
用例子演示一下:
[tongye@localhost ~]$ grep -ni ROOT /etc/passwd 1:root:x:0:0:root:/root:/bin/bash 10:operator:x:11:0:operator:/root:/sbin/nologin
该命令将会在 /etc/passwd 中查找有 root 的行,并将该行显示出来, -n 选项输出行号,-i 选项忽略大小写。
二、sed 命令
sed( stream editor 流编辑器) ,可以用来在管道或者命令序列中编辑数据。sed 的语法如下:
sed option command file
其中,command 是命令部分,用来指示 sed 该执行何种操作,file 则是 sed 命令将要操作的对象,通常是一个文件,如果没有文件,则使用标准输入。option 是 sed 命令可以使用的选项,主要有三个选项: -n、-e、-f,在后面再介绍。
sed 命令读取每一个文件,一次读一行,将读取的行放到内存的一个区域--称为模式空间(pattern space),所有编辑上的操作都会应用到模式空间的内容。当所有操作完成后,sed 命令会将模式空间的最后内容打印到标准输出,再回到开始处,读取另一个输入行。为了演示 sed 命令,笔者写了一小段文本 test.txt 用作试验的素材(英语差,语法问题请忽略)
hello,my name is tongye I want to write a program named HelloWorld.c now,let`s begin #include "stdio.h" main(){ printf("Hello world"); } oh,it`s symple writed by tongye end
2.1 使用 s 参数执行替换操作
sed 命令一个常用的功能是进行替换操作,sed 替换操作的一般格式如下:
sed 's/string1/string2/' file # 将文件中每行的第一个 string1 替换成 string2
在上述语句中,参数 s 表示这是一个替换操作,/ 字符是界定符,用于分隔正则表达式与替代文本。界定符可以是任何可显示的字符,但是 / 字符是最常用的界定符。另外,在处理文件名称时,一般使用分号、冒号或逗号作为界定符。string1 是被替换的文本,可以是正则表达式;string2 是替换文本。
[tongye@localhost Shell_Program]$ sed 's/tongye/ttyezi/' test.txt hello,my name is ttyezi I want to write a program named HelloWorld.c now,let`s begin #include "stdio.h" main(){ printf("Hello world"); } oh,it`s symple writed by ttyezi end
需要注意的是,上述语句只能替换第一个匹配到的文本,若想要将每一个匹配到的文本都替换掉,需要在结尾加上 g 参数(global),即:
sed 's/string1/string2/g' file # 将文件中所有的 string1 都替换成 string2
如果需要删除文本中的一个字符串,可以在替换文本处不放入任何文本(即空)来实现,如下:
sed 's/string1//g' file # 删除文件中所有的 string1
2.2 使用 -e 选项和 -f 选项同时执行多个编辑命令
当 sed 后面需要同时接多个编辑命令的时候,需要使用 -e 选项。每一个编辑命令都使用一个 -e 选项,如:
[tongye@localhost Shell_Program]$ sed -e 's/tongye/ttyezi/g' -e 's/HelloWorld/helloworld/g' test.txt hello,my name is ttyezi I want to write a program named helloworld.c now,let`s begin #include "stdio.h" main(){ printf("Hello world"); } oh,it`s symple writed by ttyezi end
当需要编辑的项目很多时,如果把每一个编辑命令都接到 sed 后面,无疑会让代码很复杂,不易阅读且容易出错。这时,可以将所有的编辑命令都写进一个脚本,再使用 sed 搭配 -f 选项来操作:
# substitute.sed 存放着编辑命令 s/tongye/ttyezi/g s/HelloWorld/helloworld/g s;^\(.\).*\1$;The first letter of this line is the same as its last letter; [tongye@localhost Shell_Program]$ sed -f substitute.sed test.txt hello,my name is ttyezi I want to write a program named helloworld.c The first letter of this line is the same as its last letter #include "stdio.h" main(){ printf("Hello world"); } oh,it`s symple writed by ttyezi end
2.3 使用 -n 选项与 p 参数打印特定的行
sed 默认情况下会把输入的每一行都打印到标准输出上,如果不想输出所有行,可以使用 -n 选项。使用了 -n 选项的 sed 命令将不打印任何行, -n 选项通常与参数 p 配合起来使用,p 参数可以让 sed 命令打印出符合指定范围或模式的所有行:
[tongye@localhost Shell_Program]$ sed -n 's/tongye/ttyezi/p' test.txt hello,my name is ttyezi writed by ttyezi
该语句将文件中的 tongye 替换成 ttyezi,并且只打印发生替换操作的两行;
[tongye@localhost Shell_Program]$ sed -n '1,3p' test.txt # 打印 1 到 3 行 hello,my name is tongye I want to write a program named HelloWorld.c now,let`s begin [tongye@localhost Shell_Program]$ sed -n '4p' test.txt # 只打印第 4 行 #include "stdio.h"
使用 sed -n '1,3p' 来只打印文件的前三行,注意这里的 '1,3' 表示的是一个范围;
[tongye@localhost Shell_Program]$ sed -n '/HelloWorld/p' test.txt I want to write a program named HelloWorld.c
该命令只打印包含 HelloWorld 的行。
2.4 使用 d 参数执行删除操作
要删除某一个特定的行,可以使用参数 d,只需要指定行号或者行范围,就可以从输入中删除指定的行:
[tongye@localhost Shell_Program]$ sed '1,4d' test.txt # 删除第 1 到 4 行,然后将剩余的行打印到标准输出 main(){ printf("Hello world"); } oh,it`s symple writed by tongye end
sed 也可以使用参数 d 来删除符合匹配模式的行:
[tongye@localhost Shell_Program]$ sed '/tongye/d' test.txt # 删除所有含有 tongye 的行 I want to write a program named HelloWorld.c now,let`s begin #include "stdio.h" main(){ printf("Hello world"); } oh,it`s symple end
2.5 使用 -i 选项或输出重定向来保存 sed 编辑的内容
在上面的所有操作中,我们发现虽然 sed 操作确实完成了,输出到标准输出上的文本内容确实发生了变化,但是如果我们再次打开所编辑的文件,会发现文件内容并没有发生更改。如果需要保存 sed 编辑的内容,可以使用 -i 选项:
[tongye@localhost Shell_Program]$ sed -i 's/tongye/ttyezi/' test.txt [tongye@localhost Shell_Program]$ cat test.txt hello,my name is ttyezi I want to write a program named HelloWorld.c now,let`s begin #include "stdio.h" main(){ printf("Hello world"); } oh,it`s symple writed by ttyezi end
也可以使用输出重定向的方式,将编辑后的内容保存到一个新的文本文件中而不是输出到标准输出:
[tongye@localhost Shell_Program]$ sed 's/hello/HELLO/' test.txt > test1.txt [tongye@localhost Shell_Program]$ cat test1.txt HELLO,my name is ttyezi I want to write a program named HelloWorld.c now,let`s begin #include "stdio.h" main(){ printf("Hello world"); } oh,it`s symple writed by ttyezi end
三、awk 命令
3.1 awk 的基本模式与操作
shell 中提供 awk 命令来重新编排字段。实际上, awk 本身所提供的功能十分完备,已经是一种很好用的程序语言了,这里暂且只讨论它在 shell 脚本中的一些长处:文本处理功能。awk 命令的基本模式如下:
awk 'program' [ file ...]
awk 读取命令行上所指定的各个文件(若没有文件,则为标准输入),一次读取一条记录(行),再针对每一行,应用 program 所指定的命令。 awk程序(program)基本架构为:
pattern {action}
pattern {action}
...
pattern 部分几乎可以是任何表达式,但是在单命令行程序里,它通常是由斜杠括起来的 ERE。 action 为任意的 awk 语句,但是在单命令行程序里,通常是一个直接明了的 print 语句。pattern 或 action 都可以省略(不要全部省略)。省略 pattern,则会对每一条输入记录执行 action;省略 action 则默认 action 为{ print } ,将打印显示整条记录。
[tongye@localhost etc]$ awk '/root/ {print}' /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin
上述指令将打印文本中所有包含 root 的行。
3.2 字段
awk 设计的重点就在字段与记录上:awk 读取输入记录(通常是一些行),然后自动将各个记录切分为字段。awk 将每条记录内的字段数目,存储到内建变量 NF 中。awk 默认以空白分隔字段(如空格符、制表符),不过,也可以设置成其他的,通过设置 FS 变量(列数据分隔符)。如果需要使用字段值,可以使用 $ 字符来引用,$1表示第一个字段值、$2表示第二个字段值、... 另外,$0表示整条记录。举个例子验证一下:
[tongye@localhost Shell_Program]$ awk '{print $1}' test.txt hello,my I now,let`s #include main(){ printf("Hello } oh,it`s writed end
这句指令将会按照默认的分隔符(空白符)来对字段进行划分,并打印第一个字段的值到标准输出。
3.3 设置字段分隔符
可以使用 -F 选项来修改字段分隔符。-F 选项会自动的设置 FS 变量,只需将 FS 变量放到 -F 选项后面即可。FS 变量可以被设置为单个字符(此时,只要该字符出现一次,就分隔出一个字段),也可以被设置为一个完整的 ERE(此时,每一个匹配该 ERE 的文本都将被视为字段分隔符):
awk -F: '{print $1,$2}' /etc/passwd
这段指令将使用冒号 : 作为字段分隔符去处理 /etc/passwd 文件,然后输出文件的第1、第2个字段到标准输出。
-F 选项设置的字段分隔符是相对于输入而言的。而 awk 的输入、输出分隔符用法是分开的,因此即使使用 -F 选项设置了 FS 变量,输出的字段分隔符还是默认的空白符,这样可能会影响结果判断,使用 -v 选项可以设置输出字段分隔符,通过改变 OFS 变量(列数据输出分隔符)的值,使用形式也 -F 选项有所区别:
awk -v 'OFS=/' '{print $1,$2}' /etc/passwd
-v 选项的用法如上,该指令的将把 awk 的输出与分隔符设置为斜杠符 / 。举一个例子:
[tongye@localhost Shell_Program]$ awk -F '/..' -v 'OFS=/' '{ print $1,$2,$3 }' /etc/passwd root:x:0:0:root:/ot:/n bin:x:1:1:bin:/n:/in daemon:x:2:2:daemon:/in:/in adm:x:3:4:adm:/r/m: lp:x:4:7:lp:/r/ool sync:x:5:0:sync:/in:/n shutdown:x:6:0:shutdown:/in:/in halt:x:7:0:halt:/in:/in mail:x:8:12:mail:/r/ool operator:x:11:0:operator:/ot:/in games:x:12:100:games:/r/mes: ftp:x:14:50:FTP User:/r/p: nobody:x:99:99:Nobody:/sbin/login systemd-network:x:192:192:systemd Network Management:/sbin/login dbus:x:81:81:System message bus:/sbin/login polkitd:x:999:998:User for polkitd:/sbin/login tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/v/ll: abrt:x:173:173::/c/rt: sshd:x:74:74:Privilege-separated SSH:/r/pty postfix:x:89:89::/r/ool chrony:x:998:996::/r/b tongye:x:1000:1000:tongye:/me/ngye:
这段指令使用正则表达式 /.. 作为段分隔符去处理 /etc/passwd 文件,文件中每个匹配该正则表达式的文本都被视为一个字段分隔符。然后使用斜杠符 / 作为输出字段分隔符,并将第1、2、3个字段输出到标准输出。
3.4 打印行 print 与 printf
print 上面已经用到过,这是 awk 里面最常使用的一条语句,可以用来进行简单的打印工作。print 的参数可以是字段列表、变量或者字符串:
[tongye@localhost Shell_Program]$ awk -F: -v 'OFS=:' '{print "username is",$1}' /etc/passwd
print 命令将后面的参数一个一个打印到标准输出,如果没有后接参数,则默认参数为 $0,将打印整条记录。注意,print 的参数之间需要用逗号隔开,否则输出结果将会连到一起没有间隔。
对于上面的语句,print 后面的参数混合了字符串和变量,当参数较多时,写起来会比较不方便。此时,可以使用 printf 语句来替代 print 语句。printf 语句可以将所有参数放到一对双引号中去,与 C 中的 printf 用法类似:
[tongye@localhost Shell_Program]$ awk -F: '{printf "username is %s\n",$1}' /etc/passwd username is root username is bin username is daemon username is adm username is lp username is sync username is shutdown username is halt username is mail username is operator username is games username is ftp username is nobody username is systemd-network username is dbus username is polkitd username is tss username is abrt username is sshd username is postfix username is chrony username is tongye
需要注意的是,awk 的 print 语句会自动提供换行符,而 printf 语句不能,需要自己提供 \n 来进行换行。
参考资料:
《Linux 程序设计 第四版》
《Shell 脚本学习指南》
《UNIX/Linux/OS X 中的 Shell 编程 第四版》