美文网首页
Unix文本处理工具之awk

Unix文本处理工具之awk

作者: SpaceCat | 来源:发表于2015-11-12 23:31 被阅读217次

    Unix命令行下输入的命令是文本,输出也都是文本。因此,掌握Unix文本处理工具是很重要的一种能力。awk是Unix常用的文本处理工具中的一种,它是以其发明者(Aho,Weinberger和Kernighan)的名字首字符命名的,是一种基于模式匹配检查输入然后将期望的匹配结果处理后输出到屏幕的文本数据处理工具。

    1、awk命令格式

    awk ‘模式 {操作}’ 文件1 文件2 ……

    awk命令的工作过程是这样的:对于每一个输入文件,逐行对其进行检查,如果该行和awk命令参数的‘模式’部分匹配,则对该行执行命令参数‘{操作}’部分所代表的操作。下面是一个简单的例子:

    $cat awk_test.txt
    1 a a,b,d,f
    2 b alsdjf,apple,kdjf
    3 c 163.2.201.1
    4 d www.google.com
    5 e http://blog.csdn.net/xia7139
    $awk 'NR==1{print}' awk_test.txt
    1 a a,b,d,f
    

    上面的例子中,用awk命令输出了awk_test.txt文件的第一行,其中命令的模式部分所用的NR是awk命令的内建变量,代表文件的行号。这样,便可以对所有行号为1的行进行打印输出。

    2、语法说明

    2.1 常用的内建变量

    变量 含义
    NR 当前处理行的行号
    FS 字段分隔,默认为空格或TAB
    $n 当前处理行的第n个字段
    $0 当前处理行的全部内容
    $NF 表示当前处理行的最后一个字段

    这里要解释下字段的含义:在awk的使用中,字段分隔符将文件的一行分隔为各个部分,每一个部分称为一个字段,从左到右分别为第1个字段,……,第n个字段,其中,第0个字段是指这一整行。字段分隔符如果没有特殊指定,则默认为空格或tab制表符。

    2.2 在字段中匹配

    awk可以支持根据特定字段内容的匹配,操作符是~。该操作符的否定是!~,表示不匹配。下面是几个例子:

    • 输出第三个字段包含a的行。

        $ awk '$3 ~ /a/ {print}' awk_test.txt 
        1 a a,b,d,f
        2 b alsdjf,apple,kdjf
        5 e http://blog.csdn.net/xia7139
      
    • 输出第三个字段不包含a的行

        $ awk '$3 !~ /a/ {print}' awk_test.txt 
        3 c 163.2.201.1
        4 d www.google.com
      

    2.3 awk变量

    awk命令是支持变量的,定义变量的选项是-v,下面是一个例子。

    • 打印第二个字段包含b的行

        $ var=b
        $ awk -v x=$var '$2 ~ x{print}' awk_test.txt 
        2 b alsdjf,apple,kdjf
      
    • 打印第二个字段不包含b的行

        $ var=b
        $ awk -v x=$var '$2 !~ x{print}' awk_test.txt 
        1 a a,b,d,f
        3 c 163.2.201.1
        4 d www.google.com
        5 e http://blog.csdn.net/xia7139
      

    但是,变量在//中不起作用:

    $ var=b
    $ awk -v x=$var '$3 ~ /x/{print}' awk_test.txt 
    5 e http://blog.csdn.net/xia7139
    

    可以认为awk -v x=$var '$2 ~ x{print}' awk_test.txt就相当于awk '$2 ~ /b/{print}' awk_test.txt,也就是说会将变量的值当作正则表达式匹配。下面的例子可以说明这个问题:

    $ var=.
    $ awk -v x=$var '$3 ~ x{print}' awk_test.txt 
    1 a a,b,d,f
    2 b alsdjf,apple,kdjf
    3 c 163.2.201.1
    4 d www.google.com
    5 e http://blog.csdn.net/xia7139
    
    $ var=\\\\.
    $ awk -v x=$var '$3 ~ x{print}' awk_test.txt 
    3 c 163.2.201.1
    4 d www.google.com
    5 e http://blog.csdn.net/xia7139
    

    2.4 awk默认的行分隔符和列分隔符

    awk中有两个特殊的变量ORS和OFS分别记录着其缺省的行分隔符,ORS的默认值为换行\n,OFS的默认值为空格。因此,默认的awk会用它们来分隔行和列。下面是几个例子:

    • 当我们试图用=分隔各个字段时

        $ awk '{print $1,"=",$2,"=",$3}' awk_test.txt 
        1 = a = a,b,d,f
        2 = b = alsdjf,apple,kdjf
        3 = c = 163.2.201.1
        4 = d = www.google.com
        7 = d = www.google.com
        4 = d = www.googlecom
        5 = e = http://blog.csdn.net/xia7139
      

      可以看到,这样=被作为awk的一个字段输出,和其它字段之间用空格隔开,实际上相当于" = "作为了分隔符分隔了各个原来的字段。

    • 只用=分隔各个字段

      这时候就需要将OFS置为空字符串,如下:

        $ awk 'BEGIN{OFS=""}{print $1,"=",$2,"=",$3}' awk_test.txt 
        1=a=a,b,d,f
        2=b=alsdjf,apple,kdjf
        3=c=163.2.201.1
        4=d=www.google.com
        7=d=www.google.com
        4=d=www.googlecom
        5=e=http://blog.csdn.net/xia7139
      
    • 同理,可实现在行与行之间添加空行

        $ awk 'BEGIN{ORS="\n\n"}{print $1,"=",$2,"=",$3}' awk_test.txt | head -n 5
        1 = a = a,b,d,f
        
        2 = b = alsdjf,apple,kdjf
        
        3 = c = 163.2.201.1
      

    2.5 awk中的BEGIN和END

    BEGIN作用是执行一些初始化操作,END的作用是程序结束后执行扫尾的工作。

    任何在BEGIN之后列出的操作(在{}内)将在Unix awk开始扫描输入之前执行,而END之后列出的操作将在扫描完全部的输入之后执行。因此,通常使用BEGIN来显示变量和预置(初始化)变量,使用END来输出最终结果。

    下面是一个例子:

    $ awk 'BEGIN{product=1}{print $1,"=",$2,"=",$3;product=product*$1}END{printf "product: %.3f\n",product}' awk_test.txt
    1 = a = a,b,d,f
    2 = b = alsdjf,apple,kdjf
    3 = c = 163.2.201.1
    4 = d = www.google.com
    7 = d = www.google.com
    4 = d = www.googlecom
    5 = e = http://blog.csdn.net/xia7139
    product: 3360.000
    $ 
    

    3、几个例子及其输出

    3.1 下面的例子都是对上文中的awk_test.txt文件的操作

    3.1.1 按行号操作

    • 打印文件的1-3行

        $awk 'NR==1,NR==3{print}' awk_test.txt
        1 a a,b,d,f
        2 b alsdjf,apple,kdjf
        3 c 163.2.201.1
      
    • 打印文件的第1行和第3行

        $awk 'NR==1||NR==3{print}' awk_test.txt
        或者是
        $awk '(NR==1)||(NR==3){print}' awk_test.txt
        1 a a,b,d,f
        3 c 163.2.201.1
      
    • 只打印奇数行(偶数行)

        $awk '(NR%2)==1{print}' awk_test.txt
        1 a a,b,d,f
        3 c 163.2.201.1
        5 e http://blog.csdn.net/xia7139
        $awk '(NR%2)==0{print}' awk_test.txt
        2 b alsdjf,apple,kdjf
        4 d www.google.com
      

    3.1.2 使用正则表达式

    • 打印包含2的行

        $awk '/2/{print}' awk_test.txt
        2 b alsdjf,apple,kdjf
        3 c 163.2.201.1
      
    • 打印以com结尾的行

        $awk '/com$/{print}' awk_test.txt
        4 d www.google.com
      

    3.1.3 指定分隔,输出指定字段

    • 打印第1-3行的第一个字段和第三个字段

        $awk 'NR==1,NR==3{print $1,$3}' awk_test.txt
        1 a,b,d,f
        2 alsdjf,apple,kdjf
        3 163.2.201.1
      
    • 指定分隔符为.,输出第二个字段为csdn的行的第三个字段和整行

        $awk -F. '$2=="csdn"{print $3,$0}' awk_test.txt
        net/xia7139 5 e http://blog.csdn.net/xia7139
      
    • 指定分隔符为.,输出每行的最后一个字段

        $ awk -F. '{print $NF}' awk_test.txt 
        1 a a,b,d,f
        2 b alsdjf,apple,kdjf
        1
        com
        net/xia7139
      

    3.2 awk对文件中的行按重复次数排序

    下面的文件是从数据库中导出的一些数据(一部分),但是后续发现有些字段不需要。而如果重新从数据库中导出生成的话,耗费的时间太长,这里就用到了awk命令了。

    文件test.txt的内容如下,每行有三个字段,字段之间用“ ::: ”隔开:

    11 ::: Thomas R. Dean ::: 54
    14 ::: Johann van Rensburg ::: 1
    75 ::: Arun G. Phadke ::: 13
    81 ::: Tiffany M. Frazier ::: 2
    84 ::: Sridhar R. Iyer ::: 1
    95 ::: Leesa Murray ::: 11
    96 ::: David S. Munro ::: 34
    104 ::: David R. Lovell ::: 2
    112 ::: Steffen Rusitschka ::: 3
    161 ::: Peter Forbrig ::: 116
    

    现在想只取出第后面的两个字段去掉前面的字段:

    $ awk -F :::  '{print $2,$3}' test.txt
     Thomas R. Dean   54
     Johann van Rensburg   1
     Arun G. Phadke   13
     Tiffany M. Frazier   2
     Sridhar R. Iyer   1
     Leesa Murray   11
     David S. Munro   34
     David R. Lovell   2
     Steffen Rusitschka   3
     Peter Forbrig   116
    

    发现前面有多余的不想要的空格,一点都不优雅。原来-F指定的分隔符是要将空格转义才能生效。

    $ awk -F\ :::\  '{print $2,$3}' test.txt
    Thomas R. Dean 54
    Johann van Rensburg 1
    Arun G. Phadke 13
    Tiffany M. Frazier 2
    Sridhar R. Iyer 1
    Leesa Murray 11
    David S. Munro 34
    David R. Lovell 2
    Steffen Rusitschka 3
    Peter Forbrig 116
    

    这样就好多了,但是,现在又想将上面的两个字段还是用原来的“ ::: ”隔开。

    $ awk -F\ :::\  '{print $2,":::",$3}' test.txt
    Thomas R. Dean ::: 54
    Johann van Rensburg ::: 1
    Arun G. Phadke ::: 13
    Tiffany M. Frazier ::: 2
    Sridhar R. Iyer ::: 1
    Leesa Murray ::: 11
    David S. Munro ::: 34
    David R. Lovell ::: 2
    Steffen Rusitschka ::: 3
    Peter Forbrig ::: 116
    

    Wow, it is beautiful!
    下面如果发现有重复的话,可以进行进一步的去重。这里随意生成了一个有重复的test.txt来进行操作,其内容如下:

    11 ::: Thomas R. Dean ::: 54
    1411 ::: Johann van Rensburg ::: 1
    106 ::: Peter Forbrig ::: 116 
    141 ::: Johann van Rensburg ::: 1
    143 ::: Johann van Rensburg ::: 1
    75 ::: Arun G. Phadke ::: 13
    844 ::: Sridhar R. Iyer ::: 1
    149 ::: Johann van Rensburg ::: 1
    81 ::: Tiffany M. Frazier ::: 2
    84 ::: Sridhar R. Iyer ::: 1
    95 ::: Leesa Murray ::: 11
    96 ::: David S. Munro ::: 34
    104 ::: David R. Lovell ::: 2
    15 ::: Johann van Rensburg ::: 1
    112 ::: Steffen Rusitschka ::: 3
    12 ::: Steffen Rusitschka ::: 3
    161 ::: Peter Forbrig ::: 116 
    106 ::: Peter Forbrig ::: 116
    

    首先对awk生成的结果排序:

    $ awk -F\ :::\  '{print $2,":::",$3}' test.txt | sort
    Arun G. Phadke ::: 13
    David R. Lovell ::: 2
    David S. Munro ::: 34
    Johann van Rensburg ::: 1
    Johann van Rensburg ::: 1
    Johann van Rensburg ::: 1
    Johann van Rensburg ::: 1
    Johann van Rensburg ::: 1
    Leesa Murray ::: 11
    Peter Forbrig ::: 116
    Peter Forbrig ::: 116
    Peter Forbrig ::: 116
    Sridhar R. Iyer ::: 1
    Sridhar R. Iyer ::: 1
    Steffen Rusitschka ::: 3
    Steffen Rusitschka ::: 3
    Thomas R. Dean ::: 54
    Tiffany M. Frazier ::: 2
    

    然后,进行去重,之所以进行排序,是因为uniq命令只能对相邻行进行去重。

    $ awk -F\ :::\  '{print $2,":::",$3}' test.txt | sort | uniq 
    Arun G. Phadke ::: 13
    David R. Lovell ::: 2
    David S. Munro ::: 34
    Johann van Rensburg ::: 1
    Leesa Murray ::: 11
    Peter Forbrig ::: 116
    Sridhar R. Iyer ::: 1
    Steffen Rusitschka ::: 3
    Thomas R. Dean ::: 54
    Tiffany M. Frazier ::: 2
    

    如果需要根据重复次数排序,可以用“awk -F\ :::\ '{print $2,":::",$3}' test.txt | sort | uniq -c | sort -rn”这里sort的-n选项是指定根据每行第一个字段的数字值的大小排序,比如30比4大,如果没有-n那么就是默认字典序排,4比30大。而unique中的-c选项是指定在每行之前加一个重复次数字段。
    这里为了排序用sort -n先将数字字段放到前面,然后排序,排完之后,再将数字字段放到后面(实际上,可以用更加优雅的方法,直接指定按第二个字段排序就可以,这里用的是先颠过来,然后再倒回去的方法。

    $ awk -F\ :::\  '{print $3,":::",$2}' test.txt | sort | uniq | sort -rn | awk -F\ :::\  '{print $2,":::",$1}'
    Peter Forbrig ::: 116
    Thomas R. Dean ::: 54
    David S. Munro ::: 34
    Arun G. Phadke ::: 13
    Leesa Murray ::: 11
    Steffen Rusitschka ::: 3
    Tiffany M. Frazier ::: 2
    David R. Lovell ::: 2
    Sridhar R. Iyer ::: 1
    Johann van Rensburg ::: 1
    

    3.3 awk命令将文本文件中的数字相加

    这里有一个文本文件,其中,每行的第二个字段是一个数字,现在想要将每行的数字加起来,应该如何操作呢?下面给出awk命令的版本:

    $ cat awk_sum_test.txt
    apple 1
    google 2
    sammung 3
    moto 4
    xiaomi 5
    smartisan 6
    oppo 7
    huawei 8
    coolpad 9
    lenevo 10
    $ awk '{sum+=$2}END{print sum}' awk_sum_test.txt 
    55
    $ 
    

    从这个命令可以看出,awk命令的使用还是比较方便的。初次之外,要知道awk命令的语法十分复杂,上面说到的只是很少的一部分。从这个例子中的用法来看,我们也能够知道前面提到的awk命令的基本格式awk ‘模式 {操作}’ 文件1 文件2 ……中的,'模式 {操作}'单位实际上,是可以有多个的,也就是说可以是awk ‘模式 {操作}模式 {操作}... ...’ 文件1 文件2 ……。awk会逐个检查每个模式,然后对符合模式的行执行相应的操作。下面是一个例子:

    $ awk '{sum+=$2}NR <5 1{print $1}END{print sum}' awk_sum_test.txt    
    apple
    google
    sammung
    moto
    xiaomi
    lenevo
    55
    $ 
    

    实际上,这个例子中的问题也可以用linux shell下读取文件的方法来解决,只不过稍微有点麻烦。关于awk命令,个人认为只能是边用边学,以后遇到比较好的例子,还会贴在在这里。_

    相关文章

      网友评论

          本文标题:Unix文本处理工具之awk

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