zoukankan      html  css  js  c++  java
  • awk简要使用

    1          前言

    awkUnix环境下一种非常好的语言,适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行特殊技巧程序设计。对于短消息来说,比如处理话单文件,使用awk就非常方便,鉴于测试组大部分人对awk并不熟悉,因此仅以本文作一个提纲挈领的介绍,希望对初学者迅速掌握awk有所帮助。

    2          第一部分:入门2.1    第一个awk程序

    所有的编程书籍都是通过一个“Hello world!”简单程序入门,作为借鉴,本文也采用此方法带大家入门。现在,在当前目录下建立一个文本文件hello.txt,内容如下:

    Hello world!

    在命令行中输入以下命令:

    $ awk '{ print }' hello.txt

    执行后hello.txt文件的内容显示在屏幕上。编写并且执行awk程序的方法很简单,如上所示,花括号内的是程序代码,后面的hello.txt为指定的输入文件。awk是一种行处理程序,执行awk时,它依次对输入文件中的每一行执行花括号中的代码,如上面的例子,就是对hello.txt中的每一行执行print命令。所有输出都发送到stdout,最后在屏幕上显示的结果为“Hello world!”。

    上面的例子是将脚本作为命令行自变量传递给awk,我们也可以把脚本写入一个外部文件,然后通过-f选项向awk传递这个脚本文件。例如编写脚本文件hello.ask如下:

    { print }

    然后通过如下方式执行:

    $ awk -f hello.awk hello.txt

    这种编写独立脚本文件的方式应用在多行、比较复杂的程序上,非常合适,而且,在后面你还将看到,这种方式还可以让你非常方便地使用附加awk功能。

    2.2    常用变量

    awk有自己的特殊变量集合。其中一些允许调整awk的运行方式,而其它变量可以被读取以收集关于输入的有用信息。下面列举出最基本的几个常用变量,并用例子一一说明用法。

    Awk特殊变量

    描述

    $number

    表示记录的字段。比如,$1表示第1个字段,$2表示第2个字段,如此类推。而$0比较特殊,表示整个当前行

    FS

    表示字段分隔符

    NF

    表示当前记录中的字段数量

    NR

    表示当前记录的编号(awk将第一个记录算作记录号1

    为了便于举例说明,在当前目录下生成一个历史话单文件bill(注意,是一行内容,中间没有换行符),其内容如下:

    4429300,0,8613902700001,8613902700002,8613800288500,2004/06/26 10:31:33,2004/06/26 10:31:33,0,0,0,4,6,1,0,0,0,,0,2004/06/26 10:31:32,NULL,1,0,0,nihao@,

    4429300,0,8613902700001,8613902700003,8613800288500,2004/06/26 10:31:33,2004/06/26 10:31:33,0,0,0,4,6,1,0,0,0,,0,2004/06/26 10:31:32,NULL,1,0,0,nihao@,

    我们可以使用$0变量将整个bill文件内容读出:

    $ awk '{ print $0 }' bill

    可以看出,在awk中,printprint $0的作用完全一样。

    使用$number变量,对多个字段的文本进行处理,将变得非常容易,它可以让你毫不费力地引用每个独立的字段。例如,我们可以列出主叫号码和被叫号码:

    $ awk -F"," '{ print $3,$4 }' bill

    上例中,在调用awk时,使用-F选项来指定","作为字段分隔符。awk处理后打印出主叫号码字段和被叫号码字段。而且,你还可以在字段中插入字符,比如:

    $ awk -F"," '{ print "OrgAddr: "$3, " DestAddr: "$4 }' bill

    以下是该脚本输出:

    OrgAddr: 8613902700001  DestAddr: 8613902700002

    OrgAddr: 8613902700001  DestAddr: 8613902700003

    以上命令中,我们是通过-F选项来指定字段分隔符的。如果我们使用的是脚本文件,则就要用到FS变量来设置字段分隔符。编写脚本文件bill.awk如下:

    BEGIN {

        FS=","

    }

     

    { print "OrgAddr: "$3, " DestAddr: "$4 }

    建议最好用脚本文件,这样方便日后重复利用,也方便修改。

    FS值并没有被限制为单一字符,可以通过指定任意长度的字符模式,将它设置成正则表达式。如果正在处理由一个或多个tab分隔的字段,可以按以下方式设置FS

    FS=" +"

    另外,这个脚本里出现了陌生的BEGIN,有必要解释一下。通常,对于每个输入行,awk都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在awk开始处理输入文件中的文本之前执行初始化代码。对于这种情况,awk允许你定义一个BEGIN块。我们在上面的例子中使用了BEGIN块。因为awk在开始处理输入文件之前会执行BEGIN块,因此它是初始化FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。

    awk还提供了另一个特殊块,叫作END块。awk在处理了输入文件中的所有行之后执行这个块。通常,END块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。

    NF变量,也叫做“字段数量”变量。awk会自动将该变量设置成当前记录中的字段数量。可以使用NF变量来只显示某些输入行:

    NF == 3 { print $1,$2,$3 }

    NR变量始终包含当前记录的编号(awk将第一个记录算作记录号1)。迄今为止,我们处理的输入文件每一行包含一个记录,对于这些情况,NR就是当前行号。可以象使用NF变量一样使用NR来只打印某些输入行:

    (NR < 10 ) || (NR > 100) { print "We are on record number 1-9 or 101+" }

    在本文的第二部分我们还会讨论两个特别有用的特殊变量。

    2.3    表达式和运算符

    awk 允许使用正则表达式,根据正则表达式是否匹配当前行来选择执行独立代码块。以下示例脚本只输出bill中包含字符序列8613902700003的那些行:

    $ awk '/8613902700003/ { print }' bill

    当然,可以使用更复杂的正则表达式:

    /[0-9]*/ { print }

    下面列出正则表达式元字符:

    字符

    描述

    .

    可代替除一行之外的任何单个字符

    *

    可代替零个或多个在它前面出现的字符

    [chars]

    可代替chars中的任何一个字符,chars是一串字符序列。你可以用-符号来定义一个字符范围。如果^chars中的第一个字符,那么将匹配没有在chars中指定的字符

    ^

    匹配一行的开头

    $

    匹配一行的结尾

    后面的字符照常输出,通常用来转义(不使用特殊含义)一个元字符

    除了使用正则表达式来选择行,我们也可以使用布尔表达式来选择行。使用方法是将布尔表达式放在代码块之前,仅当对前面的布尔表达式求值为真时,awk才执行代码块。以下示例脚本将输出bill中第四个字段等于8613902700003的所有行中的第三、四字段。如果当前行的第四个字段不等于8613902700003awk将继续处理文件而不对当前行执行print语句:

    $4 == "8613902700003" { print "OrgAddr: "$3, " DestAddr: "$4 }

    注意,代码块前的布尔表达式必须与代码块在同一行上。

    awk 提供了完整的比较运算符集合,包括"==""<"">""<="">=""!="。另外,awk还提供了"~""!~" 运算符,它们分别表示匹配不匹配。它们的用法是在运算符左边指定变量,在右边指定正则表达式。例如:

    $4 ~ /8613902700003/ { print "OrgAddr: "$3, " DestAddr: "$4 }

    awk还允许使用布尔运算符"||"(逻辑或)和"&&"(逻辑与),以便创建更复杂的布尔表达式:

    ( $3 == "8613902700001" ) && ( $4 == "8613902700003" ) { print }

    awk的另一个优点是它有完整的数学运算符集合。除了标准的加、减、乘、除,awk还允许使用指数运算符"^"、模运算符"%"和其它许多从C语言中借入的易于使用的赋值操作符。

    这些运算符包括前后加减(i++--j)、加/减/乘/除赋值运算符(a+=3b*=2c/=2.2d-=6.2)。不仅如此,还有易于使用的模/指数赋值运算符(a^=2b%=4)。

    2.4    字符串化变量

    awk变量“字符串化”是指所有awk变量在内部都是按字符串形式存储的。而且只要变量包含有效数字字符串,就可以对它执行数学操作,awk会自动处理字符串到数字的转换步骤。请看以下这个示例:

    BEGIN   { x="0" }

    /^$/    { x=x+1 }

    END     { print "I found " x " blank lines. :)" }

    这个例子的功能是计算文件中空白行的数量,^$表示空行。

    如果做一个小实验,就可以发现如果某个特定变量不包含有效数字,awk在对数学表达式求值时会将该变量当作数字0处理。

    3          第二部分:提高3.1    处理多行

    在这一节里,顺带着讲一下三个特别的变量:

    Awk特殊变量

    描述

    RS

    表示记录分隔符

    OFS

    表示输出字段分隔符,在两个单独的字段间插入定义的字符串

    ORS

    表示输出记录分隔符,在两个单独的记录间插入定义的字符串

    第一部分我们讨论的都是一个记录占用一行的情况,如果要分析占据多行的记录,除了依靠FS,还需要设置RS(记录分隔符变量)。RS变量告诉awk当前记录什么时候结束,新记录什么时候开始。

    为了便于讨论,我们依然首先在当前目录下生成一个通讯录文件address,其内容如下:

    zhangsan

    13712345678

    zhs@hotmail.com

     

    lisi

    13012345678

    ls@21cn.com

    要处理这个文件,可以将每三行看作是一个独立的记录,一个记录包含三个字段。如下脚本将原记录由三行转换成一行输出:

    BEGIN {

        FS=" "

        RS=""

    }

     

    {

        print $1 ", " $2 ", " $3

    }

    此代码将产生以下输出:

    zhangsan, 13712345678, zhs@hotmail.com

    lisi, 13012345678, ls@21cn.com

    在上面例子中,为了在三个字段之间插入一个逗号和空格,使用了", "。这个方法虽然有用,但比较难看。其实我们还有更好的方法,那就是设置变量OFS(输出字段分隔符)。OFS缺省情况下被设置成" "(单个空格)。使用如下脚本可以达到上面例子同样的效果:

    BEGIN {

        FS=" "

        RS=""

        OFS=", "

    }

     

    {

        print $1, $2, $3

    }

    awk还有一个特殊变量ORS(输出记录分隔符)。ORS缺省情况下被设置成" ",如果我们将其设为" ",就可以使输出记录的间隔翻倍。例子就不举了,大家可以自己试试。个空格分隔记录(而不换行),将ORS设置成" "

    需要注意的是,使用上面的方法,最多只能处理一个记录占用三行的文本,象下面一个记录占据四行的通讯录,就处理不了了(大家可以试试看):

    wangwu

    13512345678

    ww@163.com

    wuhan, hubei

    要处理这种情况,代码最好考虑每个记录的字段数量,并依次打印每个记录。以下就是修正的代码:

    BEGIN {

        FS=" "

        RS=""

        ORS=""

    }

            x=1

            while ( x<NF ) {

                    print $x " "

                    x++

            }

            print $NF " "

    }

    程序输出如下:

    wangwu  13512345678     ww@163.com      wuhan, hubei

    3.2    条件语句

    awkif语句类似于C语言的if语句,没什么好说的,举一个例子吧:

    {

        if ( $1 == "foo" ) {

            if ( $2 == "foo" ) {

                print "uno"

            } else {

                print "one"

            }

        } else if ($1 == "bar" ) {

            print "two"

        } else {

            print "three"

        }

    }

    3.3    循环语句

    其时在第1节的最后,我们已经看到了awkwhile循环结构,它等同于相应的C语言while循环。awk还有"do...while"循环,它在代码块结尾处对条件求值,还是直接举例吧:

    do...while 示例

    {

        count=1

        do {

            print "I get printed at least once no matter what"

        } while ( count != 1 )

    }

    for 循环

    也等同于C语言的for循环:

    for ( x = 1; x <= 4; x++ ) {

        print "iteration",x

    }

    break continue

    此外,如同C语言一样,awk提供了breakcontinue来控制awk的循环结构。break语句用于跳出最深层的循环,使循环立即终止,并继续执行循环代码块后面的语句。continue语句使awk立即开始执行下一个循环迭代,而不执行代码块的其余部分。

    3.4    数组

    awk中,数组下标通常从1开始,而不是0

    myarray[1]="jim"

    myarray[2]=456

    awk遇到第一个赋值语句时,它将创建myarray,并将元素myarray[1]设置成"jim"。执行了第二个赋值语句后,数组就有两个元素了。Awk数组不需要连续的数字序列下标(例如,可以定义myarr[1]myarr[1000],但不定义其它所有元素)

    awk可以使用"in"操作来遍历数组中的所有元素,如下所示:

    for ( x in myarray ) {

        print myarray[x]

    }

    但是这个方法有一个缺点——当awk在数组下标之间轮转时,它不会依照任何特定的顺序。那就意味着我们不能知道以上代码的输出是:

    jim

    456

    还是

    456

    jim

    awk数组中还可以使用字符串下标,其实,不管你使用的下标是字符串还是数字,awk在幕后还将其认为是字符串下标。举例如下:

    代码一:

    myarr["1"]="China"

    print myarr["1"]

    代码二:

    myarr["1"]="Mr. Whipple"

    print myarr[1]

    代码三:

    myarr["name"]="Mr. Whipple"

    print myarr["name"]

    它们都将打印 "China"

    删除数组元素使用delete,举例如下:

    delete fooarray[1]

    另外,如果想要查看是否存在某个特定数组元素,可以使用特殊的"in"布尔运算符,如下所示:

    if ( 1 in fooarray ) {

        print "It's there."

    } else {

        print "Can't find it."

    }

    4          第三部分:精通4.1    格式化输出

    虽然大多数情况下awkprint语句可以完成任务,但有时我们还需要更多。使用两个函数printf()sprintf(),将能让输出锦上添花。printf()会将格式化字符串打印到stdout,而sprintf()则返回可以赋值给变量的格式化字符串。

    从下面例子可以看到,它们几乎与C语言完全相同。

    x=1

    b="foo"

    printf("%s got a %d on the last test ","Jim",83)

    myout=("%s-%d",b,x)

    print myout

    此代码将打印:

    Jim got a 83 on the last test

    foo-1

    4.2    字符串函数

    既然awk把所有变量都当作字符串处理,那么字符串处理对awk就显得尤为重要。但要说明一点,awk不能象在其它语言(如 CC++)中那样将字符串看作是字符数组。例如,如果执行以下代码:

    mystring="How are you doing today?"

    print mystring[3]

    将会接收到一个错误,如下所示:

    awk: string.gawk:59: fatal: attempt to use scalar as array

    不用担心,awk有许多字符串函数,弥补了这个缺陷。现将常用的字符串函数列举如下:

    字符串函数

    描述

    length()

    返回字符串的长度

    index()

    返回子字符串在另一个字符串中出现的位置

    tolower()

    返回字符串并且将所有字符转换成小写

    toupper()

    返回字符串并且将所有字符转换成大写

    substr()

    返回从字符串中选择的子串

    match()

    返回子字符串在另一个字符串中出现的位置。它与index()的区别在于它并不搜索子串,它搜索的是正则表达式。

    sub()

    替换匹配的第一个字符序列,并返回整个字符串

    gsub()

    替换匹配的全部字符序列,并返回整个字符串

    split()

    分割字符串,并将各部分放到使用整数下标的数组中

    length()返回字符串的长度,例子如下:

    print length(mystring)

    index()返回子字符串在另一个字符串中出现的位置,如果没有找到该字符串则返回0,例子如下:

    print index(mystring,"you")

    tolower()toupper()返回字符串并且将所有字符分别转换成小写或大写。注意,tolower()toupper()返回新的字符串,不会修改原来的字符串。例子如下:

    print tolower(mystring)

    print toupper(mystring)

    使用substr()可以从字符串中选择子串。以下是substr()的调用方法:

    mysub=substr(mystring,startpos,maxlen)

    以下是一个示例:

    print substr(mystring,9,3)

    match()index()非常相似,它与index()的区别在于它并不搜索子串,它搜索的是正则表达式。match()函数将返回匹配的起始位置,如果没有找到匹配,则返回0。此外,match()还将设置两个变量,叫作RSTARTRLENGTHRSTART包含返回值(第一个匹配的位置),RLENGTH指定它占据的字符跨度(如果没有找到匹配,则返回-1)。通过使用RSTARTRLENGTHsubstr()和一个小循环,可以轻松地遍历字符串中的每个匹配。以下是一个match()调用示例:

    print match(mystring,/you/), RSTART, RLENGTH

    sub()gsub()是两个字符串替换函数。sub()的调用方法如下:

    sub(regexp,replstring,mystring)

    调用sub()时,它将在mystring中匹配regexp的第一个字符序列,并且用replstring替换该序列。gsub()sub()的唯一的区别是sub()替换第一个regexp匹配(如果有的话),gsub()则执行全局替换,换出字符串中的所有匹配。举例如下:

    sub(/o/,"O",mystring)

    print mystring

    mystring="How are you doing today?"

    gsub(/o/,"O",mystring)

    print mystring

    其输出结果如下:

    HOw are you doing today?

    HOw are yOu dOing tOday?

    split()的功能是分割字符串,并将分割后的各部分放到使用整数下标的数组中。此函数有三个变量,第一个自变量为要分割的原始字符串,第二个自变量为分割后填入的数组名称,第三个变素为用于指示分割的分隔符。split()返回时,它将返回分割的字符串元素的数量。split()将分割的每一个部分赋值给下标从1开始的数组。举例如下:

    numelements=split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",mymonths,",")

    print mymonths[1],mymonths[numelements]

    其输出如下:

    Jan Dec

    最后需要说明一点的是,调用length()sub()gsub()时,可以去掉最后一个变量,这样awk将对$0(整个当前行)应用函数调用。例如要打印文件中每一行的长度,使用以下awk脚本:

    {

        print length()

    }

     

  • 相关阅读:
    Manually Traverse a DOM Tree Using jQuery | James Wiseman
    JW Player
    San Francisco Bay Area Professional Blog: Traverse/walk DOM tree recursively
    开源集群计算环境 Spark
    INNO Setup 使用笔记
    C++ 容器及选用总结 Keosu 博客园
    Qt 4.7: DOM Traversal Example
    JavaScript Lab Articles Nonrecursive Preorder Traversal Part 4
    Tree traversal Wikipedia, the free encyclopedia
    Javascript – Traversing the HTML DOM recursively « blog.swapnilsarwe.com
  • 原文地址:https://www.cnblogs.com/chenjianhong/p/4144373.html
Copyright © 2011-2022 走看看