美文网首页
awk--基本操作一

awk--基本操作一

作者: 一萍之春 | 来源:发表于2021-07-21 22:16 被阅读0次

    通过学习《awk精通》整理

    作者: 骏马金龙
    学习链接: https://www.junmajinlong.com/how_to_nav_posts/
    学习来源: 骏马金龙
    思维导图查看

    what

    awk是一个文本处理工具

    awk基本用法

    铺垫:文件读取的几种方式

    1. 按字符数量读取:每一次可以读取一个字符,或者多个字符,直到把整个文件读取完
             while read -n 1 char;do echo $char;done <a.txt
    2. 按照分隔符进行读取:一直读取直到遇到了分隔符才停止,下次继续从分隔的位置处向后读取,直到读完整个 文件
    while read -d "m" chars;do echo "$chars";done <a.txt 
    # 以字符'm'为分割点
    3. 按行读取:每次读取一行,直到把整个文件读完
    是按照分隔符读取的一种特殊情况:将分隔符指定为了换行符 \n while read line;do echo "$line";done <a.txt
    4. 一次性读取整个文件 是按字符数量读取的特殊情况,也是按分隔符读取的特殊情况
    read -N 10000000 data <a.txt
    echo "$data #展示
    
    read -d '_' data <a.txt
    echo "$data" #展示
    

    awk用法入门

     awk 'awk_program' a.txt
    
    • a.txt是awk要读取的文件,可以是0个文件或一个文件,也可以多个文件
      • 如果不给定任何文件,但又需要读取文件,则表示从标准输入中读取
    • 单引号 包围的是awk代码,也称为awk程序
      • 尽量使用单引号,因为在awk中经常使用 符号,而符号在Shell是变量符号,如果使用双引号包围 awk代码,
        符号会被Shell解析成Shell变量,然后进行Shell变量替换。使用单引号包围awk代码, 则 会脱离Shell的魔掌,使得$符号留给了awk去解析
    • awk程序中,大量使用大括号,大括号表示代码块,代码块中间可以之间连用,代码块内部的多个语句需使用分 号";"分隔
    awk '{print $0}' a.txt
    awk '{print $0}{print $0;print $0}' a.txt
    

    BEGIN和END语句块

    awk 'BEGIN{print "我在前面"}{print $0}' a.txt
    awk 'END{print "我在后面"}{print $0}' a.txt
    awk 'BEGIN{print "我在前面"}{print $0}END{print "我在后面"}' a.txt
    

    BEGIN代码块:

    • 在读取文件之前执行,且执行一次
    • 在BEGIN代码块中,无法使用 $0 或其它一些特殊变量

    END代码块:

    • 在读取文件完成之后执行,且执行一次
    • 有END代码块,必有要读取的数据(可以是标准输入)
    • END代码块中可以使用$0等一些特殊变量,只不过这些特殊变量保存的是最后一轮awk循环的数据

    main代码块:

    • 读取文件时循环执行,(默认情况)每读取一行,就执行一次main代码块
    • main代码块可有多个

    安装新版本gawk

    # 1.下载
    wget --no-check-certificate https://mirrors.tuna.tsinghua.edu.cn/gnu/gawk/gawk-4.2.0.tar.gz
    # 2.解压、进入解压后目录
    tar xf gawk-4.2.0.tar.gz
    cd gawk-4.2.0/
    # 3.编译
    ./configure --prefix=/usr/local/gawk4.2 && make && make install
    # 4.创建一个软链接:让awk指向刚新装的gawk版本
    ln -fs /usr/local/gawk4.2/bin/gawk /usr/bin/awk
    # 此时,调用awk将调用新版本的gawk,调用gawk将调用旧版本的gawk awk --version
    gawk --version
    

    系统深入awk

    awk命令行结构和语法结构

    在Shell命令行当中,双短横线 -- 表示选项到此结束,后面的都是命令的参数。

    awk [ -- ] program-text file ...
    awk -f program-file [ -- ] file ...
    awk -e program-text [ -- ] file ...
    
    cmd -x -r root -ppassword a.txt b.txt c.txt
    # 1.选项分为长选项和短选项 
    # 2.选项分为3种:
    #       (1).不带参数的选项
    #       (2).是带参数的选项,如果该选项后面没有给参数,则报错
    #       (3).参数可选的选项,选项后面可以跟参数,也可以不跟参数
    #          参数可选选项,如果要接参数,则必须将参数紧紧跟在选项后面,不能使用空格分隔选项和参数
    #3.两种参数:
    #   (1).选项型参数
    #   (2).非选项型参数
    

    awk的语法充斥着 pattern{action} 的模式,它们称为awk rule:

    awk 'BEGIN{n=3} /^[0-9]/{$1>5}{$1=333;print $1} /Alice/{print "Alice"} END{print "hello"}' a.txt
    
    • pattern部分用于测试筛选数据,action表示在测试通过后执行的操作
      • 省略 pattern ,等价于对每一行数据都执行action
        • awk '{print $0}' a.txt
      • 省略代码块{action} ,等价于 {print} 即输出所有行
        • awk '/Alice/' a.txt 等价于awk '/Alice/{print $0}' a.txt
      • 省略代码块中的 ,表示对筛选的行什么都不做
        • awk '/Alice/{}' a.txt
      • pattern{action}任何一部分都可以省略
        + awk '' a.txt
      
    • 多个 pattern{action} 可以直接连接连用

    pattern和action

    对于 pattern{action} 语句结构(都称之为语句块),其中的pattern部分可以使用下面列出的模式:

    # 特殊pattern BEGIN
    END
    # 布尔代码块
    /regular expression/ # 正则匹配成功与否 /a.*ef/{action}
    relational expression # 即等值比较、大小比较 3>2{action}
    pattern && pattern   # 逻辑与 3>2 && 3>1 {action}
    
    pattern || pattern # 逻辑或 3>2 || 3<1 {action}
    ! pattern # 逻辑取反 !/a.*ef/{action} 
    (pattern) # 改变优先级
    pattern ? pattern : pattern # 三目运算符决定的布尔值
    
    # 范围pattern,非布尔代码块
    pattern1, pattern2 # 范围,pat1打开、pat2关闭,即flip,flop模式
    

    action部分,可以是任何语句,例如print语句。

    awk读取文件

    详细分析awk如何读取文件

    awk读取输入文件时,每次读取一条记录(record)(默认情况下按行读取,所以此时记录就是行)。
    每读取一条记录,将其保存到 $0中,然后执行一次main代码段。

    awk '{print $0}' a.txt
    

    如果是空文件,则因为无法读取到任何一条记录,将导致直接关闭文件,而不会进入main代码段。
    但是BEGIN END会进行执行。

    touch x.log # 创建一个空文件
    awk '{print "hello world"}' x.log
    

    可设置表示输入记录分隔符的预定义变量RS(Record Separator)来改变每次读取的记录模式。

    # RS="\n" 、 RS="m"
    awk 'BEGIN{RS="\n"}{print $0}' a.txt 
    awk 'BEGIN{RS="m"}{print $0}' a.txt
    

    RS通常设置在BEGIN代码块中,因为要先于读取文件就确定好RS分隔符。

    RS指定输入记录分隔符时,所读取的记录中是不包含分隔符字符的。
    例如 RS="a" ,则 $0 中一定不可能出现 字符a。
    

    RS两种可能情况:

    • RS为单个字符:直接使用该字符来分割记录
    • RS为多个字符:将其当做正则表达式,只要匹配正则表达式的符号,都用来分割记录
      • 设置预定义变量IGNORECASE为非零值,正则匹配时表示忽略大小写
      • 兼容模式下,只有首字符才生效,不会使用正则模式去分割记录
        特殊的RS值用来解决特殊读取需求:
      • 按段落读取:RS=''
      • RS='\0' 一次性读取所有数据,但有些特殊文件中包含了空字符 \0
      • RS="^$" 真正的一次性读取所有数据,因为非空文件不可能匹配成功
      • RS='\n+'按行读取,但忽略所有空行
    # 按段落读取:RS=''
    $ awk 'BEGIN{RS=''}{print $0"------"}' a.txt
    # 一次性读取所有数据:RS='\0' RS="^$"
    $ awk 'BEGIN{RS='\0'}{print $0"------"}' a.txt $ awk 'BEGIN{RS='^$'}{print $0"------"}' a.txt
    # 忽略空行:RS='\n+'
    $ awk 'BEGIN{RS='\n+'}{print $0"------"}' a.txt
    # 忽略大小写:预定义变量IGNORECASE设置为非0值
    $ awk 'BEGIN{IGNORECASE=1}{print $0"------"}' RS='[ab]' a.txt
    
    预定义变量RT:
    在awk每次读完一条记录时,会设置一个称为RT的预定义变量,表示Record Termination。
    当RS为单个字符时,RT的值和RS的值是相同的。
    当RS为多个字符(正则表达式)时,则RT设置为正则匹配到记录分隔符之后,真正用于划分记录时的字符。 
    当无法匹配到记录分隔符时,RT设置为控制空字符串(即默认的初始值)。
    awk 'BEGIN{RS="(fe)?male"}{print RT}' a.txt
    

    两种行号:NR和FNR

    在读取每条记录之后,将其赋值给$0,同时还会设置NR、FNR、RT。

    • NR:所有文件的行号计数器
    • FNR:是各个文件的行号计数器
    awk '{print NR}' a.txt a.txt
    awk '{print FNR}' a.txt a.txt
    

    详细的分段字段分割

    awk读取每一条记录之后,会将其赋值给 0 ,同时还会对这条记录按照预定义变量FS划分字段,将划分好的各个\ 字段分别赋值给1 23 4...N ,同时将划分的字段数量赋值给预定义变量NF。

    引用字段的方式

    $N 引用字段:

    • N=0 :即 $0 ,引用记录本身
    • 0<N<=NF :引用对应字段
    • N>NF :表示引用不存在的字段,返回空字符串 N<0 :报错
      可使用变量或计算的方式指定要获取的字段序号。
    awk '{n = 5;print $n}' a.txt
    awk '{print $(2+2)}' a.txt # 括号必不可少,用于改变优先级 
    awk '{print $(NF-3)}' a.txt
    

    分割字段的方式

    读取record之后,将使用预定义变量FS、FIELDWIDTHS或FPAT中的一种来分割字段。分割完成之后,再进入
    main代码段(所以,在main中设置FS对本次已经读取的record是没有影响的,但会影响下次读取)。

    FS或-F

    FS 或者 -F :字段分隔符

    • FS为单个字符时,该字符即为字段分隔符
    • FS为多个字符时,则采用正则表达式模式作为字段分隔符
    • 特殊的,也是FS默认的情况,FS为单个空格时,将以连续的空白(空格、制表符、换行符)作为字段分隔符
    • 特殊的,FS为空字符串""时,将对每个字符都进行分隔,即每个字符都作为一个字段
    • 设置预定义变量IGNORECASE为非零值,正则匹配时表示忽略大小写(只影响正则,所以FS为单字时无影响)
    • 如果record中无法找到FS指定的分隔符(例如将FS设置为"\n"),则整个记录作为一个字段,即 1 和0 相 等

    FIELDWIDTHS

    指定预定义变量FIELDWIDTHS按字符宽度分割字段,这是gawk提供的高级功能。在处理某字段缺失时非常好用。
    用法:

    • FIELDWIDTHS="3 5 6 9" 表示第一个字段3字符,第二字段5字符...
    • FIELDWIDTHS = "8 1:5 6 2:33"表示:
      • 第一个字段读8个字符
      • 然后跳过1个字符再读5个字符作为第二个字段
      • 然后读6个字符作为第三个字段
      • 然后跳过2个字符在读33个字符作为第四个字段(如果不足33个字符,则读到结尾)
    • FIELDWIDTHS="2 3 *" :
      • 第一个字段2个字符
      • 第二个字段3个字符
      • 第三个字段剩余所有字符 星号只能放在最后,且只能单独使用,表示剩余所有
    • 设置该变量后,FS失效
    • 之后再设置FS或FPAT,
      示例1:
    # 没取完的字符串DDD被丢弃,且NF=3
    $ awk 'BEGIN{FIELDWIDTHS="2 3 2"}{print $1,$2,$3,$4}' <<<"AABBBCCDDDD" AA BBB CC
    # 字符串不够长度时无视
    $ awk 'BEGIN{FIELDWIDTHS="2 3 2 100"}{print $1,$2,$3,$4"-"}' <<<"AABBBCCDDDD" AA BBB CC DDDD-
    # *号取剩余所有,NF=3
    $ awk 'BEGIN{FIELDWIDTHS="2 3 *"}{print $1,$2,$3}' <<<"AABBBCCDDDD" AA BBB CCDDDD
    # 字段数多了,则取完字符串即可,NF=2
    $ awk 'BEGIN{FIELDWIDTHS="2 30 *"}{print $1,$2,NF}' <<<"AABBBCCDDDD" AA BBBCCDDDD 2
    

    示例2:处理某些字段缺失的数据。
    如果按照常规的FS进行字段分割,则对于缺失字段的行和没有缺失字段的行很难统一处理,但使用FIELDWIDTHS则非常方便。
    假设a.txt文本内容如下:

    ID  name    gender  age  email          phone
    1   Bob     male    28   abc@qq.com     18023394012
    2   Alice   female  24   def@gmail.com  18084925203
    3   Tony    male    21   aaa@163.com    17048792503
    4   Kevin   male    21                  17023929033
    5   Alex    male    18   ccc@xyz.com    18185904230
    6   Andy    female  22   ddd@139.com    18923902352
    7   Jerry   female  25   exdsa@189.com  18785234906
    8   Peter   male    20   bax@qq.com     17729348758
    9   Steven  female  23   bc@sohu.com    15947893212
    10  Bruce   female  27   bcbd@139.com   13942943905
    

    因为email字段有的是空字段,所以直接用FS划分字段不便处理。可使用FIELDWIDTHS。

    # 字段1:4字符
    # 字段2:8字符
    # 字段3:8字符
    # 字段4:2字符
    # 字段5:先跳过3字符,再读13字符,该字段13字符
    # 字段6:先跳过2字符,再读11字符,该字段11字符
    awk '
    BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
    NR>1{
        print "<"$1">","<"$2">","<"$3">","<"$4">","<"$5">","<"$6">"
    }' a.txt
    # 如果email为空,则输出它
    awk '
    BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"} NR>1{
        if($5 ~ /^ +$/){print $0}
    }' a.txt
    

    FPAT

    FS是指定字段分隔符,来取得除分隔符外的部分作为字段。
    FPAT是取得匹配的字符部分作为字段。它是gawk提供的一个高级功能。
    FPAT根据指定的正则来全局匹配record,然后将所有匹配成功的部分组成 1、2... ,不会修改 $0 。

    • awk 'BEGIN{FPAT="[0-9]+"}{print $3"-"}' a.txt
    • 之后再设置FS或FPAT,该变量将失效
      FPAT常用于字段中包含了字段分隔符的场景。例如,CSV文件中的一行数据如下:
    Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA
    

    其中逗号分隔每个字段,但双引号包围的是一个字段整体,即使其中有逗号。
    这时使用FPAT来划分各字段比使用FS要方便的多。

    echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
    awk '
        BEGIN{FPAT="[^,]*|(\"[^\"]*\")"}
        {
            for (i=1;i<NF;i++){
                print "<"$i">"
            } 
        }
    '
    

    最后,patsplit()函数和FPAT的功能一样。

    检查字段分隔的方式

    有FS、FIELDWIDTHS、FPAT三种获取字段的方式,可使用 PROCINFO 数组来确定本次使用何种方式获得字段。
    PROCINFO是一个数组,记录了awk进程工作时的状态信息。

    • PROCINFO["FS"]=="FS",表示使用FS分割获取字段
    • PROCINFO["FPAT"]=="FPAT" ,表示使用FPAT匹配获取字段
    • PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS",表示使用FIELDWIDTHS分割获取字段
      例如:
    if(PROCINFO["FS"]=="FS"){
        ...FS spliting...
    } else if(PROCINFO["FPAT"]=="FPAT"){
        ...FPAT spliting...
    } else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
        ...FIELDWIDTHS spliting...
    }
    

    修改字段或NF值的联动效应

    注意下面的分割和计算两词:分割表示使用FS(field Separator),计算表示使用预定义变量OFS(Output Field Separator)。

    1. 修改 0 ,将使用 FS 重新分割字段,所以会影响1、$2...
    2. 修改 1、2 ,将根据 1 到NF 来重新计算 $0
      • 即使是 1 =1 这样的原值不变的修改,也一样会重新计算 $0
    3. 为不存在的字段赋值,将新增字段并按需使用空字符串填充中间的字段,并使用 OFS 重新计算 $0
      • awk '{(NF+2)=5;print0}' OFS='-' a.txt
    4. 增加NF值,将使用空字符串新增字段,并使用 OFS 重新计算 $0
      • awk '{NF+=3;print $0}' OFS='-' a.txt
    5. 减小NF值,将丢弃一定数量的尾部字段,并使用 OFS 重新计算 $0
      • awk '{NF-=3;print $0}' OFS='-' a.txt

    关于$0

    当读取一条record之后,将原原本本地被保存到 $0 当中。

    awk '{print $0}' a.txt
    

    但是,只要出现了上面所说的任何一种导致 0 重新计算的操作,都会立即使用OFS去重建0 。
    换句话说,没有导致 0 重建,0就一直是原原本本的数据,所以指定OFS也无效。

    awk '{print $0}' OFS="-" a.txt # OFS此处无效
    

    当 $0 重建后,将自动使用OFS重建,所以即使没有指定OFS,它也会采用默认值(空格)进行重建。

    awk '{$1=$1;print $0}' a.txt # 输出时将以空格分隔各字段
    awk '{print $0;$1=$1;print $0}' OFS="-" a.txt
    

    如果重建 $0 之后,再去修改OFS,将对当前行无效,但对之后的行有效。所以如果也要对当前行生效,需要再次重 建。

    # OFS对第一行无效
    awk '{$4+=10;OFS="-";print $0}' a.txt
    # 对所有行有效
    awk '{$4+=10;OFS="-";$1=$1;print $0}' a.txt
    

    关注 0 重建是一个非常有用的技巧。\ 例如,下面通过重建0 的技巧来实现去除行首行尾空格并压缩中间空格

    $ echo " a b c d " | awk '{$1=$1;print}'
    a b c d
    $ echo " a b c d " | awk '{$1=$1;print}' OFS="-" 
    a-b-c-d
    

    awk数据筛选示例

    筛选行

    # 1.根据行号筛选
    awk 'NR==2' a.txt # 筛选出第二行
    awk 'NR>=2' a.txt # 输出第2行和之后的行
    
    # 2.根据正则表达式筛选整行
    awk '/qq.com/' a.txt # 输出带有qq.com的行 
    awk '$0 ~ /qq.com/' a.txt # 等价于上面命令
    awk '/^[^@]+$/' a.txt # 输出不包含@符号的行 
    awk '!/@/' a.txt # 输出不包含@符号的行
    
    # 3.根据字段来筛选行
    awk '($4+0) > 24{print $0}' a.txt # 输出第4字段大于24的行 
    awk '$5 ~ /qq.com/' a.txt # 输出第5字段包含qq.com的行
    
    # 4.将多个筛选条件结合起来进行筛选
    awk 'NR>=2 && NR<=7' a.txt
    awk '$3=="male" && $6 ~ /^170/' a.txt 
    awk '$3=="male" || $6 ~ /^170/' a.txt
    
    # 5.按照范围进行筛选 flip flop
    # pattern1,pattern2{action}
    awk 'NR==2,NR==7' a.txt # 输出第2到第7行 
    awk 'NR==2,$6 ~ /^170/' a.txt
    

    处理字段

    修改字段时,一定要注意,可能带来的联动效应:即使用OFS重建$0。

    awk 'NR>1{$4=$4+5;print $0}' a.txt
    awk 'NR>1{$6=$6"*";print $0}' a.txt
    

    awk运维面试试题

    从ifconfig命令的结果中筛选出除了lo网卡外的所有IPv4地址。

    # 1.法一:
    ifconfig | awk '/inet / && !($2 ~ /^127/){print $2}'
    # 2.法二:
    ifconfig | awk 'BEGIN{RS=""}!/lo/{print $6}'
    # 3.法三:
    ifconfig | awk 'BEGIN{RS="";FS="\n"}!/lo/{$0=$2;FS=" ";$0=$0;print $2}'
    

    awk工作流程

    参考自: man awk 的"AWK PROGRAM EXECUTION"段。

    man --pager='less -p ^"AWK PROGRAM EXECUTION" awk
    

    执行步骤

    1. 解析 -v var=val... 选项中的变量赋值
    2. 编译awk源代码为awk可解释的内部格式,包括-v的变量
    3. 执行BEGIN代码段
    4. 根据输入记录分隔符RS读取文件(根据ARGV数组的元素决定要读取的文件),如果没有指定文件,则从标准 输入中读取文件,同时执行main代码段
      • 如果文件名部分指定为 var=val 格式,则声明并创建变量,此阶段的变量在BEGIN之后声明,所以 BEGIN中不可用,main代码段可用
      • 每读取一条记录:
        • 都将设置NR、FNR、RT、$0等变量
        • (默认)根据输入字段分隔符FS切割字段,将各字段保存到 1、2... 中
        • 测试main代码段的pattern部分,如果测试成功则执行action部分
    5. 执行END代码段

    相关文章

      网友评论

          本文标题:awk--基本操作一

          本文链接:https://www.haomeiwen.com/subject/yjesmltx.html