shell那点事儿——运维工程师必会正则表达式及文本处理三剑客

Posted 偷学技术的梁胖胖yo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

xm            字符x重复m次               lo5ve    

xm,           字符x重复至少m次            lo5,ve

xm,n          字符x重复m到n次             lo5,10ve

二、文本处理三剑客

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 '1h;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 'BEGINFS=":";OFS="--" print $1,$2,$3' passwd

RS:      输入记录分隔符 默认换行      awk 'BEGINRS="/" print $0' passwd           # 以"/"和空格分成多行

ORS:     输出记录分隔符              awk -F: 'BEGINORS=" " print $0' passwd     # 每一行以空格分开 默认为回车 

格式化输出:

awk -F: 'print "username is: " $1 "\\t uid is: " $3' passwd     # 变量以外的其他所有字符串要用引号引起来

tail /etc/services |awk 'BEGINprint "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. 正则表达式

    (1) 匹配一整行

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

    (2) 匹配字段

    awk -F":" '$1 ~/root/' passwd
    
    awk -F":" '$1 !~/bin/' passwd
    
  2. 比较表达式

    awk -F":" '$3==7' passwd
    
    awk -F":" '$NF=="/sbin/nologin"' passwd
    
    awk -F":" '$NF ~"/nologin"' passwd
    
  3. 条件表达式

    awk -F":" 'if($3>5)print $1,$3 elseprint $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$/' passwd
    
    awk -F":" '$1 ~/^ro/ || $1 ~/^bin/' passwd
    
    awk -F":" '!($1 ~/^ro/ || $1 ~/^bin/)' passwd
    
    
    awk -F":" '!/^#|^$/' passwd  
    
    
  6. 布尔值判断

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

    awk 'BEGINprint 1==1?"yes":"no"'    # 格式 条件?"真":"假"
    
    yes
    
    seq 4 |awk 'printf NR%2!=0?$0" ":$0" \\n"'   # 一行拆分两行
    
    1 2
    
    3 4
    
  8. 控制输出

    awk '/^root/nextprint $0' file     # 不打印root开头的行
    
    seq 5 |awk 'NR!=1print $0'           # 不打印第一行
    
    tail -n5 /etc/services |awk 'print $2 > "a.txt"'     # 打印结果到一个文件
    
    tail -n5 /etc/services |awk 'print $2 |"grep tcp"'   # 过滤结果
    
    awk 'NR%2printf $0",";nextprint $0'     # 两行合并为一行 ","分隔
    

awk脚本:

  1. 条件判断

    awk -F: 'if($3>0 && $3<1000)i++  ENDprint i' passwd
    
    awk -F: 'if($3==0)i++ elsej++ ENDprint "管理员个数: "i; print "系统用户数: "j' passwd
    
    awk -F: 'if($3==0)i++ else if($3>999)k++ elsej++ ENDprint "管理员: "i; print "普通用户: "k; print "系统用户: "j' passwd
    
  2. 循环

    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      # 分别打印每一行的每一列
    
  3. 数组

    awk -F: 'username[i++]=$1 ENDprint username[0]' passwd
    

    按索引遍历:

    awk -F: 'username[$1]++ ENDfor(i in username) print i,username[i] ' passwd    # 用户数量统计
    
    awk -F":" 'shells[$NF]++ ENDfor(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]++ ENDfor(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]++ ENDfor(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]=$0ENDfor(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]++ENDfor(v in a)print v,a[v]' access.log
    

    统计访问访问大于 100 次的IP:

    awk 'a[$1]++ENDfor(v in a)if(a[v]>100)print v,a[v]' access.log
    

    统计访问 IP 次数并排序取前10:

    awk 'a[$1]++ENDfor(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]++ENDfor(v in a)print v,a[v]' access.log
    

    统计上一分钟访问量:

    date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M)
    
    awk -vdate=$date '$4~datec++ENDprint c' access.log
    

    统计访问最多的 10 个页面:

    awk 'a[$7]++ENDfor(v in a)print v,a[v] |"sort -k1 -nr|head -n10"' access.log
    

    统计每个 URL 数量和返回内容总大小:

    awk 'a[$7]++;size[$7]+=$10ENDfor(v in a)print a[v],v,size[v]' access.log
    

    统计每个 IP 访问状态码数量:

    awk 'a[$1" "$9]++ENDfor(v in a)print v,a[v]' access.log
    

    统计访问 IP 是 404 状态次数:

    awk 'if($9~/404/)a[$1" "$9]++ENDfor(i in a)print v,a[v]' access.log
    

    找出 b 文件在 a 文件相同记录:

    awk 'NR==FNRa[$0]=1NR!=FNRif($0 in a) print $0' a b
    

    找出 b 文件在 a 文件不同记录:

    awk 'FNR==NRa[$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    1
    zhangsan        5000    1
    lisi            1000    1
    lisi            2000    1
    wangwu          1500    1
    zhaoliu         6000    1
    zhaoliu         2000    1
    zhaoliu         3000    1
    
    awk 'NR>1name[$1]++;number[$1]+=$3;money[$1]+=$2ENDfor(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 'BEGINprint "姓名\\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
    

以上是关于shell那点事儿——运维工程师必会正则表达式及文本处理三剑客的主要内容,如果未能解决你的问题,请参考以下文章

自动化运维工程师必备的shell脚本知识

数据库备份那点事儿

关于LOG那点事儿

❤️ 运维工程师必会480个linux命令,还不收藏? ❤️

必会的 24 道 Shell 脚本面试题

软件工程:帕金森定律,项目工期的那点事儿