shell那点事儿——运维工程师必会正则表达式及文本处理三剑客
正则表达式及文本处理三剑客
前言
玩转Linux操作系统必须会shell,会shell必须知道正则表达式及grep、sed、awk文本处理三剑客。好多年前有个大佬告诉我,不知道正则不要说自己会Linux,为此我专门学习这部分内容,这篇文章是对我学习内容的整理,也是我经常查阅的一篇笔记,也希望他可以帮助到大家。共同进步,瑞思拜~
一、正则表达式
基本正则表达式元字符:
元字符 功能 示例
^ 行首定位符 ^love grep '^root' /etc/passwd
$ 行尾定位符 love$ grep 'root$' /etc/passwd
. 匹配单个字符 l..e grep '^r..t' /etc/passwd
- 匹配0个或多个前导字符 ab*love grep 'ro*t' /etc/passwd
.* 任意多个字符 grep 'r.*ot' /etc/passwd # r开头ot结尾
[] 匹配指定范围内的一个字符 [lL]ove grep '[rx]oot' /etc/passwd
[ - ] 匹配指定范围内的一个字符 [a-z0-9]ove grep '[a-z0-9]oot' /etc/passwd
[^] 匹配不在指定组内的字符 [^a-z0-9]ove grep '[^a-z0-9]oot' /etc/passwd
^[^] 括号外尖号是指匹配,匹配括号内的任意一个字符 括号内尖号是指取反,匹配不在括号内的任意字符
\ 用来转义元字符 love\. 匹配love.
\< 词首定位符 \<love 定位单词首
\> 词尾定位符 love\> 定位单词尾
\(..\) 匹配稍后使用的字符的标签 :% s/172.16.130.1/172.16.130.5/
:% s/\(172.16.130.\)1/\15/
:% s/\(172.\)\(16.\)\(130.\)1/\1\2\35/
:3,9 s/\(.*\)/#\1/
x\{ m\} 字符x重复出现m次 lo\{ 5\}ve
x\{ m,\} 字符x重复出现m次以上 lo\{ 5,\}ve
x\{ m,n\} 字符x重复出现m到n次 lo\{ 5,10\}ve
扩展正则表达式元字符(需要egrep或者转义)
元字符 功能 示例
+ 匹配一个或多个前导字符 [a-z]+ove
? 匹配零个或一个前导字符 lo?ve
a|b 匹配a或b love|hate
() 组字符 loveable|rs love(able|rs) ov+ (ov)+
(..)(..)\1\2 标签匹配字符 (love)able\1er
x{ m} 字符x重复m次 lo{ 5}ve
x{ m,} 字符x重复至少m次 lo{ 5,}ve
x{ m,n} 字符x重复m到n次 lo{ 5,10}ve
二、文本处理三剑客
grep
grep使用的元字符:
grep: 使用基本元字符集 ^, $, ., *, [], [^], \< \>,\(\),\{ \}, \+, \|
egrep(grep -E): 使用扩展元字符集 ?, +, { }, |, ( )
grep也可以使用扩展集中的元字符,仅需要对这些元字符前置一个反斜线(转义)
\w 所有字母与数字,称为字符[a-zA-Z0-9] 'l[a-zA-Z0-9]*ve' 'l\w*ve'
\W 所有字母与数字之外的字符,称为非字符 'love[^a-zA-Z0-9]+' 'love\W+'
\b 词边界 '\<love\>' '\blove\b'
grep不支持\d、\D、\s、\S,\d匹配数字,\D匹配非数字,\s匹配空白,\S匹配非空白
grep常用选项:
-i --ignore-case # 忽略大小写
-v --invert-match # 反向查找,只显示不匹配的行
-n --line-number # 输出的同时打印行号
-e --regexp=PATTERN
grep -e "a" -e "b" file # 同时过滤多个条件
-f --file=FILE
grep -f a b # 输出b文件中与a相同的行
-c --count
grep -c "1" a # 输出a文件中匹配到1的行数
-o --only-matching
ifconfig |grep -E -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" # 打印所有ip
sed
sed使用的元字符
使用基本元字符集 ^, $, ., *, [], [^], \< \>,\(\),\{ \}
使用扩展元字符集 ?, +, { }, |, ( )
sed选项
sed -r 打印结果,没有真正执行
sed -i 真正执行
sed -n 静默输出(只打印处理过的行)
sed常用用法
sed -n '10,20p' 只显示匹配到的行
sed -r 's/root/alice/gi' /etc/passwd 忽略大小写且全局替换root>alice
sed -r 's#root#alice#gi' /etc/passwd 替换 不需要定义分隔符 直接使用
sed -r '\#root#d' /etc/passwd 查找 要定义分隔符
sed -r '/^[ \t]*#/d' passwd 删除`#`开头的行
sed -r '/^[ \t]*$/d' passwd 删除空行
sed -r '/^[ \t]*#/d; /^[ \t]*$/d' passwd 删除注释行和空行 ;号隔开
sed -r '3,$ s/^#*/#/' passwd 将行首零个或多个#换成一个#
sed -r '30,50 s/^[ \t]*#*/#/' passwd 将行首带有空格的零个#或多个#换成一个# 全部注释
sed ':label;N;s/\n/ /;b label' 回车符转行为空格
sed定址
sed -r 'd' passwd 全部删除
sed -r '3d' /etc/passwd 删除第3行
sed -r '1,3d' /etc/passwd 删除1-3行
sed -r '/root/d' /etc/passwd 删除带root的行
sed -r 's/root/alice/g' /etc/passwd 全局替换root为alice
sed -r '/^adm/,20d' /etc/passwd 从adm开头的行删除到第20行
sed -r '/^adm/,+20d' /etc/passwd 删除adm开头的行再删除其行后20行
sed常用参数
d 删除行
s 用一个字符串替换另一个
s 替换标志
g 全局替换
i 忽略大小写
sed -r 's/(.*)/#\1/' passwd
sed -r 's/(.*)/#&/' passwd &代表在查找串中匹配到的内容
sed -r 's/^/#/' passwd 全部注释
sed -r 's/(.)(.)(.*)/\1oooo\2\3/' passwd 在第二个字母前添加内容 oooo
sed -r 's/(192)(.)/\1\2xx\2/' a.file
192.xx.168.1.1 10.19.200.200
sed -n "s/192/xx/p" a 替换并打印
xx.168.1.1 10.19.200.200
r 读取文件
sed -r '2r 1.txt' a.txt 把a.txt的前2行放到1.txt最前面
w 写入文件
sed -r '3,$w 1.txt' a.txt 把a.txt的3行到最后一行保存到1.txt
aic 插入
sed -r '2a 1111111111111' 1.txt 在第二行后面追加111111111111
sed -r '2i 1111111111111' 1.txt 在第二行前面插入一行1111111111111
sed -r '2c 1111111111111' 1.txt 第二行替换成1111111111
sed -r '/^192/a abc123' a 在以192开头的行后面追加abc123
192.168.1.1 10.19.200.200
abc123
192.168.1.1 10.19.200.200
abc123
sed空间操作
n 读取下一行到模式空间,如果没有下一行则执行 q 退出
N 追加下一行内容到模式空间,并以换行符\n分割
q 退出
p 打印模式空间所有内容
P 打印模式空间第一行
seq 6 |sed -n 'n;p' 打印偶数行
seq 6 |sed 'N;q' 打印1\n2
d 删除模式空间所有内容
D 删除模式空间第一行,以`\n`分割,放弃之后的命令,但是对剩余模式空间重新执行sed
seq 6 |sed -n 'N;P' 打印奇数行
seq 6 |sed 'N;D' 打印最后一行
读取1,执行N,模式空间为1\n2,执行D,删除1剩余2,执行N,模式空间为2\n3,执行D,删除2剩余3,依此类推,得出5,执行N,条件失败退出。
h/H 模式空间**覆盖/追加**到暂存空间
g/G 暂存空间**覆盖/追加**到模式空间
sed -r '1h;$G' passwd 把第1行复制到最后一行
sed -r '1{h;d};$G' passwd 把第1行剪切到最后一行
sed -r '1h; 2,$g' passwd 把其它行全部替换成第1行
sed -r '1h; 2,3H; $G' passwd 把前3行复制到最后三行
sed -r '1!G;h;$!d' 1 倒序!!!!!
读取第一行 1 时,跳过 G 命令,执行 h 命令将模式空间 1 复制到保持空间,执行 d 命令删除模式空
间的 1。
读取第二行 2 时,模式空间是 2,执行 G 命令,将保持空间 1 追加到模式空间,此时模式空间是2\n1,执行 h 命令将 2\n1 覆盖到保持空间,d 删除模式空间。
以此类推,读到第 5 行时,模式空间是 5,执行 G 命令,将保持空间的 4\n3\n2\n1 追加模式空间,
然后复制到模式空间,5\n4\n3\n2\n1,不执行 d,模式空间保留,输出。
x 暂存空间和模式空间替换
sed -r '1h;4x;$G' passwd 把第4行替换成第1行并把第4行追加到最后一行
sed ':t;N;s/\n/,/;b t' 将换行符换成逗号
sed命令区分制表符和空格
sed -n l tab_space.txt
this is tab\tfinish.$
this is several space finish.$
awk
awk内部变量:
$0: 保存当前记录的内容 awk -F: '{print $0}' passwd
NF: 显示每一行的字段数 awk -F: '{print NR,$0,NF}' passwd
NR: 显示总共行号 awk -F: '{print NR,$0}' passwd passwd1
FNR: 分别显示每个文件行号 awk -F: '{print FNR,$0}' passwd passwd1
FS: 输入字段分隔符 默认空格 awk -F"[ :\t]" '{print $1,$2,$3}' passwd
OFS: 输出字段分隔符 awk 'BEGIN{FS=":";OFS="--"} {print $1,$2,$3}' passwd
RS: 输入记录分隔符 默认换行 awk 'BEGIN{RS="/"} {print $0}' passwd # 以"/"和空格分成多行
ORS: 输出记录分隔符 awk -F: 'BEGIN{ORS=" "} {print $0}' passwd # 每一行以空格分开 默认为回车
格式化输出:
awk -F: '{print "username is: " $1 "\t uid is: " $3}' passwd # 变量以外的其他所有字符串要用引号引起来
tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="} {print $0}'
# Service Port Description
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' passwd
%s 字符类型
%d 数值类型
%f 浮点类型 %.2f 保留两位小数点
占15字符
- 表示左对齐,默认是右对齐
printf默认不会在行尾自动换行,加\n
awk模式:
-
正则表达式
(1) 匹配一整行
awk '/^root/' passwd awk '!/^root/' passwd
(2) 匹配字段
awk -F":" '$1 ~/root/' passwd awk -F":" '$1 !~/bin/' passwd
-
比较表达式
awk -F":" '$3==7' passwd awk -F":" '$NF=="/sbin/nologin"' passwd awk -F":" '$NF ~"/nologin"' passwd
-
条件表达式
awk -F":" '{if($3>5){print $1,$3} else{print $1}}' passwd
-
算术运算:+ - * / %(模) (幂23)
awk -F":" '{if($3*1>5){print $1,$3}}' passwd
-
逻辑操作符
&& 逻辑与 a&&b || 逻辑或 a||b ! 逻辑非 !a
awk -F":" '$1 ~/^ro/ && $1 ~/ooot$/' passwd awk -F":" '$1 ~/^ro/ || $1 ~/^bin/' passwd awk -F":" '!($1 ~/^ro/ || $1 ~/^bin/)' passwd awk -F":" '!/^#|^$/' passwd
-
布尔值判断
seq 6 |awk 'i=!i' # 打印奇数行 seq 6 |awk '!(i=!i)' # 打印偶数行 i值未被定义所以为假,!i就为真,所以i为真,打印1 第二次,i已经为真,!i为假,所以i又为假,不打印2 ,依次循环
-
三目运算
awk 'BEGIN{print 1==1?"yes":"no"}' # 格式 条件?"真":"假" yes seq 4 |awk '{printf NR%2!=0?$0" ":$0" \n"}' # 一行拆分两行 1 2 3 4
-
控制输出
awk '/^root/{next}{print $0}' file # 不打印root开头的行 seq 5 |awk 'NR!=1{print $0}' # 不打印第一行 tail -n5 /etc/services |awk '{print $2 > "a.txt"}' # 打印结果到一个文件 tail -n5 /etc/services |awk '{print $2 |"grep tcp"}' # 过滤结果 awk 'NR%2{printf $0",";next}{print $0}' # 两行合并为一行 ","分隔
awk脚本:
-
条件判断
awk -F: '{if($3>0 && $3<1000){i++}} END{print i}' passwd awk -F: '{if($3==0){i++} else{j++}} END{print "管理员个数: "i; print "系统用户数: "j}' passwd awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员: "i; print "普通用户: "k; print "系统用户: "j}' passwd
-
循环
awk -F: '{i=1;while(i<=5) {print $1;i++}}' passwd awk -F":" '{i=1; while(i<=NF){print $i; i++}}' passwd # 分别打印每一行的每一列 awk -F":" '/^root/{ for(i=1;i<=NF;i++) {print $i} }' passwd # 分别打印每一行的每一列
-
数组
awk -F: '{username[i++]=$1} END{print username[0]}' passwd
按索引遍历:
awk -F: '{username[$1]++} END{for(i in username) {print i,username[i]} }' passwd # 用户数量统计 awk -F":" '{shells[$NF]++} END{for(i in shells){print i,shells[i]}}' passwd # shell类型的数量统计 ss -antp |grep :22 |awk -F":" '!/LISTEN/{ip[$2]++} END {for (i in ip) {print i,ip[i]}}' |sort -rn -k3 |head |awk '{print $2,$3}' # 统计每个ip ssh连接的次数 awk '/22\/Mar\/2018/{ips[$1]++} END{for(i in ips){print i,ips[i]}}' access.log |awk '$2>100' |sort -k2 -rn|head # 统计nginx访问次数大于100的ip awk '/22\/Mar\/2018/{ips[$1]++} END{for(i in ips){if(ips[i]>100){print i,ips[i]}}}' access.log |sort -k2 -rn|head
cat /var/log/access_liang.log |awk '$7~/liang.php/ {print $1}' |sort |uniq -c # 统计含有liang.php页面的ip
awk '{sum += $1} END {print sum}' # 列相加 awk '{line[NR]=$0}END{for(i=NR;i>=1;i--){print line[i]}}' 1 # 行倒序 awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' 1 # 列倒序
统计访问 IP 次数:
awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log
统计访问访问大于 100 次的IP:
awk '{a[$1]++}END{for(v in a){if(a[v]>100)print v,a[v]}}' access.log
统计访问 IP 次数并排序取前10:
awk '{a[$1]++}END{for(v in a)print v,a[v] |"sort -k2 -nr |head -10"}' access.log
统计时间段访问最多的IP:
awk '$4>="[02/Jan/2018:00:00:00" && $4<="[02/Jan/2018:00:03:00"{a[$1]++}END{for(v in a)print v,a[v]}' access.log
统计上一分钟访问量:
date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M) awk -vdate=$date '$4~date{c++}END{print c}' access.log
统计访问最多的 10 个页面:
awk '{a[$7]++}END{for(v in a)print v,a[v] |"sort -k1 -nr|head -n10"}' access.log
统计每个 URL 数量和返回内容总大小:
awk '{a[$7]++;size[$7]+=$10}END{for(v in a)print a[v],v,size[v]}' access.log
统计每个 IP 访问状态码数量:
awk '{a[$1" "$9]++}END{for(v in a)print v,a[v]}' access.log
统计访问 IP 是 404 状态次数:
awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log
找出 b 文件在 a 文件相同记录:
awk 'NR==FNR{a[$0]=1}NR!=FNR{if($0 in a) print $0}' a b
找出 b 文件在 a 文件不同记录:
awk 'FNR==NR{a[$0]=1;next}!a[$0]' a b
-
引用外部变量
使用-v参数可以将外部值传给awk中使用的变量 awk -v user=root -F: '$1 == user' passwd
-
案例
例1
cat a.file 姓名 费用 数量 zhangsan 8000 1 zhangsan 5000 1 lisi 1000 1 lisi 2000 1 wangwu 1500 1 zhaoliu 6000 1 zhaoliu 2000 1 zhaoliu 3000 1 awk 'NR>1{name[$1]++;number[$1]+=$3;money[$1]+=$2}END{for(i in name)print i,number[i],money[i]}' a.file zhaoliu 3 11000 zhangsan 2 13000 wangwu 1 1500 lisi 2 3000
例2
求每个人的平均成绩 cat a.txt one 23 23 45 22 22 two 23 23 45 22 22 28 three 23 23 45 22 22 82 23 four 23 23 45 22 22 23 45 32 23 cat a.awk BEGIN{ print "姓名","平均成绩" } { for(i=2;i<=NF;i++){ sum=sum+$i } avg=sum/(NF-1) print $1,avg sum=0 } awk -f a.awk a.txt 姓名 平均成绩 one 27 two 27.1667 three 34.2857 four 28.6667
例3
按列统计,列转行 cat a.file 姓名:liang 性别:male 电话:18623432212 姓名:mobai 性别:male 电话:18223432122 姓名:right 性别:female 电话:18123432212 cat a.file |awk -F":" '{print $2}' |sed "/^$/d" |awk 'BEGIN{print "姓名\t性别\t电话"} {printf NR%3!=0?$0"\t":$0"\n"}' 姓名 性别 电话 liang male 18623432212 mobai male 18223432122 right female 18123432212
例4
获取每行的最大值 cat c.txt 20 30 40 55 44 33 23 76 54 13 19 9 cat c.awk { max=$1 for (i=2;i<=NF;i++) { if ($i>max) { max=$i } } print max } awk -f c.awk c.txt 40 55 76 19 按行从小到大排序 cat c1.awk { num=0 if($1>$2) { num=$1 $1=$2 $2=num } if($1>$3) { num=$1 $1=$3 $3=num } if($2>$3) { num=$2 $2=$3 $3=num } print $1,$2,$3 } awk -f c1.awk c.txt 20 30 40 33 44 55 23 54 76 9 13 19
目录 返回
首页