写在前面:如果此文有幸被某位朋友看见并发现有错的地方,希望批评指正。如有不明白的地方,愿可一起探讨。



awk是个什么东西


    最简单地说,AWK是一个用于处理文本的编程语言工具,是linux及UNIX环境中现有的功能最强大的数据处理引擎之一。AWK提供了极其强大的功能:可以进行正则表达式的匹配,样式装入,流控制,数学运算符,程序控制语句以及甚至拥有内置的变量和函数。它具备了一个完整的语言所应该具有的特性,实际上AWK的确拥有自己的语言:AWK程序设计语言,三位创建者(Alfred Aho, Peter Weinberger, and Brian Kernighan)已将其定义为“样式扫描和处理语言”。以上内容引用于维基百科。


AWK基本语法结构及其工作机制


    AWK基本语法结构

        awk [options] 'program' file file ...

        awk [options] 'PATTERN{action}' file file ...

    AWK的工作机制

        首先根据模式去读取文件的某行或者多行,如果没有给模式匹配,就去读取文件的所有行;

        然后将所读取的行以输入分隔符(默认情况下,输入分隔符为空白)进行切片,将所切取的每片赋值给内建变量$0(代表整行),$1(代表第一个字段),$2(代表第二个字段),...;

        最后根据语法结构中的action来对各个字段进行相应的处理。

    AWK如何处理各行

        AWK自身具有遍历功能,其主要作用是遍历文件中的所有行;那么AWK如何处理各行字段呢?那就得用到循环功能了。


    接下来,本文将描述基本语法结构中的program和PATTERN{action};而AWK的[options]非常简单,并且可以包含在program和PATTERN{action}中,因此本文将一笔带过。

    

AWK的options


   先感受一下awk的使用方法

        -F CHAR:输入分隔符

        # awk -F : '{print}' /etc/passwd

        # awk -F : '{print $1 $7}' /etc/passwd

        # awk -F : '{print "hello"}' /etc/passwd

        # awk -F : '{print "hello" $1}' /etc/passwd

        # awk -F : '{print 100}' /etc/passwd

    AWK的options真的很简单,更多的options建议查看帮助手册


AWK的输出


    print item1,item2,...

    基本要点:

        (1)各项之间使用逗号分隔,而输出时则使用输出分隔符分隔(默认情况下为空白字符),而不是逗号分隔;

        (2)输出的各项可以为字符串、数值、当前记录的字段、变量以及awk表达式;注意:数值会被隐式转换为字符串后输出。想想为什么?(显示器本身就是字符设备)。

        (3)如果print后面的item被省略,相当于print $0;如果想输出空白,则使用print ""。


AWK的变量


    AWK的变量分为内置变量和自定义变量

    内置变量:

        FS: Field Seperator,输入时的字段分隔符;

    # awk 'BEGIN{FS=":"}{print $1,$7}' /etc/passwd

            # awk -F : '{print $1,$7}' /etc/passwd

            比较以上两条命令的输出结果有何不同?其实没有什么不一样。

                这里想说的是:-F CHAR 是选项,与内置变量不是同一概念

        

        RS: Record Seperator,输入时的行分隔符;

            # awk 'BEGIN{RS=":"}{print $1,$7}' /etc/passwd

            # awk 'BEGIN{FS=":"}{print $1,$7}' /etc/passwd

            比较以上两条命令的输出结果有何不同,就可知道行分隔符的概念了,默认情况下为\n。

           仔细比较你会发现:

                第七字段为空,而执行# awk 'BEGIN{RS=":"}{print $1,$7}' /etc/passwd命令的结果中发现少了一些字段输出,那是因为我们并没有输出换行符接下来的那个字段(不知道它属于哪个字段???)。试一试这条命令:

            # awk 'BEGIN{RS=":"}{print $1,$ARGC}' /etc/passwd,查看结果就明白了。

        

        OFS: Output Field Seperator,输出时的字段分隔符;

    # awk 'BEGIN{FS=":";OFS=":"}{print $1,$7}' /etc/passwd

                注意:定义两个变量时,需要用;分隔开来(语句之间需要分号分割)

    # awk 'BEGIN{FS=":";OFS=":"}{print $1 $7}' /etc/passwd

    # awk 'BEGIN{FS=":";OFS=":"}{print $1$7}' /etc/passwd

                注意比较以上两条命令的执行结果,看看是什么样子

        

        ORS: Outpput Row Seperator, 输出时的行分隔符;

            # awk 'BEGIN{FS=":";ORS=":"}{print $1,$7}' /etc/passwd

            # awk 'BEGIN{FS=":";ORS="###"}{print $1,$7}' /etc/passwd

        

        NF:Numbers of Field,每行的字段数;


        NR:Numbers of Record, 行数;所有文件的一并计数;

    此概念比较诡异,存在多个文件时,是单独记数,还是整体记数??所有文件一并记数


        FNR:行数;各文件分别计数;

    与NR相对应??测试一下就知道

    # awk 'BEGIN{FS=":";OFS=":"}{print FNR,$1,$7}' /etc/passwd /etc/group

    # awk 'BEGIN{FS=":";OFS=":"}{print NR,$1,$7}' /etc/passwd /etc/group


    # awk 'BEGIN{FS=":";OFS=":"}{print FNR,$1,$7}' /etc/passwd

        注意,上例中的FNR不能加$,加$表示的是引用字段,测试一下就知道:

                    # awk 'BEGIN{FS=":";OFS=":"}{print $FNR,$1,$7}' /etc/passwd

        

            ARGV:数组,例如:awk '{print $0}' 1.txt 2.txt,意味着ARGV[0]保存awk;

        注意'{print $0}'并不保存,也即ARGV[1]保存1.txt

        # awk 'BEGIN{print ARGV[0],ARGV[1]}' /etc/passwd /etc/group

                注意print不能大写


            ARGC: 保存awk命令中参数的个数;

        # awk 'BEGIN{print ARGV[0],ARGV[1],ARGC}' /etc/passwd /etc/group


            FILENAME: awk正在处理的当前文件的名称;

        # awk 'BEGIN{FS=":";OFS="-->"}{print $1,FILENAME}' /etc/passwd

    

    用户可自定义变量:

        -v var_name=VALUE

    注意,变量名区分字符大小写;


    (1)可以在program中定义变量;

    (2)可以在命令行中通过-v选项自定义变量;

        # awk 'BEGIN{a="hello awk"}{print a}' /etc/passwd

        # awk 'BEGIN{a="hello awk";print a}'

    表示不对文件进行处理,因为BEGIN这个模式跟文件没有关系,只是在处理文件前起作用

        # awk -v a="hello awk" 'BEGIN{print a}'

            与# awk 'BEGIN{a="hello awk";print a}'效果一样


awk的printf命令


    如果你学过C语言,你会发现这个printf似曾相识,是不是有种亲切感,^_^。

    命令格式:

        printf format, item1,item2,...

    基本要点:

        (1)format需要指定;

        (2)format用于为后面的每个item指定其输出格式;

        (3)不会自动换行;如需换行需要在format中添加\n;

    format格式的指示符都以%开头:

        

        %c: 显示字符的ASCII码;

        %d, %i: 十进制整数;

        %e, %E: 科学计数法显示数值;

        %f: 显示浮点数;

        %g, %G: 以科学计数法格式或浮点数格式显示数值;

        %s: 显示字符串;

        %u: 显示无符号整数;

        %%: 显示%自身;


        修饰符(输出格式的修饰符):

            #:(数字)显示宽度  

            -:左对齐,默认右对齐

            +:显示数值的符号

            .#: 取值精度

    

        # awk -F : '{printf "%-20s %-30s\n",$1,$7}' /etc/passwd

        # awk -F : '{printf "%-20s, %-30s\n",$1,$7}' /etc/passwd

            比较以上两个命令的输出结果,看有什么不同

            "%-20s, %-30s\n"中的逗号(,)会原样输出


        # awk 'BEGIN{printf "%f\n",3.1415}'

        # awk 'BEGIN{printf "%f\n",3.1415926}'

            比较以上两个命令的输出结果,看有什么不同


        # awk 'BEGIN{printf "%g\n",3.1415926}'


        # awk 'BEGIN{printf "%e\n",3.1415926}'

        # awk 'BEGIN{printf "%e\n",31415.926}'

        比较上面两条命令,看有什么不同,注意点(.)的位置


        # awk 'BEGIN{printf "%15e\n",31415.926}'

        

        # awk 'BEGIN{printf "%15.2e\n",31415.926}'

        # awk 'BEGIN{printf "%-15.2e\n",31415.926}'


AWK的输出重定向


    

    print items > output-file

    print items >> output-file

    print items | command


    特殊文件描述符:FD-->File Description

        进程打开某文件时,内核就会追踪这个文件且会使用很小的数字代表这个文件

        标准输入(/dev/stdin): 代码为0,<或<<

        标准输出(/dev/stdout): 代码为1,使用1>或1>>

        错误输出(/dev/stderr):  代码为2,使用2>或2>>


AWK的操作符


    算术操作符:

        x+y,x-y,x*y,x/y,x**y,x^y,x%y,-x:负值,+x:转换为数值


    字符串操作符:连接


    赋值操作符:

        =,+=,-=,*=,/=,%=,^=,**=,++,--

        如果模式自身是=号,要写为/=/


    比较操作符:

        <,<=,>,>=,==,!=

        ~:模式匹配,左边的字符串能够被右边的模式所匹配为真,否则为假;

        !~:与~相反


    逻辑操作符:

        &&: 与

        ||:或


    条件表达式:

        selector?if-true-expression:if-false-expression

            注意,表达式与语句的区别;一般说来表达式有值,而语句不一定有值

        # awk -F: '{$3>=500?utype="common user":utype="admin or system user";print $1,"is",utype}' /etc/passwd


    函数调用:

        function_name(argu1,argu2)


AWK的模式


    (1) Regexp: 格式为/PATTERN/

        仅处理被/PATTERN/匹配到的行;

        # awk -F : /root/ '{print $0}' /etc/passwd

        # grep 'root' /etc/passwd

            注意:如果仅过滤文本,不建议使用awk


    (2) Expression: 表达式,其结果为非0或非空字符串时满足条件;

        仅处理满足条件的行,一般“比较表达式”居多

        # awk -F : '$3>=500{print $1,$3}' /etc/passwd


    (3) Ranges: 行范围,此前称为地址定界,startline, endline

        仅处理范围内的行;

        # awk '/root/,/bin/{print $0}' /etc/passwd /etc/group


    (4) BEGIN/END: 特殊模式,仅在awk命令的program运行前(BEGIN)或运行后(END)执行一次;

        BEGIN一般写在action之前,END一般写在action后

        BEGIN用来定义表头以及变量

        END用来定表尾


    (5) Empty:空模式,匹配任意行;


AWK的控制语句


    8.1 if-else

        格式:if (condition) {then body} else {else body}

        # awk -F: '{if ($3>=500) {print $1,"is a common user"} else {print $1, "is an admin or system user"}}' /etc/passwd

        # awk '{if (NF>=8) {print $0}}' /etc/inittab


    8.2 while

        格式:while (condition) {while body}


        # awk '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print ""}' /etc/inittab


        # awk -F : '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print ""}' /etc/passwd

        # awk -F : '{i=1; while (i<=NF){printf "%s ",$i;i+=2};}' /etc/passwd

            思考,为什么上面两行输出结果的格式有所差别??


        # awk '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print "\n"}' /etc/passwd

        # awk -F : '{i=1; while (i<=NF){printf "%s ",$i;i+=2};print "\n"}' /etc/passwd


        # awk '{i=1; while (i<=NF){if (length($i)>=6) {print $i}; i++}}' /etc/inittab

        # awk -F : '{i=1; while (i<=NF){if (length($i)>=6) {print $i}; i++}}' /etc/passwd

            length()函数:取字符串的长度


    8.3 do-while循环

        格式:do {do-while body} while (condition)

    8.4 for循环

        格式:for (variable assignment; condition; iteration process) {for body}

        # awk '{for (i=1;i<=NF;i+=2){printf "%s ",$i};print ""}' /etc/inittab

        # awk '{for (i=1;i<=NF;i++){if (length($i)>=6) {print $i}}}' /etc/inittab


            for循环可用来遍历数组元素:

                语法:for (i in array) {for body}

                    遍历的不是元素,而是元素的索引


    8.5 case语句

        语法:switch (expression) {case VALUE or /RGEEXP/: statement1;... default: stementN}


    8.6 循环控制

        break

        continue


    8.7 next

        提前结束对本行的处理进而进入下一行的处理;

        # awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd

        # awk -F: '{if(NR%2==0) next; print NR,$1}' /etc/passwd


AWK的数组


    关联数组:

        array[index-expression]

        index-expression: 可以使用任意字符串; 如果某数组元素事先不存在,那么在引用时,awk会自动创建此元素并将其初始化为空串;因此,要判断某数组是否存在某元素,必须使用“index in array”这种格式;


        A[first]="hello awk"

        print A[second]


    要遍历数组中的每一个元素,需要使用如下特殊结构:

        for (var in array) {for body}

        其var会遍历array的索引,而非元素本身;


        state[LISTEN]++

        state[ESTABLISHED]++


        # netstat -tan | awk '/^tcp/{++state[$NF]}END{for (s in state) {print s,state[s]}}'

        # netstat -tan | awk '/^tcp/{++state[$NF]}{for (s in state) {print s,state[s]}}'

            比较以上两条命令执行的结果,看有何不同??


        # awk '{ip[$1]++}END{for (i in ip) {print i,ip[i]}}' /var/log/httpd/access_log


    删除数组元素:

        delete array[index]


AWK的内置函数


    split(string,array[,fieldsep[,seps]]):

        功能:将string表示的字符串以fieldsep为分隔符进行切片,并切片后的结果保存至array为名的数组中;数组下标从1开始;

       注意,此函数有返回值,返回值为切片后的元素的个数


        root:x:0:0::/root:/bin/bash

        user[1]="root", user[2]


        # awk 'BEGIN{split("root:x:0:0",user,":");print user[1]}'

        # awk 'BEGIN{split("root:x:0:0",user,":");for (i in user) {print user[i]}}'

        # awk 'BEGIN{split("root:x:0:0",user,":");for (i in user) {print i,user[i]}}'

            注意:存储时,并没有按顺序???为什么总是从第四个地段开始到末尾,然后从第一个地段开始到第三个字段???

        # netstat -tn | awk '/^tcp/{lens=split($5,client,":");ip[client[lens-1]]++}END{for (i in ip) print i,ip[i]}'

            注意,时刻想着执行某条语句,表达式以及某个函数后,是否有返回值???


    length(string)

        功能:返回给定字串的长度


    substr(string,start[,length])

        功能:从string中取子串,从start为起始位置为取length长度的子串;