• 简介

    • awk:Aho, Weinberger, Kernighan,报告生成器,格式化文本输出
    • 有多种版本:New awk(nawk),GNU awk( gawk )
  • awk的用法和格式

    • Linux系统所使用的是GUN awk
    • gawk:模式扫描和处理语言
    • 基本用法:

      awk [options] ‘program’ var=value file…
      awk [options] -f programfile var=value file…
      awk [options] 'BEGIN{ action;… } pattern{ action;… } END{action;… }' file ...
      awk 程序通常由:BEGIN语句块、能够使用模式匹配的通用语句块、END语句块,共3部分组成
      program通常是被单引号或双引号中

    • 选项:
      $0:整行
      $1,$2..... : 位置参数
      -F : 指定输入时用到的字段分隔符
      -v var=value : 自定义变量
      -f filename : 调用awk脚本文件

  • 基本格式

    awk [options] 'program' file…
    program:
                pattern{action statements;..}
    • pattern:
      pattern部分决定动作语句何时触发及触发事件
      地址定界: /pat1/,/pat2/ 一个范围
      /pattern/ 被匹配到的行
      experssion 表达式

      =,<,<=,==,!=,~(模式匹配)
      BEGIN: 执行前操作
      END: 执行后操作,收尾动作

    • action:
      print
      printf
      • printf : 格式化输出命令:
        printf "FORMAT", item1, item2, ...
        FORMAT:格式符,为每个item按位占一个位留一个特殊符号格式符,为每个item按位占一个位留一个特殊符号,所以item最终会显示在format指定格式符号的位置上;
        (1) FORMAT必须给出;
        (2) 在显示多行文本时,不会自动换行,需要显式给出换行控制符,\n;
        (3) FORMAT中需要分别为后面的每个item指定一个格式化符号;
        格式符:
        %c: 显示字符的ASCII码
        %d,%i: 显示十进制整数
        %e,%E: 显示科学计数法数值
        %f: 显示为浮点数
        %g,%G: 以科学计数法或浮点形式显示数值
        %s: 显示字符串
        %u: 无符号整数
        %%: 显示%自身
        修饰符:
        #[.#]:第一个数字控制显示的总宽度;第二个#表示小数点后精度,%3.1f
        -: 左对齐(默认右对齐) %-15s
        +:显示数值的正负符号 %+d

  • awk操作符:

    • 算数操作符:
      x+y, x-y, x*y, x/y, x^y, x%y
      -x: 转换为负数
      +x: 转换为数值
    • 赋值操作符:
      =, +=, -=, *=, /=, %=, ^=, ++, --
      例:
      (1)awk 'BEGIN{i=0;print ++i,i}' #++i,++在前,先加再赋值
      输出:1 1
      (2)awk 'BEGIN{i=0;print i++,i}' #i++,++在后,先赋值在加(这里相当于先输出i)
      输出:0 1
    • 比较操作符:
      ==, !=, >, >=, <, <=
    • 模式匹配符:
      ~:左边是否和右边匹配包含
      !~:是否不匹配

    • 逻辑操作符:
      && 与运算
      || 或运算
      ! 非运算
      函数调用: function_name(argu1, argu2, ...)
      条件表达式(三目表达式):
      selector?if-true-expression:if-false-expression
      例:
      awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf "%15s:%-s\n",$1,usertype}' /etc/passwd
      输出:
      root:Sysadmin or SysUser
      mysql:Sysadmin or SysUser
      zabbix:Common User
  • awk pattern:

    • PATTERN:根据pattern条件,过滤匹配的行,再做处理
      • (1)如果未指定:空模式,匹配每一行
      • (2)/regular expression/:仅处理能够模式匹配到的行,需要用/ /括起来
        awk '/^UUID/{print $1}' /etc/fstab
        awk '!/^UUID/{print $1}' /etc/fstab
      • (3)relational expression: 关系表达式,结果为“真”才会被处理
        真:结果为非0值,非空字
        假:结果为空字符串或0值
      • (4)line ranges:行范围
        startline,endline:/pat1/,/pat2/ 不支持直接给出数字格式
        例:
        awk -F: ‘/^root\>/,/^nobody\>/{print $1}' /etc/passwd
      • (5) BEGIN/END模式
        BEGIN{}: 仅在开始处理文件中的文本之前执行一次
        END{}:仅在文本处理完成之后执行一次
  • awk action:

    • 常用的action分类:
      (1) Expressions:算术,比较表达式等
      (2) Control statements:if, while等
      (3) Compound statements:组合语句
      (4) input statements
      (5) output statements:print等

  • awk控制语句:

    • if-else控制语句:

    • 语法:
      • 单分支:if(condition){statement;…}[else statement]
      • 多分支:if(condition1){statement1}else if(condition2){statement2}else{statement3}
    • 使用场景:对awk取得的整行或某个字段做条件判断
      • 例:
        awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
        awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd
        df -h|awk -F% '/^\/dev/{print $1}'|awk '$NF>=80{print $1,$5}'
    • while循环:

    • 语法:while(condition){statement;…}
      • 条件“真”,进入循环;条件“假”,退出循环
    • 使用场景:
      • 对一行内的多个字段逐一类似处理时使用
      • 对数组中的各元素逐一处理时使用
        • 例:
          awk '/^[[:space:]]linux16/{i=1;while(i<=NF){print $i,length($i); i++}}' /etc/grub2.cfg
          awk '/^[[:space:]]
          linux16/{i=1;while(i<=NF){if(length($i)>=10) {print $i,length($i)}; i++}}' /etc/grub2.cfg
    • do-while循环:
    • 语法:do {statement;…}while(condition)
      • 意义:无论真假,至少执行一次循环体
      • 例:
        awk 'BEGIN{ total=0;i=0;do{ total+=i;i++;}while(i<=100);print total}‘
    • for循环:
    • 语法格式:for(expr1;expr2;expr3) statement
      expr1:控制变量初始化;
      expr2:条件判断;
      expr3:控制变量的数值修正表达式;
      for(variable assignment;condition;iteration process) {for-body}
      #即:for(变量赋值;条件判断表达式;变量修正表达式) {循环体语句}
    • 例:
      awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
      #以空格开头0次或多次,后跟linux16的行,以空格为分隔符,显示每行中各字段的长度;同上while语句;

      awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {if(length($i)>=7) {print $i,length($i)}}}' /etc/grub2.cfg
      #以空格开头0次或多次,后跟linux16的行,以空格为分隔符,只显示每行中字段的长度>=7的字段;同上while语句;
    • for循环特殊用法:
    • 语法格式:for(var in array) {for-body}
    • 即:for(变量名 in 数组名) {循环体语句}

      • 变量名会遍历数组的下标,然后就能引用数组的元素;
      • 意义:能够遍历数组中的元素;
    • witch语句(在awk中用的不多)
    • 语法格式:
      switch(expression) {case VALUE1 or /REGEXP1/: statement1; case VALUE2 or /REGEXP2/: statement2; ...; default: statement}
      #即:switch(表达式) {case 值1或/正则式1/: 语句1; case 值1或/正则式1/: 语句2; ...; default: 语句n}

      • 判断表达式的值符合case里面的哪个条件,可以等于case里的值,也可以是被case里的正则表达式匹配;如果符合条件就执行该case里的语句,不会往下判断了;类似case语句,只不过是关键字写法不同;
    • break和continue:

      • break [n]:退出n层循环;
      • continue:提前结束本轮循环,直接进入下一轮循环(即下一个字段);
    • next:
    • 在awk中能实现2重循环,awk本身可对文件每行循环,使用循环语句是为了遍历一行中的每个字段,或数组中的每个元素;
    • next同continue一样,也是控制循环的,但是是控制awk的本身循环的;

      • 即提前结束对本行的处理而直接进入下一行;
      • 例:
        awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
        #显示用户ID号为偶数的行;即判断id号是否能被2整除,如果不等于0,直接结束本行,直接进行下一行;

  • awk数组:

    • 关联数组:array[index-expression]
    • index-expression:索引表达式

      • (1) 可使用任意字符串;字符串要使用双引号;不能随便使用单引号;
      • (2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空”;允许未经声明直接引用;

      • 若要判断数组中是否存在某元素,要使用"index in array"格式进行;

        • 例:
          awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";print weekdays["mon"]}'
          #注意:字符串要使用双引号;引用数组中的元素不需使用$符号;
      • 若要遍历数组中的每个元素,要使用for循环;
        for(var in array) {for-body}
        #即for(变量名 in 数组名) {循环体}
        #变量名中保存的是数组的索引,而不是数组中的元素;
        #注意:不要使用$符号;

        • 例:
          awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
          #遍历数组中的每个元素;要使用for循环;显示的次序可能不太和想象的一样;
          #注意:对应的变量var会遍历array数组的每个索引;
          #某个数组元素不存在,直接引用后会被创建且为空,做数值操作时当做0使用;
      • 使用数组统计每一类数值各自出现次数时非常有用;
        • 例:
          state["LISTEN"]++
          state["ESTABLISHED"]++
        • 显示tcp每个状态分别出现的次数;
          netstat -tan | awk '/^tcp\>/{state[$NF]++}END{for(i in state) { print i,state[i]}}'
          输出:
          LISTEN 2

  • awk函数:

    • 数值处理:
      rand():返回0和1之间一个随机数
      awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'
    • 字符串处理:
      length([s]):返回指定字符串的长度
      sub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为s

      例:echo "2008:08:08 08:08:08" | awk 'sub(/:/,“-",$1)'

      gsub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并全部替换为s所表示的内容
          # 例:echo "2008:08:08 08:08:08" | awk ‘gsub(/:/,“-",$0)'
      
      split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,…
          # 例:netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'

  • awk中调用shell命令:

    • system命令
    • 空格是awk中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,或者说除了awk的变量外其他一律用""引用起来。
    • 例:
      awk BEGIN'{system("hostname") }'
      awk 'BEGIN{score=100; system("echo your score is " score) }'

  • 练习:

    • awk打印奇数行:
      seq 10 | awk 'i=!i'
    • awk打印偶数行:
      seq 10 | awk '!(i=!1)'
      seq 10 | awk -v i=1 'i=!1'

    • awk取出文件中的重复行:
      awk '!line[$0]++' file
    • awk取出nginx访问成功的ip的次数
      awk '{ip[$1]++}END{for (i in ip){print i,ip[i]}}' /usr/local/nginx/log/access.log
      awk '{ip[$1]++}END{for(i in ip)if(ip[i]>1000)print i}' access_log |while read line;do
      iptables -A INPUT -s $line -j REJECT
      done

    • 统计/etc/fstab文件中每个文件系统类型出现的次数
      awk '/^[^#]/{type[$3]++}END{for(i in type){print i,type[i]}}' /etc/fstab

    • 统计/etc/fstab文件中每个单词出现的次数
      awk '{for(i=1;i<=NF;i++)word[$i]++}END{for(i in word)print word[i],i}' /etc/fstab

    • 提取出字符串Yd$C@M05MB%9&Bdh7dq+YVixp3vpw中的所有数字
      echo 'Yd$C@M05MB%9&Bdh7dq+YVixp3vpw' |awk -F "" '{for(i=1;i<=NF;i++){if($i ~ /[0-9]/)sum[$i]=$i}}END{for(i in sum)print sum[i]}'

    • 解决DOS***生产案例:根据web日志或者或者网络连接数,监控当某个IP并发连接数或者短时内PV达到100,即调用防火墙命令封掉对应的IP,监控频率每隔5分钟。防火墙命令为:iptables -A INPUT -s IP -j REJECT
      ss -tan | awk -F "[ :]+" '/^ESTAB/{ip[$(NF-2)]++}END{for(i in ip)if(ip[i]>1)system("iptables -A INPUT -s "i" -j REJECT")}'