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()

    }

     

  • 相关阅读:
    Educational Codeforces Round 10 C. Foe Pairs 水题
    Educational Codeforces Round 10 B. z-sort 构造
    CDOJ 1048 Bob's vector 三分
    Educational Codeforces Round 10 A. Gabriel and Caterpillar 模拟
    第14届电子科大初赛民间盗版部分题目题解
    HDU 5654 xiaoxin and his watermelon candy 离线树状数组 区间不同数的个数
    HDU 5653 Bomber Man wants to bomb an Array. dp
    HDU 5652 India and China Origins 二分+并查集
    HDU 5651 xiaoxin juju needs help 数学
    HDU 5650 so easy 数学
  • 原文地址:https://www.cnblogs.com/chenjianhong/p/4144375.html
Copyright © 2011-2022 走看看