LInux文本处理三剑客之grep的用法
为什么要学习 Shell 脚本语言?
现代的互联网架构底层系统几乎都是基于 Linux 操作系统构建的,Linux 的核心价值在于提供了强大的系统内核功能进行文件管理和信息交互管理。
而 Shell 则是软件研发人员高效控制和使用 Linux 的工具和桥梁。Shell 本身是 C 语言编写的系统软件,通常也叫命令行工具。它具有一个基础的界面,用户在这个界面通过 Shell 脚本语言(Shell Script)来访问 Linux 操作系统内核服务。
在科幻电影里,我们经常看到在暗色调的屏幕上 Shell 脚本代码在快速滚动,这简直成为了 Geek/Hacker 的一个形象标签。实际工作中,Shell 也备受开发、运维、测试人员甚至运营人员的青睐,几乎是 IT 技术人员的必备技能。
而在软件测试领域,Shell 脚本编程作为自动化测试技术的基石,是测试开发工程师必须熟练掌握的技能。
Tips:尽管通常我们把「Shell」和「Shell 脚本语言」都叫做 “Shell”,但其实这两者是有本质区别的。
Shell 脚本语言编程有哪些优势?
Shell 脚本语言的优势在于能够以轻量级、最快捷的速度处理 Linux 操作系统偏底层的业务。比如软件的自动化安装、更新版本、监控报警、日志分析等。虽然其他高级编程语言如 PHP、Python、Ruby 等语言也能做到,但是效率和开发成本上会大打折扣,所谓“杀鸡用牛刀”,有点得不偿失。
成熟的技术人会摒弃华而不实的方法,根据不同的场景选择最合适的工具去解决问题,朴实但高效。比如本文着重介绍的 Linux 三剑客:grep、awk 和 sed 就是 Linux 文本处理问题的最高效工具。
下面,我们将依次介绍 Linux 文本处理三剑客的基础语法,使用场景和特性,以及给出对应的实战演练题目。
Shell 编程环境
1. Windows 用户,建议安装 Git Bash 软件。
2. Mac 用户,建议安装 iterm2 软件。
3. ssh 工具
- 霍格沃兹测试学院学员用自己的帐号登录
- `ssh 手机号后8位@http://shell.testing-studio.com`
- 没有 ssh 账号的可以临时用
- `ssh hogwarts2019@shell.testing-studio.com`
4. 演练文档:
http://testerhome.com/tmp/nginx.log 保存了一份一天的 Nginx 访问 log。
Linux 三剑客介绍
grep
grep 示例 Shell 脚本代码
ps -ef | grep bash
echo "ABC" | grep -i abc
ps -ef | grep bash | grep -v grep
echo "1234 7654" | grep -o "[0-9]4"
echo "1234 7654" | grep -oE "[0-9]4|76"
grep 实战演练题目
- 找出 nginx.log 中所有 404 和 503 报错的 log 数据,取出前 3 条数据,把命令贴到回复里。
- 找出 testerhome 首页的所有 http 和 https 的链接。
awk
awk 示例 Shell 脚本代码
ps | awk 'BEGIN{print "start"}{print $0}END{print "end"}'
awk '/ 404 | 500 /' /tmp/nginx.log
echo '1
2
3
4
5' | awk '/2/,/4/'
echo '1
2
3
4
5' | awk '$0>3'
ps | awk 'NR>1'
ps | awk '{print $NF}'
echo $PATH | awk 'BEGIN{RS=":"}{print $0}' | grep -v "^$" | awk 'BEGIN{FS="\n";ORS=":"}{print $0}END{printf "\n" }'
echo '1,10
2,20
3,30' | awk 'BEGIN{a=0;FS=","}{a+=$2}END{print a,a/NR}'
awk 'BEGIN{print 33*20*76/200/3}'
echo "123|456_789" | awk 'BEGIN{FS="\\||_"}{print $2}'
echo "123|456_789" | awk "BEGIN{FS=\"\\\\||_\"}{print \$2}" #尽量使用单引号
awk 实战演练题目
- 找出 404 和 500 的数据,只打印状态码这一列,然后排序去重。把命令贴到回复里
- 去 testerhome 首页找到所有的 http 的连接,然后打印不带 http 的纯域名部分
sed
pattern表达式
- 20 30,35 行数与行数范围
- /pattern/ 正则匹配
- //,// 正则匹配的区间
action
- d 删除
- p 打印,通畅结合-n参数
- s/REGEXP/REPLACEMENT/[FLAGS]
- 替换时引用 \1 \2 匹配的字段
sed 示例 Shell 脚本代码
ps | sed -n 1,3p
ps | sed 's/CMD/command/'
ps | sed -n '/ps/p'
echo '1
2
3
4
5' | sed -n '/3/,/4/p'
echo '1
2
3
4
5' | sed '/3/,/4/d'
ps | sed -e 's/CMD/command/' -e 's#00#20#g'
grep
作为linux中最为常用的三大文本(awk,sed,grep)处理工具之一,掌握好其用法是很有必要的。
grep命令的常用格式为:grep [选项] ”模式“ [文件]
grep家族总共有三个:grep,egrep,fgrep
常用选项:
-E :开启扩展(Extend)的正则表达式。
-i :忽略大小写(ignore case)。
-v :反过来(invert),只打印没有匹配的,而匹配的反而不打印。
-n :显示行号
-w :被匹配的文本只能是单词,而不能是单词中的某一部分,如文本中有liker,而我搜寻的只是like,就可以使用-w选项来避免匹配liker
-c :显示总共有多少行被匹配到了,而不是显示被匹配到的内容,注意如果同时使用-cv选项是显示有多少行没有被匹配到。
-o :只显示被模式匹配到的字符串。
--color :将匹配到的内容以颜色高亮显示。
-A n:显示匹配到的字符串所在的行及其后n行,after
-B n:显示匹配到的字符串所在的行及其前n行,before
-C n:显示匹配到的字符串所在的行及其前后各n行,context
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep -i "Root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep -n "root" /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
10:operator:x:11:0:operator:/root:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep -vc "root" /etc/passwd
21
[root@zabbix ~]#
[root@zabbix ~]# grep -o "root" /etc/passwd
root
root
root
root
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep -A 2 "core id" /proc/cpuinfo
core id : 0
cpu cores : 1
apicid : 0
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep -B 2 "core id" /proc/cpuinfo
physical id : 0
siblings : 1
core id : 0
[root@zabbix ~]#
[root@zabbix ~]# grep -C 2 "core id" /proc/cpuinfo
physical id : 0
siblings : 1
core id : 0
cpu cores : 1
apicid : 0
[root@zabbix ~]#
模式部分:
直接输入要匹配的字符串,这个可以用fgrep(fast grep)代替来提高查找速度,比如我要匹配一下hello.c文件中printf的个数:fgrep -c "printf" hello.c
使用基本正则表达式,下面谈关于基本正则表达式的使用:
匹配字符:
. :任意一个字符。
[abc] :表示匹配一个字符,这个字符必须是abc中的一个。
[a-zA-Z] :表示匹配一个字符,这个字符必须是a-z或A-Z这52个字母中的一个。
[^123] :匹配一个字符,这个字符是除了1、2、3以外的所有字符。
对于一些常用的字符集,系统做了定义:
[A-Za-z] 等价于 [[:alpha:]]
[0-9] 等价于 [[:digit:]]
[A-Za-z0-9] 等价于 [[:alnum:]]
tab,space 等空白字符 [[:space:]]
[A-Z] 等价于 [[:upper:]]
[a-z] 等价于 [[:lower:]]
标点符号 [[:punct:]]

匹配次数:
\{m,n\} :匹配其前面出现的字符至少m次,至多n次。
\? :匹配其前面出现的内容0次或1次,等价于\{0,1\}。
* :匹配其前面出现的内容任意次,等价于\{0,\},所以 ".*" 表述任意字符任意次,即无论什么内容全部匹配。
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "/.*sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep "/.\{0,2\}sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep -w ".\{0,2\}sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
[root@zabbix ~]#
位置锚定:
^ :锚定行首
$ :锚定行尾。技巧:"^$"用于匹配空白行。
\b或\<:锚定单词的词首。如"\blike"不会匹配alike,但是会匹配liker
\b或\>:锚定单词的词尾。如"\blike\b"不会匹配alike和liker,只会匹配like
\B :与\b作用相反。
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "h" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep "^h" /etc/passwd #匹配以h开头的
halt:x:7:0:halt:/sbin:/sbin/halt
[root@zabbix ~]#
[root@zabbix ~]# grep "h$" /etc/passwd #匹配以h结尾的内容
root:x:0:0:root:/root:/bin/bash
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep "sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep "\<sh" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep "\Bsh\b" /etc/passwd
root:x:0:0:root:/root:/bin/bash
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
[root@zabbix ~]#
分组及引用:
\(string\) :将string作为一个整体方便后面引用
\1 :引用第1个左括号及其对应的右括号所匹配的内容。
\2 :引用第2个左括号及其对应的右括号所匹配的内容。
\n :引用第n个左括号及其对应的右括号所匹配的内容。
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "^\([A-Za-z]\).*\1$" /etc/passwd
nobody:x:99:99:Nobody:/:/sbin/nologin
[root@zabbix ~]#
扩展的(Extend)正则表达式(注意要使用扩展的正则表达式要加-E选项,或者直接使用egrep):
匹配字符:这部分和基本正则表达式一样
匹配次数:
* :和基本正则表达式一样
? :基本正则表达式是\?,而这里没有\。
{m,n} :相比基本正则表达式也是没有了\。
+ :匹配其前面的字符至少一次,相当于{1,}。
位置锚定:和基本正则表达式一样。
分组及引用:
(string) :相比基本正则表达式也是没有了\。
\1 :引用部分和基本正则表达式一样。
\n :引用部分和基本正则表达式一样。
或者:
a|b :匹配a或b,注意a是指 | 的左边的整体,b也同理。比如 C|cat 表示的是 C或cat,而不是Cat或cat,如果要表示Cat或cat,则应该写为 (C|c)at 。记住(string)除了用于引用还用于分组。
注1:默认情况下,正则表达式的匹配工作在贪婪模式下,也就是说它会尽可能长地去匹配,比如某一行有字符串 abacb,如果搜索内容为 "a.*b" 那么会直接匹配 abacb这个串,而不会只匹配ab或acb。
注2:所有的正则字符,如 [ 、* 、( 等,若要搜索 * ,而不是想把 * 解释为重复先前字符任意次,可以使用 \* 来转义。
下面用一个练习来结束本次grep的学习:
在网络配置文件 /etc/sysconfig/network-scripts/ifcfg-ens32 中检索出所有的 IP
检索出 0-255的范围
[root@zabbix ~]#
[root@zabbix ~]# egrep ."[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]" /etc/sysconfig/network-scripts/ifcfg-ens32
NAME=ens32
UUID=a42c58f3-b9ef-42fb-a6dd-827892c88da4
DEVICE=ens32
[root@zabbix ~]#