记录日常工作关于系统运维,虚拟化云计算,数据库,网络安全等各方面问题。


正则表达式及文本处理三剑客


前言

玩转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=PATTERNgrep -e "a" -e "b"   file   # 同时过滤多个条件 -f    --file=FILEgrep -f a b       # 输出b文件中与a相同的行-c     --countgrep -c "1" a     # 输出a文件中匹配到1的行数-o     --only-matchingifconfig |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>alicesed -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为alicesed -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/' passwdsed -r 's/(.*)/#&/' passwd          &代表在查找串中匹配到的内容sed -r 's/^/#/' passwd      全部注释sed -r 's/(.)(.)(.*)/\1oooo\2\3/' passwd      在第二个字母前添加内容 oooosed -r 's/(192)(.)/\1\2xx\2/' a.file  192.xx.168.1.1 10.19.200.200sed -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}' passwdNF:      显示每一行的字段数          awk -F: '{print NR,$0,NF}' passwdNR:      显示总共行号               awk -F: '{print NR,$0}' passwd passwd1

FNR:     分别显示每个文件行号         awk -F: '{print FNR,$0}' passwd passwd1

FS:      输入字段分隔符 默认空格       awk -F"[ :\t]" '{print $1,$2,$3}' passwdOFS:      输出字段分隔符              awk 'BEGIN{FS=":";OFS="--"} {print $1,$2,$3}' passwdRS:      输入记录分隔符 默认换行      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 Servicesawk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}'  passwd

  %s 字符类型

  %d 数值类型

  %f 浮点类型   %.2f 保留两位小数点

  占15字符

  - 表示左对齐,默认是右对齐

  printf默认不会在行尾自动换行,加\n

awk模式:

  1. 正则表达式

    (1) 匹配一整行

    awk '/^root/' passwdawk '!/^root/' passwd

    (2) 匹配字段

    awk -F":" '$1 ~/root/' passwdawk -F":" '$1 !~/bin/' passwd

  2. 比较表达式

    awk -F":" '$3==7' passwdawk -F":" '$NF=="/sbin/nologin"' passwdawk -F":" '$NF ~"/nologin"' passwd

  3. 条件表达式

    awk -F":" '{if($3>5){print $1,$3} else{print $1}}' passwd

  4. 算术运算:+ - * / %(模) (幂23)

    awk -F":" '{if($3*1>5){print $1,$3}}' passwd

  5. 逻辑操作符

     &&    逻辑与    a&&b
    
     ||    逻辑或    a||b
    
     !    逻辑非    !a

    awk -F":" '$1 ~/^ro/ && $1 ~/ooot$/' passwdawk -F":" '$1 ~/^ro/ || $1 ~/^bin/' passwdawk -F":" '!($1 ~/^ro/ || $1 ~/^bin/)' passwdawk -F":" '!/^#|^$/' passwd

  6. 布尔值判断

    seq 6 |awk 'i=!i'      # 打印奇数行seq 6 |awk '!(i=!i)'   # 打印偶数行i值未被定义所以为假,!i就为真,所以i为真,打印1
    第二次,i已经为真,!i为假,所以i又为假,不打印2 ,依次循环

  7. 三目运算

    awk 'BEGIN{print 1==1?"yes":"no"}'    # 格式 条件?"真":"假"yesseq 4 |awk '{printf NR%2!=0?$0" ":$0" \n"}'   # 一行拆分两行1 23 4

  8. 控制输出

    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脚本:

  1. 条件判断

    awk -F: '{if($3>0 && $3<1000){i++}} END{print i}' passwdawk -F: '{if($3==0){i++} else{j++}} END{print "管理员个数: "i; print "系统用户数: "j}' passwdawk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员: "i; print "普通用户: "k; print "系统用户: "j}' passwd

  2. 循环

    awk -F: '{i=1;while(i<=5) {print $1;i++}}' passwdawk -F":" '{i=1; while(i<=NF){print $i; i++}}' passwd      # 分别打印每一行的每一列awk -F":" '/^root/{ for(i=1;i<=NF;i++) {print $i} }' passwd      # 分别打印每一行的每一列

  3. 数组

    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的ipawk '/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

  4. 引用外部变量

    使用-v参数可以将外部值传给awk中使用的变量awk -v user=root  -F: '$1 == user' passwd

  5. 案例

    例1

    cat a.file
    
    姓名             费用   数量
    zhangsan        8000    1zhangsan        5000    1lisi            1000    1lisi            2000    1wangwu          1500    1zhaoliu         6000    1zhaoliu         2000    1zhaoliu         3000    1awk '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 11000zhangsan 2 13000wangwu 1 1500lisi 2 3000

    例2

    求每个人的平均成绩cat a.txt
     
    one          23 23 45 22 22two           23 23 45 22 22 28three     23 23 45 22 22 82 23four  23 23 45 22 22 23 45  32  23cat a.awk 
    
    BEGIN{ print "姓名","平均成绩"}{ for(i=2;i<=NF;i++){ sum=sum+$i}    avg=sum/(NF-1)print $1,avgsum=0}awk -f a.awk a.txt
    
    姓名 平均成绩
    one 27two 27.1667three 34.2857four 28.6667

    例3

    按列统计,列转行cat a.file
     
    姓名:liang
    性别:male
    电话:18623432212
    
    姓名:mobai
    性别:male
    电话:18223432122
    
    姓名:right
    性别:female
    电话:18123432212cat a.file  |awk -F":" '{print $2}' |sed "/^$/d" |awk 'BEGIN{print "姓名\t性别\t电话"} {printf NR%3!=0?$0"\t":$0"\n"}'姓名  性别  电话
    liang  male  18623432212mobai  male  18223432122right  female  18123432212

    例4

    获取每行的最大值cat c.txt 
    20 30 4055 44 3323 76 5413 19 9cat c.awk 
    { 
      max=$1
      for (i=2;i<=NF;i++)
      { if ($i>max){   max=$i}
      }print max}awk -f c.awk c.txt 
    40557619按行从小到大排序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 4033 44 5523 54 769 13 19



转载请标明出处【shell那点事儿——运维工程师必会正则表达式及文本处理三剑客】。

《www.micoder.cc》 虚拟化云计算,系统运维,安全技术服务.

网站已经关闭评论