learning awk

    awk 适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行需要特殊技巧程序设计。与某些语言不同,awk 的语法较为常见。它借鉴了某些语言的一些精华部分,如 C 语言、python 和 bash(虽然在技术上,awk 比 python 和 bash 早创建)。awk 是那种一旦学会了就会成为您战略编码库的主要部分的语言。

   $awk `{ print }` /etc/passwd

  在命令行中输入以上的命令,会显示/etc/passwd文件的内容。调用 awk 时,我们指定 /etc/passwd 作为输入文件。执行 awk 时,它依次对 /etc/passwd 中的每一行执行 print 命令。所有输出都发送到 stdout,所得到的结果与执行cat /etc/passwd完全相同。{ print } 代码块。在 awk 中,花括号用于将几块代码组合到一起,这一点类似于 C 语言。在代码块中只有一条 print 命令。在 awk 中,如果只出现 print 命令,那么将打印当前行的全部内容。

  $awk `{print $0}` /etc/passwd

  以上命令和前一命令执行结果一样,其中$0 变量表示整个当前行,所以 print 和 print $0 的作用完全一样。

  $awk `{print " "}` /etc/passwd

  $awk `{print "Hello"}` /etc/passwd

  以上命令对于/etc/passwd中的每一行或者输出空行或者输出Hello。

  awk 非常善于处理分成多个逻辑字段的文本,以下脚本输出所有用户账户的列表

   $awk -F ":" `{print $1}` /etc/passwd

  上例中,在调用 awk 时,使用 -F 选项来指定 ":" 作为字段分隔符。

  awk 处理 print $1 命令时,它会打印出在输入文件中每一行中出现的第一个字段。

  类似命令如下:

  $awk -F ":"  `{print $1 $3}` /etc/passwd

  $awk -F ":"  `{print $1 " " $3}` /etc/passwd

  $awk -F ":"  `{print "username:" $1 "\t\tuid:" $3}` /etc/passwd

  上面三条命令中的前两条虽然都能输出第一和第三个字段,但是由于脚本运行时,在两个输出字段之间没有空格!如果习惯于使用 bash 或 python 进行编程,那么您会指望 print $1 $3 命令在两个字段之间插入空格。然而,当两个字符串在 awk 程序中彼此相邻时,awk 会连接它们但不在它们之间添加空格,所以必须要自己添加上空格。上面的第三条命令添加了可读的输出。

  可以在外部文件中编写脚本,然后将脚本通过awk -f传递。这样可以增加awk的功能。例如:下面的例子也能打印用户列表

  $awk -f myscript.awk myfile.in

 myscript.awk的内容:

BEGIN

{

  FS=":"

}

{print $1}

 在这个脚本中,字段分隔符在代码自身中指定(通过设置 FS 变量),而在前一个示例中,通过在命令行上向 awk 传递 -F":" 选项来设置 FS。

BEGIN 和 END 块

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

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

规则表达式和块

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

重写myscript.awk内容:

 BEGIN

{

  FS=":"

}

/foo/{print $1}

或者编写更复杂的正则表达式:只打印包含浮点数的行

 重写myscript.awk内容:

 BEGIN

{

  FS=":"

}

/[0-9]+\.[0-9]*/{print $1}

表达式和块

    还有许多其它方法可以选择执行代码块。我们可以将任意一种布尔表达式放在一个代码块之前,以控制何时执行某特定块。仅当对前面的布尔表达式求值为真时,awk 才执行代码块。以下示例脚本输出将输出其第一个字段等于 fred 的所有行中的第三个字段。如果当前行的第一个字段不等于 fred ,awk 将继续处理文件而不对当前行执行 print 语句: 

 重写myscript.awk内容:

 BEGIN

{

  FS=":"

}

$1=="fred"{print  $3}

   awk 提供了完整的比较运算符集合,包括 "=="、"<"、">"、"<="、">=" 和 "!="。另外,awk 还提供了 "~" 和 "!~" 运算符,它们分别表示“匹配”和“不匹配”。它们的用法是在运算符左边指定变量,在右边指定规则表达式。如果某一行的第五个字段包含字符序列 root ,那么以下示例将只打印这一行中的第三个字段: 

 重写myscript.awk内容:

  BEGIN

 {

  FS=":"

 }

 $5 ~ /root/{print  $3}

 条件语句

 

  awk 还提供了非常好的类似于 C 语言的 if 语句。如果您愿意,可以使用 if 语句重写前一个脚本:

重写myscript.awk内容:

  BEGIN

{

  FS=":"

}

{

if ( $5 ~ /root/)

  print  $3

}

 

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

  另外一些if的实例:

script内容1:

{

  if($0 !~/machme/)

    print $1 $2 $3

}

script内容2:

{

  if($1=="foo" && $2=="bar")

    print  

}

 数值变量

     至今,我们不是打印字符串、整行就是打印特定字段。然而,awk 还允许我们执行整数和浮点运算。通过使用数学表达式,可以很方便地编写计算文件中空白行数量的脚本。以下就是这样一个脚本:

 重写myscript.awk内容:

BEGIN   { x=0 }
/^$/    { x=x+1 }
END     { print "I found " x " blank lines. :)" } 

 

字符串化变量

 

    awk 的优点之一就是“简单和字符串化”。awk 变量“字符串化”是因为所有 awk 变量在内部都是按字符串形式存储的。同时,awk 变量是“简单的”,因为可以对它执行数学操作,且只要变量包含有效数字字符串,awk 会自动处理字符串到数字的转换步骤。要理解我的观点,请研究以下这个示例:

 

x="1.01"
# We just set x to contain the *string* "1.01"
x=x+1
# We just added one to a *string* 
print x
# Incidentally, these are comments :)

awk 将输出:


2.01

  虽然将字符串值 1.01 赋值给变量 x ,我们仍然可以对它加一。但在 bash 和 python 中却不能这样做。首先,bash 不支持浮点运算。而且,如果 bash 有“字符串化”变量,它们并不“简单”;要执行任何数学操作,bash 要求我们将数字放到丑陋的 $( ) ) 结构中。如果使用 python,则必须在对 1.01 字符串执行任何数学运算之前,将它转换成浮点值。虽然这并不困难,但它仍是附加的步骤。如果使用 awk,它是全自动的,而那会使我们的代码又好又整洁。如果想要对每个输入行的第一个字段乘方并加一,可以使用以下脚本:

 


{ print ($1^2)+1 }

 

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

 

众多运算符

 

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

 

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

字段分隔符

    awk 有它自己的特殊变量集合。其中一些允许调整 awk 的运行方式,而其它变量可以被读取以收集关于输入的有用信息。我们已经接触过这些特殊变量中的一个,FS。前面已经提到过,这个变量让您可以设置 awk 要查找的字段之间的字符序列。我们使用 /etc/passwd 作为输入时,将 FS 设置成 ":"。当这样做有问题时,我们还可以更灵活地使用 FS。

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


FS="\t+"

以上示例中,我们使用特殊 "+" 规则表达式字符,它表示“一个或多个前一字符”。

如果字段由空格分隔(一个或多个空格或 tab),您可能想要将 FS 设置成以下规则表达式:


FS="[[:space:]+]"

    这个赋值表达式也有问题,它并非必要。为什么?因为缺省情况下,FS 设置成单一空格字符,awk 将这解释成表示“一个或多个空格或 tab”。在这个特殊示例中,缺省 FS 设置恰恰是您最想要的!

    复杂的规则表达式也不成问题。即使您的记录由单词 "foo" 分隔,后面跟着三个数字,以下规则表达式仍允许对数据进行正确的分析:


FS="foo[0-9][0-9][0-9]"

 

字段数量

 

    接着我们要讨论的两个变量通常并不是需要赋值的,而是用来读取以获取关于输入的有用信息。第一个是 NF 变量,也叫做“字段数量”变量。awk 会自动将该变量设置成当前记录中的字段数量。可以使用 NF 变量来只显示某些输入行:

 


NF == 3 { print "this particular record has three fields: " $0 }

 当然,也可以在条件语句中使用 NF 变量,如下:

 


{   
    if ( NF > 2 ) {
        print $1 " " $2 ":" $3 
    }
}

 

 记录号

    记录号 (NR) 是另一个方便的变量。它始终包含当前记录的编号(awk 将第一个记录算作记录号 1)。迄今为止,我们已经处理了每一行包含一个记录的输入文件。对于这些情况,NR 还会告诉您当前行号。然而,当我们在本系列以后部分中开始处理多行记录时,就不会再有这种情况,所以要注意!可以象使用 NF 变量一样使用 NR 来只打印某些输入行:


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

另一个示例:


{
    #skip header
    if ( NR > 10 ) {
        print "ok, now for the real information!"
    }
}

 

 


注意:几个变量的区别:

NR - Number of Record - 当前处理的行是第几行(因为awk是流处理工具,一行一行处理的,所以NR在不停的自增1)
FNR - File Number of Record - 当前处理的行是当前处理文件的第几行
NF - Number of Fileds - 当前行有多少列数据(这个在每行都会根据设定的分割符重新计算,默认分割符是任务长个空白符)

(NR==FNR) 大致等效于 (ARGIND==1) , 前者就是说当然处理的总行数跟文件内行数相等,而这种情况一般来说都是在第一个文件上,而ARGIND==1是参数位置第1的文件时生效。<也有可能前面的文件是空的,那么NR==FNR就生效于第一个非空文件,这就是跟ARGIND==1的明显区别> 

NR,表示awk开始执行程序后所读取的数据行数.

FNR,与NR功用类似,不同的是awk每打开一个新文件,FNR便从0重新累计.

下面看两个例子:

1,对于单个文件NR 和FNR 的 输出结果一样的 :

# awk '{print NR,$0}' file1 
1 a b c d
2 a b d c

3 a c b d

#awk '{print FNR,$0}' file1 

1 a b c d
2 a b d c
3 a c b d 

2,但是对于多个文件 :

# awk '{print NR,$0}' file1 file2
1 a b c d
2 a b d c
3 a c b d
4 aa bb cc dd
5 aa bb dd cc
6 aa cc bb dd

# awk '{print FNR,$0}' file1 file2
1 a b c d
2 a b d c
3 a c b d
1 aa bb cc dd
2 aa bb dd cc
3 aa cc bb dd

在看一个例子关于NR和FNR的典型应用:

现在有两个文件格式如下:

#cat account
张三|000001
李四|000002
#cat cdr
000001|10
000001|20
000002|30
000002|15

想要得到的结果是将用户名,帐号和金额在同一行打印出来,如下:

张三|000001|10
张三|000001|20
李四|000002|30
李四|000002|15

执行如下代码

#awk -F \| 'NR==FNR{a[$2]=$0;next}{print a[$1]"|"$2}' account cdr

注释:

由NR=FNR为真时,判断当前读入的是第一个文件account,然后使用{a[$2]=$0;next}循环将account文件的每行记录都存入数组a,并使用$2第2个字段作为下标引用.

由NR=FNR为假时,判断当前读入了第二个文件cdr,然后跳过{a[$2]=$0;next},对第二个文件cdr的每一行都无条件执行{print a[$1]"|"$2},此时变量$1为第二个文件的第一个字段,与读入第一个文件时,采用第一个文件第二个字段$2为数组下标相同.因此可以在此使用a[$1]引用数组。


参考:http://www.ibm.com/search/csass/search/?sn=dw&lang=zh&cc=CN&en=utf&hpp=20&dws=cndw&lo=zh&q=awk&Search=%E6%90%9C%E7%B4%A2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2012-08-09 21:21  王耀it  阅读(282)  评论(0编辑  收藏  举报