Linux Shell常用技巧(五) awk编程

简介:

九.  awk实用功能:

    和sed一样,awk也是逐行扫描文件的,从第一行到最后一行,寻找匹配特定模板的行,并在这些行上运行“选择”动作。如果一个模板没有指定动作,这些匹配的行就被显示在屏幕上。如果一个动作没有模板,所有被动作指定的行都被处理。
    
   1.  awk的基本格式:
    /> awk 'pattern' filename
    /> awk '{action}' filename
    /> awk 'pattern {action}' filename
    
    具体应用方式分别见如下三个用例:
    /> cat employees
    Tom Jones         4424    5/12/66         543354
    Mary Adams      5346    11/4/63         28765
    Sally Chang       1654    7/22/54         650000
    Billy Black         1683    9/23/44         336500

    /> awk '/Mary/' employees   #打印所有包含模板Mary的行。
    Mary Adams      5346    11/4/63         28765

    #打印文件中的第一个字段,这个域在每一行的开始,缺省由空格或其它分隔符。
    /> awk '{print $1}' employees 
    Tom
    Mary
    Sally
    Billy
    
    /> awk '/Sally/{print $1, $2}' employees #打印包含模板Sally的行的第一、第二个域字段。
    Sally Chang
    
    2.  awk的格式输出:
    awk中同时提供了print和printf两种打印输出的函数,其中print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数 用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。下面给出基本的转码序 列:

转码 含义
\n 换行
\r 回车
\t 制表符


    /> date | awk '{print "Month: " $2 "\nYear: ", $6}'
    Month: Oct
    Year:  2011

    /> awk '/Sally/{print "\t\tHave a nice day, " $1,$2 "\!"}' employees
                    Have a nice day, Sally Chang!

    在打印数字的时候你也许想控制数字的格式,我们通常用printf来完成这个功能。awk的特殊变量OFMT也可以在使用print函数的时候,控制数字的打印格式。它的默认值是"%.6g"----小数点后面6位将被打印。
    /> awk 'BEGIN { OFMT="%.2f"; print 1.2456789, 12E-2}'
    1.25  0.12

    现在我们介绍一下功能更为强大的printf函数,其用法和c语言中printf基本相似。下面我们给出awk中printf的格式化说明符列表:

格式化说明符 功能 示例 结果
%c 打印单个ASCII字符。 printf("The character is %c.\n",x) The character is A.
%d 打印十进制数。 printf("The boy is %d years old.\n",y) The boy is 15 years old.
%e 打印用科学记数法表示的数。 printf("z is %e.\n",z) z is 2.3e+01.
%f 打印浮点数。 printf("z is %f.\n",z) z is 2.300000
%o 打印八进制数。 printf("y is %o.\n",y) y is 17.
%s 打印字符串。 printf("The name of the culprit is %s.\n",$1); The name of the culprit is Bob Smith.
%x 打印十六进制数。 printf("y is %x.\n",y) y is f.

    注:假设列表中的变脸值为x = A, y = 15, z = 2.3, $1 = "Bob Smith"

    /> echo "Linux" | awk '{printf "|%-15s|\n", $1}'  # %-15s表示保留15个字符的空间,同时左对齐。
    |Linux          |

    /> echo "Linux" | awk '{printf "|%15s|\n", $1}'   # %-15s表示保留15个字符的空间,同时右对齐。
    |          Linux|

    #%8d表示数字右对齐,保留8个字符的空间。
     /> awk '{printf "The name is %-15s ID is %8d\n", $1,$3}' employees
    The name is Tom             ID is     4424
    The name is Mary            ID is     5346
    The name is Sally            ID is     1654
    The name is Billy             ID is     1683

    3.  awk中的记录和域:
    awk中默认的记录分隔符是回车,保存在其内建变量ORS和RS中。$0变量是指整条记录。
    /> awk '{print $0}' employees #这等同于print的默认行为。
    Tom Jones        4424    5/12/66         543354
    Mary Adams      5346    11/4/63         28765
    Sally Chang       1654    7/22/54         650000
    Billy Black         1683    9/23/44         336500

    变量NR(Number of Record),记录每条记录的编号。
    /> awk '{print NR, $0}' employees
    1 Tom Jones        4424    5/12/66         543354
    2 Mary Adams      5346    11/4/63         28765
    3 Sally Chang       1654    7/22/54         650000
    4 Billy Black         1683    9/23/44         336500

    变量NF(Number of Field),记录当前记录有多少域。
    /> awk '{print $0,NF}' employees
    Tom Jones        4424    5/12/66          543354   5
    Mary Adams      5346    11/4/63         28765     5
    Sally Chang      1654    7/22/54          650000   5
    Billy Black        1683     9/23/44         336500    5

    #根据employees生成employees2。sed的用法可以参考上一篇blog。
    /> sed 's/[[:space:]]\+\([0-9]\)/:\1/g;w employees2' employees
    /> cat employees
    Tom Jones:4424:5/12/66:543354
    Mary Adams:5346:11/4/63:28765
    Sally Chang:1654:7/22/54:650000
    Billy Black:1683:9/23/44:336500

    /> awk -F: '/Tom Jones/{print $1,$2}' employees2  #这里-F选项后面的字符表示分隔符。
    Tom Jones 4424

    变量OFS(Output Field Seperator)表示输出字段间的分隔符,缺省是空格。
    />  awk -F: '{OFS = "?"};  /Tom/{print $1,$2 }' employees2 #在输出时,域字段间的分隔符已经是?(问号)了
    Tom Jones?4424

    对于awk而言,其模式部分将控制这动作部分的输入,只有符合模式条件的记录才可以交由动作部分基础处理,而模式部分不仅可以写成正则表达式(如上面的例子),awk还支持条件表达式,如:
    /> awk '$3 < 4000 {print}' employees
    Sally Chang     1654    7/22/54         650000
    Billy Black       1683    9/23/44         336500

    在花括号内,用分号分隔的语句称为动作。如果模式在动作前面,模式将决定什么时候发出动作。动作可以是一个语句或是一组语句。语句之间用分号分隔,也可以用换行符,如:
    pattern { action statement; action statement; etc. } or
    pattern {
        action statement
        action statement
    }
    模式和动作一般是捆绑在一起的。需要注意的是,动作是花括号内的语句。模式控制的动作是从第一个左花括号开始到第一个右花括号结束,如下:
    /> awk '$3 < 4000 && /Sally/ {print}' employees
    Sally Chang     1654    7/22/54         650000

    4.  匹配操作符:
    " ~ " 用来在记录或者域内匹配正则表达式。
    /> awk '$1 ~ /[Bb]ill/' employees      #显示所有第一个域匹配Bill或bill的行。
    Billy Black     1683    9/23/44         336500

    /> awk '$1 !~ /[Bb]ill/' employees     #显示所有第一个域不匹配Bill或bill的行,其中!~表示不匹配的意思。
    Tom Jones        4424    5/12/66         543354
    Mary Adams      5346    11/4/63         28765
    Sally Chang       1654    7/22/54         650000

    5.  awk的基本应用实例:
    /> cat testfile
    northwest     NW        Charles Main            3.0        .98        3        34
    western        WE        Sharon Gray            5.3        .97        5        23
    southwest     SW        Lewis Dalsass          2.7        .8          2        18
    southern       SO        Suan Chin                5.1        .95        4        15
    southeast      SE        Patricia Hemenway    4.0        .7          4        17
    eastern         EA        TB Savage                4.4        .84        5        20
    northeast      NE        AM Main Jr.               5.1        .94        3        13
    north            NO        Margot Weber          4.5        .89        5        9
    central          CT        Ann Stephens           5.7        .94        5        13

    /> awk '/^north/' testfile            #打印所有以north开头的行。
    northwest      NW      Charles Main     3.0     .98     3       34
    northeast       NE      AM Main Jr.        5.1     .94     3       13
    north             NO      Margot Weber   4.5     .89     5       9

    /> awk '/^(no|so)/' testfile          #打印所有以so和no开头的行。
    northwest       NW      Charles Main                3.0     .98      3       34
    southwest       SW      Lewis Dalsass              2.7     .8       2       18
    southern         SO      Suan Chin                    5.1     .95     4       15
    southeast        SE      Patricia Hemenway        4.0     .7       4       17
    northeast        NE      AM Main Jr.                   5.1     .94     3       13
    north              NO      Margot Weber              4.5     .89     5       9

    /> awk '$5 ~ /\.[7-9]+/' testfile     #第五个域字段匹配包含.(点),后面是7-9的数字。
    southwest       SW      Lewis Dalsass            2.7     .8      2       18
    central             CT      Ann Stephens            5.7     .94     5       13

    /> awk '$8 ~ /[0-9][0-9]$/{print $8}' testfile  #第八个域以两个数字结束的打印。
    34
    23
    18
    15
    17
    20
    13

十.  awk表达式功能:

    1.  比较表达式:
    比较表达式匹配那些只在条件为真时才运行的行。这些表达式利用关系运算符来比较数字和字符串。见如下awk支持的条件表达式列表:

运算符 含义 例子
< 小于 x < y
<= 小于等于 x <= y
== 等于 x == y
!= 不等于 x != y
>= 大于等于 x >= y
> 大于 x > y
~ 匹配 x ~ /y/
!~ 不匹配 x !~ /y/

    /> cat employees
    Tom Jones        4424    5/12/66         543354
    Mary Adams      5346    11/4/63         28765
    Sally Chang       1654    7/22/54         650000
    Billy Black         1683    9/23/44         336500

    /> awk '$3 == 5346' employees       #打印第三个域等于5346的行。
    Mary Adams      5346    11/4/63         28765

    /> awk '$3 > 5000 {print $1}' employees  #打印第三个域大于5000的行的第一个域字段。
    Mary

    /> awk '$2 ~ /Adam/' employess      #打印第二个域匹配Adam的行。
    Mary Adams      5346    11/4/63         28765

    2.  条件表达式:
    条件表达式使用两个符号--问号和冒号给表达式赋值: conditional expression1 ? expression2 : expressional3,其逻辑等同于C语言中的条件表达式。其对应的if/else语句如下:
    {
        if (expression1)
            expression2
        else
            expression3
    }
    /> cat testfile
    northwest     NW        Charles Main             3.0        .98        3        34
    western        WE        Sharon Gray             5.3        .97         5        23
    southwest     SW        Lewis Dalsass           2.7        .8          2        18
    southern       SO        Suan Chin                 5.1        .95        4        15
    southeast      SE        Patricia Hemenway     4.0        .7          4        17
    eastern         EA        TB Savage                 4.4        .84        5        20
    northeast      NE        AM Main Jr.                5.1       .94         3        13
    north            NO        Margot Weber           4.5       .89         5        9
    central          CT        Ann Stephens            5.7       .94         5        13

    /> awk 'NR <= 3 {print ($7 > 4 ? "high "$7 : "low "$7) }' testfile
    low 3
    high 5
    low 2

    3.  数学表达式:
    运算可以在模式内进行,其中awk将所有的运算都视为浮点运算,见如下列表:

运算符 含义 例子
+ x + y
- x - y
* x * y
/ x / y
% 取余 x % y
^ 乘方 x ^ y

    /> awk '/southern/{print $5 + 10}' testfile  #如果记录包含正则表达式southern,第五个域就加10并打印。
    15.1

    /> awk '/southern/{print $8 /2 }' testfile   #如果记录包含正则表达式southern,第八个域除以2并打印。
    7.5

    4.  逻辑表达式:
    见如下列表:

运算符 含义 例子
&& 逻辑与 a && b
|| 逻辑或 a || b
! 逻辑非 !a

    /> awk '$8 > 10 && $8 < 17' testfile   #打印出第八个域的值大于10小于17的记录。
    southern        SO      Suan Chin               5.1     .95     4       15
    central            CT      Ann Stephens         5.7     .94     5       13

    #打印第二个域等于NW,或者第一个域匹配south的行的第一、第二个域。
    /> awk '$2 == "NW" || $1 ~ /south/ {print $1,$2}' testfile
    northwest  NW
    southwest  SW
    southern    SO
    southeast   SE

    /> awk '!($8 > 13) {print $8}' testfile  #打印第八个域字段不大于13的行的第八个域。
    3
    9
    13

    5.  范围模板:
    范围模板匹配从第一个模板的第一次出现到第二个模板的第一次出现,第一个模板的下一次出现到第一个模板的下一次出现等等。如果第一个模板匹配而第二个模板没有出现,awk就显示到文件末尾的所有行。
    /> awk '/^western/,/^eastern/ {print $1}' testfile #打印以western开头到eastern开头的记录的第一个域。
    western    WE
    southwest SW
    southern   SO
    southeast  SE
    eastern      EA    

    6.  赋值符号:
    #找到第三个域等于Ann的记录,然后给该域重新赋值为Christian,之后再打印输出该记录。
    /> awk '$3 == "Ann" { $3 = "Christian"; print}' testfile
    central CT Christian Stephens 5.7 .94 5 13

    /> awk '/Ann/{$8 += 12; print $8}' testfile #找到包含Ann的记录,并将该条记录的第八个域的值+=12,最后再打印输出。
    25





十一.  awk编程:

    1.  变量:
    在awk中变量无须定义即可使用,变量在赋值时即已经完成了定义。变量的类型可以是数字、字符串。根据使用的不同,未初始化变量的值为0或空白字符串" ",这主要取决于变量应用的上下文。下面为变量的赋值负号列表:

符号 含义 等价形式
= a = 5 a = 5
+= a = a + 5 a += 5
-= a = a - 5 a -= 5
*= a = a * 5 a *= 5
/= a = a / 5 a /= 5
%= a = a % 5 a %= 5
^= a = a ^ 5 a ^= 5

    /> awk '$1 ~ /Tom/ {Wage = $2 * $3; print Wage}' filename
    该命令将从文件中读取,并查找第一个域字段匹配Tom的记录,再将其第二和第三个字段的乘积赋值给自定义的Wage变量,最后通过print命令将该变量打印输出。

    /> awk ' {$5 = 1000 * $3 / $2; print}' filename
    在上面的命令中,如果$5不存在,awk将计算表达式1000 * $3 / $2的值,并将其赋值给$5。如果第五个域存在,则用表达式覆盖$5原来的值。

    我们同样也可以在命令行中定义自定义的变量,用法如下:
    /> awk -F: -f awkscript month=4 year=2011 filename
    这里的month和year都是自定义变量,且分别被赋值为4和2000,在awk的脚本中这些变量将可以被直接使用,他们和脚本中定义的变量在使用上没有任何区别。

    除此之外,awk还提供了一组内建变量(变量名全部大写),见如下列表:

变量名 变量内容
ARGC 命令行参数的数量。
ARGIND 命令行正在处理的当前文件的AGV的索引。
ARGV 命令行参数数组。
CONVFMT 转换数字格式。
ENVIRON 从shell中传递来的包含当前环境变量的数组。
ERRNO 当使用close函数或者通过getline函数读取的时候,发生的重新定向错误的描述信息就保存在这个变量中。
FIELDWIDTHS 在对记录进行固定域宽的分割时,可以替代FS的分隔符的列表。
FILENAME 当前的输入文件名。
FNR 当前文件的记录号。
FS 输入分隔符,默认是空格。
IGNORECASE 在正则表达式和字符串操作中关闭大小写敏感。
NF 当前文件域的数量。
NR 当前文件记录数。
OFMT 数字输出格式。
OFS 输出域分隔符。
ORS 输出记录分隔符。
RLENGTH 通过match函数匹配的字符串的长度。
RS 输入记录分隔符。
RSTART 通过match函数匹配的字符串的偏移量。
SUBSEP 下标分隔符。

    /> cat employees2
    Tom Jones:4424:5/12/66:543354
    Mary Adams:5346:11/4/63:28765
    Sally Chang:1654:7/22/54:650000
    Mary Black:1683:9/23/44:336500

    /> awk -F: '{IGNORECASE = 1}; $1 == "mary adams" { print NR, $1, $2, $NF}' employees2
    2 Mary Adams 5346 28765
    /> awk -F: ' $1 == "mary adams" { print NR, $1, $2, $NF}' employees2
    没有输出结果。
    当IGNORECASE内置变量的值为非0时,表示在进行字符串操作和处理正则表达式时关闭大小写敏感。这里的"mary adams"将匹配文件中的"Mary Admams"记录。最后print打印出第一、第二和最后一个域。需要说明的是NF表示当前记录域的数量,因此$NF将表示最后一个域的值。

    awk在动作部分还提供了BEGIN块和END块。其中BEGIN动作块在awk处理任何输入文件行之前执行。事实上,BEGIN块可以在没有任何输入 文件的条件下测试。因为在BEGIN块执行完毕以前awk将不读取任何输入文件。BEGIN块通常被用来改变内建变量的值,如OFS、RS或FS等。也可 以用于初始化自定义变量值,或打印输出标题。
    /> awk 'BEGIN {FS = ":"; OFS = "\t"; ORS = "\n\n"} { print $1,$2,$3} filename
    上例中awk在处理文件之前,已经将域分隔符(FS)设置为冒号,输出文件域分隔符(OFS)设置为制表符,输出记录分隔符(ORS)被设置为两个换行符。BEGIN之后的动作模块中如果有多个语句,他们之间用分号分隔。
    和BEGIN恰恰相反,END模块中的动作是在整个文件处理完毕之后被执行的。
    /> awk 'END {print "The number of the records is " NR }' filename
    awk在处理输入文件之后,执行END模块中的动作,上例中NR的值是读入的最后一个记录的记录号。

    /> awk '/Mary/{count++} END{print "Mary was found " count " times." }' employees2
    Mary was found 2 times.

    /> awk '/Mary/{count++} END{print "Mary was found " count " times." }' employees2
    Mary was found 2 times.
    
    /> cat testfile
    northwest       NW      Charles Main                3.0     .98     3       34
    western          WE      Sharon Gray                5.3     .97     5       23
    southwest       SW      Lewis Dalsass              2.7     .8      2       18
    southern         SO      Suan Chin                   5.1     .95     4       15
    southeast        SE      Patricia Hemenway        4.0     .7      4       17
    eastern           EA      TB Savage                   4.4     .84     5       20
    northeast        NE      AM Main Jr.                  5.1     .94     3       13
    north             NO       Margot Weber             4.5     .89     5       9
    central           CT       Ann Stephens              5.7     .94     5       13

    /> awk '/^north/{count += 1; print count}' testfile     #如记录以正则north开头,则创建变量count同时增一,再输出其值。
    1
    2
    3
    
    #这里只是输出前三个字段,其中第七个域先被赋值给变量x,在自减一,最后再同时打印出他们。
    /> awk 'NR <= 3 {x = $7--; print "x = " x ", $7 = " $7}' testfile
    x = 3, $7 = 2
    x = 5, $7 = 4
    x = 2, $7 = 1    
    
    #打印NR(记录号)的值在2--5之间的记录。
    /> awk 'NR == 2,NR == 5 {print "The record number is " NR}' testfile
    The record number is 2
    The record number is 3
    The record number is 4
    The record number is 5

    #打印环境变量USER和HOME的值。环境变量的值由父进程shell传递给awk程序的。
    /> awk 'BEGIN { print ENVIRON["USER"],ENVIRON["HOME"]}' 
    root /root
    
    #BEGIN块儿中对OFS内置变量重新赋值了,因此后面的输出域分隔符改为了\t。
    /> awk 'BEGIN { OFS = "\t"}; /^Sharon/{ print $1,$2,$7}' testfile
    western WE      5
    
    #从输入文件中找到以north开头的记录count就加一,最后在END块中输出该变量。
    /> awk '/^north/{count++}; END{print count}' testfile
    3

    2.  重新定向:
    在 动作语句中使用shell通用的重定向输出符号">"就可以完成awk的重定向操作,当使用>的时候,原有文件将被清空,同时文件持续打开, 直到文件被明确的关闭或者awk程序终止。来自后面的打印语句的输出会追加到前面内容的后面。符号">>"用来打开一个文件但是不清空原有文 件的内容,重定向的输出只是被追加到这个文件的末尾。
    /> awk '$4 >= 70 {print $1,$2 > "passing_file"}' filename  #注意这里的文件名需要用双引号括起来。
    #通过两次cat的结果可以看出>和>>的区别。
    /> awk '/north/{print $1,$3,$4 > "districts" }' testfile
    /> cat districts
    northwest Joel Craig
    northeast TJ Nichols
    north Val Shultz
    /> awk '/south/{print $1,$3,$4 >> "districts" }' testfile
    /> cat districts
    northwest Joel Craig
    northeast TJ Nichols
    north Val Shultz
    southwest Chris Foster
    southern May Chin
    southeast Derek Jonhson

    
    awk中对于输入重定向是通过getline函数来完成的。getline函数的作用是从标准输入、管道或者当前正在处理的文件之外的其他输入文件获得 输入。他负责从输入获得下一行的内容,并给NF、NR和FNR等内建变量赋值。如果得到一个记录,getline就返回1,如果达到文件末尾就返回0。如 果出现错误,如打开文件失败,就返回-1。
    /> awk 'BEGIN { "date" | getline d; print d}'
    Tue Nov 15 15:31:42 CST 2011
    上例中的BEGIN动作模块中,先执行shell命令date,并通过管道输出给getline,然后再把输出赋值给自定义变量d并打印输出它。
    
    /> awk 'BEGIN { "date" | getline d; split(d,mon); print mon[2]}'
    Nov
    上例中date命令通过管道输出给getline并赋值给d变量,再通过内置函数split将d拆分为mon数组,最后print出mon数组的第二个元素。
    
    /> awk 'BEGIN { while("ls" | getline) print}'
    employees
    employees2
    testfile
    命令ls的输出传递给getline作为输入,循环的每个反复,getline都从ls的结果中读取一行输入,并把他打印到屏幕。
    
    /> awk 'BEGIN { printf "What is your name? "; \
        getline name < "/dev/tty"}\
        $1 ~ name {print "Found" name " on line ", NR "."}\
        END {print "See ya, " name "."}' employees2
    What is your name? Mary
    Found Mary on line  2.
    See ya, Mary.    
    上例先是打印出BEGIN块中的"What is your name? ",然后等待用户从/dev/tty输入,并将读入的数据赋值给name变量,之后再从输入文件中读取记录,并找到匹配输入变量的记录并打印出来,最后在END块中输出结尾信息。
    
    /> awk 'BEGIN { while(getline < "/etc/passwd" > 0) lc++; print lc}'
    32
    awk将逐行读取/etc/passwd文件中的内容,在达到文件末尾之前,计数器lc一直自增1,当到了末尾后打印lc的值。lc的值为/etc/passwd文件的行数。
    由于awk中同时打开的管道只有一个,那么在打开下一个管道之前必须关闭它,管道符号右边可以通过可以通过双引号关闭管道。如果不关闭,它将始终保持打开状态,直到awk退出。
    /> awk {print $1,$2,$3 | "sort -4 +1 -2 +0 -1"} END {close("sort -4 +1 -2 +0 -1") } filename
    上例中END模块中的close显示关闭了sort的管道,需要注意的是close中关闭的命令必须和当初打开时的完全匹配,否则END模块产生的输出会和以前的输出一起被sort分类。


    3.  条件语句:
    awk中的条件语句是从C语言中借鉴来的,见如下声明方式:
    if (expression) {
        statement;
        statement;
        ... ...
    }
    /> awk '{if ($6 > 50) print $1 "Too hign"}' filename
    /> awk '{if ($6 > 20 && $6 <= 50) { safe++; print "OK}}' filename

    if (expression) {
        statement;
    } else {
        statement2;
    }
    /> awk '{if ($6 > 50) print $1 " Too high"; else print "Range is OK" }' filename
    /> awk '{if ($6 > 50) { count++; print $3 } else { x = 5; print $5 }' filename

    if (expression) {
        statement1;
    } else if (expression1) {
        statement2;
    } else {
        statement3;
    }
    /> awk '{if ($6 > 50) print "$6 > 50" else if ($6 > 30) print "$6 > 30" else print "other"}' filename

   4.  循环语句:
    awk中的循环语句同样借鉴于C语言,支持while、do/while、for、break、continue,这些关键字的语义和C语言中的语义完全相同。

    5.  流程控制语句:
    next语句是从文件中读取下一行,然后从头开始执行awk脚本。
    exit语句用于结束awk程序。它终止对记录的处理。但是不会略过END模块,如果exit()语句被赋值0--255之间的参数,如exit(1),这个参数就被打印到命令行,以判断退出成功还是失败。

    6.  数组:
    因 为awk中数组的下标可以是数字和字母,数组的下标通常被称为关键字(key)。值和关键字都存储在内部的一张针对key/value应用hash的表格 里。由于hash不是顺序存储,因此在显示数组内容时会发现,它们并不是按照你预料的顺序显示出来的。数组和变量一样,都是在使用时自动创建的,awk也 同样会自动判断其存储的是数字还是字符串。一般而言,awk中的数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹配的次数等等。
    /> cat employees
    Tom Jones       4424    5/12/66         543354
    Mary Adams      5346    11/4/63         28765
    Sally Chang     1654    7/22/54         650000
    Billy Black     1683    9/23/44         336500

    /> awk '{name[x++] = $2}; END{for (i = 0; i < NR; i++) print i, name[i]}' employees    
    0 Jones
    1 Adams
    2 Chang
    3 Black
    在上例中,数组name的下标是变量x。awk初始化该变量的值为0,在每次使用后自增1,读取文件中的第二个域的值被依次赋值给name数组的各个元素。在END模块中,for循环遍历数组的值。因为下标是关键字,所以它不一定从0开始,可以从任何值开始。

    #这里是用内置变量NR作为数组的下标了。
    /> awk '{id[NR] = $3}; END {for (x = 1; x <= NR; x++) print id[x]}' employees
    4424
    5346
    1654
    1683

    awk中还提供了一种special for的循环,见如下声明:
    for (item in arrayname) {
        print arrayname[item]
    }

    /> cat db
    Tom Jones
    Mary Adams
    Sally Chang
    Billy Black
    Tom Savage
    Tom Chung
    Reggie Steel
    Tommy Tucker

    /> awk '/^Tom/{name[NR]=$1}; END {for(i = 1;i <= NR; i++) print name[i]}' db
    Tom



    Tom
    Tom

    Tommy
    从输出结果可以看出,只有匹配正则表达式的记录的第一个域被赋值给数组name的指定下标元素。因为用NR作为下标,所以数组的下标不可能是连续的,因 此在END模块中用传统的for循环打印时,不存在的元素就打印空字符串了。下面我们看看用special for的方式会有什么样的输出。
    /> awk '/^Tom/{name[NR]=$1};END{for(i in name) print name[i]}' db
    Tom
    Tom
    Tommy
    Tom

    下面我们看一下用字符串作为下标的例子:(如果下标是字符串文字常量,则需要用双引号括起来)    
    /> cat testfile2
    tom
    mary
    sean
    tom
    mary
    mary
    bob
    mary
    alex
    /> awk '/tom/{count["tom"]++}; /mary/{count["mary"]++}; END{print "There are " count["tom"] \
        " Toms and " count["mary"] " Marys in the file."} testfile2
    There are 2 Toms and 4 Marys in the file.
    在上例中,count数组有两个元素,下标分别为tom和mary,每一个元素的初始值都是0,没有tom被匹配的时候,count["tom"]就会加一,count["mary"]在匹配mary的时候也同样如此。END模块中打印出存储在数组中的各个元素。

    /> awk '{count[$1]++}; END{for(name in count) printf "%-5s%d\n",name, count[name]}' testfile2
    mary 4
    tom  2
    alex 1
    bob  1
    sean 1
    在上例中,awk是以记录的域作为数组count的下标。

    /> awk '{count[$1]++; if (count[$1] > 1) name[$1]++}; END{print "The duplicates were "; for(i in name) print i}' testfile2
    The duplicates were
    mary
    tom
    在上例中,如count[$1]的元素值大于1的时候,也就是当名字出现多次的时候,一个新的数组name将被初始化,最后打印出那么数组中重复出现的名字下标。

    之前我们介绍的都是如何给数组添加新的元素,并赋予初值,现在我们需要介绍一下如何删除数组中已经存在的元素。要完成这一功能我们需要使用内置函数delete,见如下命令:
    /> awk '{count[$1]++}; \
        END{for(name in count) {\
                if (count[name] == 1)\
                    delete count[name];\
            } \
            for (name in count) \
                print name}' testfile2
    mary
    tom
    上例中的主要技巧来自END模块,先是变量count数组,如果数组中某个元素的值等于1,则删除该元素,这样等同于删除只出现一次的名字。最后用special for循环打印出数组中仍然存在的元素下标名称。

    最后我们来看一下如何使用命令行参数数组,见如下命令:
    /> awk 'BEGIN {for(i = 0; i < ARGC; i++) printf("argv[%d] is %s.\n",i,ARGV[i]); printf("The number of arguments, ARGC=%d\n",ARGC)}' testfile "Peter Pan" 12
    argv[0] is awk.
    argv[1] is testfile.
    argv[2] is Peter Pan.
    argv[3] is 12.
    The number of arguments, ARGC=4
    从输出结果可以看出,命令行参数数组ARGV是以0作为起始下标的,命令行的第一个参数为命令本身(awk),这个使用方式和C语句main函数完全一致。

    /> awk 'BEGIN{name=ARGV[2]; print "ARGV[2] is " ARGV[2]}; $1 ~ name{print $0}' testfile2 "bob"    
    ARGV[2] is bob
    bob
    awk: (FILENAME=testfile2 FNR=9) fatal: cannot open file `bob' for reading (No such file or directory)
    先解释一下以上命令的含义,name变量被赋值为命令行的第三个参数,即bob,之后再在输入文件中找到匹配该变量值的记录,并打印出该记录。
    在输出的第二行报出了awk的处理错误信息,这主要是因为awk将bob视为输入文件来处理了,然而事实上这个文件并不存在,下面我们需要做进一步的处理来修正这个问题。
    /> awk 'BEGIN{name=ARGV[2]; print "ARGV[2] is " ARGV[2]; delete ARGV[2]}; $1 ~ name{print $0}' testfile2 "bob"    
    ARGV[2] is bob
    bob
    从输出结果中我们可以看到我们得到了我们想要的结果。需要注意的是delete函数的调用必要要在BEGIN模块中完成,因为这时awk还没有开始读取命令行参数中指定的文件。

    7.  内建函数:
    字符串函数
    sub(regular expression,substitution string);
    sub(regular expression,substitution string,target string);

    /> awk '{sub("Tom","Tommy"); print}' employees   #这里使用Tommy替换了Tom。
    Tommy Jones       4424    5/12/66         543354

    #当正则表达式Tom在第一个域中第一次被匹配后,他将被字符串"Tommy"替换,如果将sub函数的第三个参数改为$2,将不会有替换发生。
    /> awk '{sub("Tom","Tommy",$1); print}' employees
    Tommy Jones       4424    5/12/66         543354

    gsub(regular expression,substitution string);
    gsub(regular expression,substitution string,target string);
    和sub不同的是,如果第一个参数中正则表达式在记录中出现多次,那么gsub将完成多次替换,而sub只是替换第一次出现的。

    index(string,substring)
    该函数将返回第二个参数在第一个参数中出现的位置,偏移量从1开始。
    /> awk 'BEGIN{print index("hello","el")}'
    2

    length(string)
    该函数返回字符串的长度。
    /> awk 'BEGIN{print length("hello")}'
    5

    substr(string,starting position)
    substr(string,starting position,length of string)
    该函数返回第一个参数的子字符串,其截取起始位置为第二个参数(偏移量为1),截取长度为第三个参数,如果没有该参数,则从第二个参数指定的位置起,直到string的末尾。
    />  awk 'BEGIN{name = substr("Hello World",2,3); print name}'
    ell

    match(string,regular expression)
    该函数返回在字符串中正则表达式位置的索引,如果找不到指定的正则表达式就返回0.match函数设置内置变量RSTART为字符串中子字符串的开始位置,RLENGTH为到字字符串末尾的字符个数。
    /> awk 'BEGIN{start=match("Good ole CHINA", /[A-Z]+$/); print start}'
    10
    上例中的正则表达式[A-Z]+$表示在字符串的末尾搜索连续的大写字母。在字符串"Good ole CHINA"的第10个位置找到字符串"CHINA"。

    /> awk 'BEGIN{start=match("Good ole CHINA", /[A-Z]+$/); print RSTART, RLENGTH}'
    10 5
    RSTART表示匹配时的起始索引,RLENGTH表示匹配的长度。

    /> awk 'BEGIN{string="Good ole CHINA";start=match(string, /[A-Z]+$/); print substr(string,RSTART, RLENGTH)}'
    CHINA
    这里将match、RSTART、RLENGTH和substr巧妙的结合起来了。

    toupper(string)
    tolower(string)
    以上两个函数分别返回参数字符串的大写和小写的形式。
    /> awk 'BEGIN {print toupper("hello"); print tolower("WORLD")}'
    HELLO
    world

    split(string,array,field seperator)
    split(string,array)
    该函数使用作为第三个参数的域分隔符把字符串分隔为一个数组。如果第三个参数没有提供,则使用当前默认的FS值。
    /> awk 'BEGIN{split("11/20/2011",date,"/"); print date[2]}'
    20

    variable = sprintf("string with format specifiers ",expr1,expr2,...)
    该函数和printf的差别等同于C语言中printf和sprintf的差别。前者将格式化后的结果输出到输出流,而后者输出到函数的返回值中。
    /> awk 'BEGIN{line = sprintf("%-15s %6.2f ", "hello",4.2); print line}'
    hello             4.20

    时间函数:
    systime()
    该函数返回当前时间距离1970年1月1日之间相差的秒数。
    /> awk 'BEGIN{print systime()}'
    1321369554

    strftime()
    时间格式化函数,其格式化规则等同于C语言中的strftime函数提供的规则,见以下列表:

数据格式 含义
%a Abbreviated weekday name
%A Full weekday name
%b Abbreviated month name
%B Full month name
%c Date and time representation appropriate for locale
%d Day of month as decimal number (01 – 31)
%H Hour in 24-hour format (00 – 23)
%I Hour in 12-hour format (01 – 12)
%j Day of year as decimal number (001 – 366)
%m Month as decimal number (01 – 12)
%M Minute as decimal number (00 – 59)
%p Current locale's A.M./P.M. indicator for 12-hour clock
%S Second as decimal number (00 – 59)
%U Week of year as decimal number, with Sunday as first day of week (00 – 53)
%w Weekday as decimal number (0 – 6; Sunday is 0)
%W Week of year as decimal number, with Monday as first day of week (00 – 53)
%x Date representation for current locale
%X Time representation for current locale
%y Year without century, as decimal number (00 – 99)
%Y Year with century, as decimal number

    /> awk 'BEGIN{ print strftime("%D",systime())}'
    11/15/11
    /> awk 'BEGIN{ now = strftime("%T"); print now}'
    23:17:29

    内置数学函数:

名称 返回值
atan2(x,y) y,x范围内的余切
cos(x) 余弦函数
exp(x) 求幂
int(x) 取整
log(x) 自然对数
sin(x) 正弦函数
sqrt(x) 平方根

    /> awk 'BEGIN{print 31/3}'
    10.3333
    /> awk 'BEGIN{print int(31/3)}'
    10

    自定义函数:
    自定义函数可以放在awk脚本的任何可以放置模板和动作的地方。
    function name(parameter1,parameter2,...) {
        statements
        return expression
    }
    给函数中本地变量传递值。只使用变量的拷贝。数组通过地址或者指针传递,所以可以在函数内部直接改变数组元素的值。函数内部使用的任何没有作为参数传递 的变量都被看做是全局变量,也就是这些变量对于整个程序都是可见的。如果变量在函数中发生了变化,那么就是在整个程序中发生了改变。唯一向函数提供本地变 量的办法就是把他们放在参数列表中,这些参数通常被放在列表的最后。如果函数调用没有提供正式的参数,那么参数就初始化为空。return语句通常就返回 程序控制并向调用者返回一个值。
    /> cat grades
    20 10
    30 20
    40 30

    /> cat add.sc
    function add(first,second) {
            return first + second
    }
    { print add($1,$2) }

    /> awk -f add.sc grades
    30
    50
    70











本文转自 chengxuyonghu 51CTO博客,原文链接:http://blog.51cto.com/6226001001/1630827,如需转载请自行联系原作者
目录
相关文章
|
17天前
|
Web App开发 Java Linux
Linux之Shell基本命令篇
Linux之Shell基本命令篇
Linux之Shell基本命令篇
|
17天前
|
Linux Perl
Linux|从 STDIN 读取 Awk 输入
Linux|从 STDIN 读取 Awk 输入
20 4
|
30天前
|
算法 Linux C++
【Linux系统编程】解析获取和设置文件信息与权限的Linux系统调用
【Linux系统编程】解析获取和设置文件信息与权限的Linux系统调用
29 0
|
30天前
|
存储 算法 Linux
【Linux系统编程】深入理解Linux目录扫描函数:scandir目录函数(按条件扫描目录
【Linux系统编程】深入理解Linux目录扫描函数:scandir目录函数(按条件扫描目录
39 0
|
30天前
|
存储 算法 Linux
【Linux系统编程】Linux 文件系统探究:深入理解 struct dirent、DIR 和 struct stat结构
【Linux系统编程】Linux 文件系统探究:深入理解 struct dirent、DIR 和 struct stat结构
45 0
|
8天前
|
监控 Shell 开发工具
Shell编程
Shell编程
|
15天前
|
存储 Shell Linux
【攻防世界】unseping (反序列化与Linux bash shell)
【攻防世界】unseping (反序列化与Linux bash shell)
|
17天前
|
Linux 开发者
Linux文件编程(open read write close函数)
通过这些函数,开发者可以在Linux环境下进行文件的读取、写入和管理。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
85 4
|
18天前
|
Shell Linux
【Linux】12. 模拟实现shell
【Linux】12. 模拟实现shell
27 2
|
21天前
|
Linux Perl
Linux| Awk 中“next”命令奇用
Linux| Awk 中“next”命令奇用
26 9