22 September 2014

在管理和维护 Linux系统过程中,有时可能需要从一个具有一定格式的文本(格式化文本)中抽取数据,这时可以使用awk编辑器来完成这项任务。发明这个工具的作者是Aho、Weinberg和Kernighan,取三个人名的首字母而得名awk。

与sed相比,awk更擅长处理格式化文本。格式化文本一般使用某个特定的字符(称为域分隔符)将文本中不同的字段(称为域)隔开。例如用于保存用户信息的系统用户文件/etc/passwd,该文件使用冒号分别将用户名、密码、UID等字段分隔开。

awk 命令执行流程概述

####概念

####命令格式:

awk [-F] 'command' input-file

awk –f script input-file

与sed类似,awk也有两种调用方式:第一种是直接使用awk命令调用,选项F用于指定域分隔符。默认情况下awk使用的域分隔符是空格,如果要处理的文件input-file的域分隔符不是空格,应该使用F选项另行指定。第二种方法跟sed一样,先将要输入的选项模式和动作放入一个脚本文件中,然后使用选项f调用。

####命令处理过程:

awk被调用后,首先读入第一行文本并按选项F指定的域分隔符将各个字段划开。以/etc/passwd其中一行为例:

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

处理这个文件时,应该使用选项F指定域分隔符为冒号“:”,划分完成后将这一行称为一条记录。一条记录中的各个字段按顺序称为域1,域2,域3……为方便对这些字段进行处理,使用标识符“$1”表示第一个字段,“$2”表示第二个字段…依次类推。如果要表示整条记录,应该使用标识符“$0”。

划分记录后,awk会按预定的模式和动作处理记录。处理完一条记录后,又会读取文本第二行重复上述动作。

####模式和动作:

一个完整的awk命令由编辑语句和格式化文本组成。编辑语句由一个或多个模式和动作组成,格式化文本即用户需要处理的文本,可以来自文件,也可以来自命令输出。

与sed一样,模式用来指定动作执行的文本位置。在awk中,模式可以是条件语句、模式匹配、正则表达式等。如果没有指定模式,awk 会对所有记录执行编辑语句。

动作一般放在模式后面的大括号{}内,一般是awk的内置函数,例如输出函数 print 等。

下面是一个输出文件/etc/passwd中所有用户名的示例:

	[root@localhost test]# awk -F: '{print "username:" $1}' /etc/passwd
	username:root
	username:bin
	username:daemon
	username:adm
	username:lp
	...

上面这个示例命令使用选项 F 指定域分隔符为冒号,后面的命令中并没有指定模式,awk将默认匹配所有行。放在大括号内的动作使用了awk的内置函数print,先输出了一个字符串“username:”,然后使用标识符“$1”输出第一个字段。

####文本头、尾表达式:

在awk中,有两个特殊的表达式BEGIN和END。BEGIN语句将在编辑语句开始执行之前运行,通常用来输出文本头信息。END语句则在所有编辑语句完成之后执行,一般用来输出结束信息和统计数据等。

使用示例文件 students(见查找文本工具grep),制作一个输出学生学号和姓名,并输出文本头、文本尾的例子:

[root@localhost test]# awk 'BEGIN{print "Student ID     name\n-----------------------"}{print $1"\t"$2}END{print "------------END-----------"}' students
Student ID      name
-----------------------
2821020225      Liulu
2821020115      Liumin
2721020321      Xuli
2921020632      Xiayu
2721010409      Liwei
2921050313      Heli
2721030227      Wangtao
------------END-----------

###awk 参数说明 awk [ -F re] [parameter…] [‘pattern {action}’ ] [-f progfile][in_file…]

	-F re : 允许awk更改其字段分隔符,默认空格

			A.-F参数后紧跟单个分隔符,则用双引号"",例如 –F"+"
			B.-F参数后紧跟多个分隔符,则用单引号' '并用[ ],中间顺序无所谓,例如-F '[+$]'

                awk -F '[(,]' '{print $2"\t"$3}'')]'
                awk -F'[ :]+' '{print $NF"\t"$(NF-2)}'  file1.txt  

                也可用split函数

	parameter:	该参数帮助为不同的变量赋值

	'pattern {action}'	:	awk的程序语句段。这个语句段必须用单拓号:'和'括起,以防被shell解释

	-f progfile:	允许awk调用并执行progfile指定有程序文件。progfile是一个文本文件,他必须符合awk的语法

	in_file:	awk的输入文件,awk允许对多个输入文件进行处理。值得注意的是awk不修改输入文件。
				如果未指定输入文件,awk将接受标准输入,并将结果显示在标准输出上。awk支持输入输出重定向。

	BEGIN{….}{…..}END{……}  1 位置:'{}'

							2 作用:BEGIN和END的作用是给程序赋予初始状态和在程序结束之后执行一些扫尾的工作
							BEGIN{}:awk开始扫描输入之前执行 (显示变量和预置(初始化)变量 )
							END{}:在扫描完全部的输入之后执行 (最终结果 )
							{}:操作

BEGIN部分:设置计数和打印头

END部分:打印输出文本总数及结尾状态标识

###正则表达式、元字符、运算符和关系运算符

在awk中,除了可以使用正则表达式和元字符外,awk还内置了算术运算符、操作符。

  • 常见的算术运算符

      =、+、-、-(负)、*、/、++、--、?:、%、+=、-=、*=、/=、%=、^、^=、in(类似shell中的in)
    
  • 常见的关系运算符

      >、>=、<、<=、!=、==
    
      如:> < 可以作为字符串比较,也可以用作数值比较,关键看操作数如果是字符串 就会转换为字符串比较。两个都为数字 才转为数值比较。字符串比较:按照ascii码顺序比较。
    
  • 正则运算符 ~/pattern/(匹配pattern)、!~/pattern/(不匹配pattern)

  • 逻辑运算符

      ||、&&、!
    
  • 其他 空格(字符串连接)

####用法示例

结合正则表达式,输出所有2007年入学的学生信息:

	[root@localhost test]# awk '/27210/{print $0}' students
	2721020321 Xuli         Jiangsu Luolei  12/25/92        76 81 85 79 321 80
	2721010409 Liwei        Sichuan tangwei 11/21/92        98 88 85 85 356 89
	2721030227 Wangtao      Yunnan  Huli    03/21/93        87 76 69 88 320 80

结合正则表达式,输出所有来自Sichuan的学生学号、姓名和总分:

	[root@localhost test]# awk '/Sichuan/{print $1"\t"$2"\t"$10}' students
	2821020225      Liulu   325
	2721010409      Liwei   356

可以指定某个域的取值精确匹配。例如要输出姓名为Liwei的学生信息:

	[root@localhost test]# awk '$2=="Liwei"{print $0}' students
	2721010409 Liwei        Sichuan tangwei 11/21/92        98 88 85 85 356 89

精确匹配时,可以使用“!=”表示不匹配。例如要输出不是来自Sichuan的学生信息:

	[root@localhost test]# awk '$3!="Sichuan"{print $0}' students
	2821020115 Liumin       Henan   lixia   05/14/94        78 65 59 78 280 70
	2721020321 Xuli         Jiangsu Luolei  12/25/92        76 81 85 79 321 80
	2921020632 Xiayu        Shanxi  Hetao   03/26/93        78 86 92 78 334 84
	2921050313 Heli         Xizang  Tangwei 07/12/94        56 78 80 45 259 65
	2721030227 Wangtao      Yunnan  Huli    03/21/93        87 76 69 88 320 80

也可以使用操作符表示匹配,例如要输出所有辅导员不是Tangwei的学生的信息:

	[root@localhost test]# awk '$4 !~/[Tt]angwei/{print $0}' students
	2821020225 Liulu        Sichuan Lixia   01/23/93        89 76 88 72 325 81
	2821020115 Liumin       Henan   lixia   05/14/94        78 65 59 78 280 70
	2721020321 Xuli         Jiangsu Luolei  12/25/92        76 81 85 79 321 80
	2921020632 Xiayu        Shanxi  Hetao   03/26/93        78 86 92 78 334 84
	2721030227 Wangtao      Yunnan  Huli    03/21/93        87 76 69 88 320 80

也可以将匹配操作符与正则表达式一起使用。例如输出所有93年以后出生的学生信息:

	[root@localhost test]# awk '$5 ~/^.......[3-9]/{print $0}' students
	2821020225 Liulu        Sichuan Lixia   01/23/93        89 76 88 72 325 81
	2821020115 Liumin       Henan   lixia   05/14/94        78 65 59 78 280 70
	2921020632 Xiayu        Shanxi  Hetao   03/26/93        78 86 92 78 334 84
	2921050313 Heli         Xizang  Tangwei 07/12/94        56 78 80 45 259 65
	2721030227 Wangtao      Yunnan  Huli    03/21/93        87 76 69 88 320 80
使用竖线“ ”匹配两边模式之一,例如要输出所有来自Sichuan和Yunnan的学生信息:
	[root@localhost test]# awk '$3 ~/(Sichuan|Yunnan)/{print $0}' students
	2821020225 Liulu        Sichuan Lixia   01/23/93        89 76 88 72 325 81
	2721010409 Liwei        Sichuan tangwei 11/21/92        98 88 85 85 356 89
	2721030227 Wangtao      Yunnan  Huli    03/21/93        87 76 69 88 320 80

在使用$0标识符输出所有域时,也可以省略后面的输出动作。下面这条命令和上面的命令效果一样:

	[root@localhost test]# awk '$3 ~/(Sichuan|Yunnan)/' students
	2821020225 Liulu        Sichuan Lixia   01/23/93        89 76 88 72 325 81
	2721010409 Liwei        Sichuan tangwei 11/21/92        98 88 85 85 356 89
	2721030227 Wangtao      Yunnan  Huli    03/21/93        87 76 69 88 320 80

可以使用算术运算符对域进行计算,这在制作一些统计信息时非常有用。例如要计算所有公共课的平均成绩并输出:

	[root@localhost test]# awk '{print $1"\t"$2"\t"$6"\t"$7"\t"$8"\t"$6+$7+$8"\t"($6+$7+$8)/3}' students
	2821020225      Liulu   89      76      88      253     84.3333
	2821020115      Liumin  78      65      59      202     67.3333
	2721020321      Xuli    76      81      85      242     80.6667
	2921020632      Xiayu   78      86      92      256     85.3333
	2721010409      Liwei   98      88      85      271     90.3333
	2921050313      Heli    56      78      80      214     71.3333
	2721030227      Wangtao 87      76      69      232     77.3333

可以结合使用关系运算符和逻辑运算符,以实现更复杂的运算。例如要输出所有来自Sichuan并且平均分大于80的学生的信息:

	[root@localhost test]# awk '($3=="Sichuan" && $11>80){print $0}' students
	2821020225 Liulu        Sichuan Lixia   01/23/93        89 76 88 72 325 81
	2721010409 Liwei        Sichuan tangwei 11/21/92        98 88 85 85 356 89

注意:使用awk时,一定要将多个模式和条件放在括号中,将执行编辑的语句放在单引号内,函数和流控制语句放入大括号内,避免产生错误。

正则运算语句(~,~!等同!~)

	[root@localhost test]# awk 'BEGIN{info="this is a test";if( info ~ /test/){print "ok"}}'
	ok

	
	awk ‘/REG/{action}’
	/REG/为正则表达式,可以将$0中,满足条件记录 送入到:action进行处理.

字符串转数字

	[root@localhost test]# awk 'BEGIN{a="100";b="10test10";print (a+b+0);}'
	110 
	只需要将变量通过”+”连接运算。自动强制将字符串转为整型。非数字变成0,发现第一个非数字字符,后面自动忽略。 

数字转为字符串

	[root@localhost test]# awk 'BEGIN{a=100;b=100;c=(a""b);print c}'     
	100100

	""为符号连接运算符。

###使用变量###

awk中的变量有两种:一种是内置变量,这些变量常用于控制输出和保存“awk”当前工作状态等信息,在引用变量时通常不需要使用美元符号$;另一种是用户自定义变量,这些变量由用户自己定义并使用,通常自定义变量放在BEGIN语句中初始化(赋值)。

使用自定义变量时,如果awk不能确定自定义变量的类型,通常将其默认为字符串进行处理。

####内置变量####

记录 :awk把每一个以换行符结束的行称为一个记录。
分隔符:默认的输入和输出的分隔符都是回车,保存在内建变量ORS和RS中。也称域

$0			当前记录(作为单个变量)
FILENAME	用于保存输入文件的文件名称。
$1~$n		当前记录的第n个字段,字段间由FS分隔
FS			用于设置字段分隔符,默认空格
NF			保存当前正在处理的记录的字段的个数。即有多少列
NR			保存从文本中读取记录的个数。即行号,从1开始
FNR			保存当前读取的记录数。当输入的文件有多个时,读取新文件时,awk会重置这个变量。
RS			设置记录分隔符,默认为换行符。
OFS			设置输出字段的分隔符,默认为空格。
ORS			用于设置输出记录分隔符,默认为新的一行。
ARGC 		命令行参数个数
ARGV 		命令行参数数组
IGNORECASE 	如果为真,则进行忽略大小写的匹配
ARGIND 		当前被处理文件的ARGV标志符
OFMT		数字的输出格式。
CONVFMT 	数字转换格式 %.6g
ENVIRON		读取环境变量
ERRNO 		UNIX系统错误消息
FIELDWIDTHS 输入字段宽度的空白分隔字符串
FNR 		当前记录数
RSTART 		被匹配函数匹配的字符串首
RLENGTH 	被匹配函数匹配的字符串长度
SUBSEP 		\034

getline 实例

	awk 'BEGIN {system("echo "Input your name://c""); getline d;print "/nYour name is",d,"/b!/n"}' 
	通过getline命令交互输入name,并显示出来。

	awk 'BEGIN {FS=":"; while(getline< "/etc/passwd" >0) { if($1~"050[0-9]_") print $1}}' 
	打印/etc/passwd文件中用户名包含050x_的用户名。 

获得传入的文件名(FILENAME使用)

	[root@localhost test]# awk 'BEGIN{FS=":";print FILENAME}{print FILENAME}' /etc/passwd

	/etc/passwd

	FILENAME,$0-$N,NF 不能使用在BEGIN中,BEGIN中不能获得任何与文件记录操作的变量。

参数格式和参数列表

	[root@localhost test]#awk 'BEGIN{FS=":";print "ARGC="ARGC;for(k in ARGV) {print k"="ARGV[k]; }}' /etc/passwd
	ARGC=2
	0=awk
	1=/etc/passwd 

有时可能需要对输出的文本重新格式化(设置你想要的输出形式),这时可以使用内置变量OFS和ORS,设置输出文本的域分隔符和记录分隔符。

	[root@localhost test]# awk 'BEGIN{FS=":"; OFS="\t"; print "NF\tNR\tusername\tshell"}{print NF,NR,$1,$7}END{print FILENAME}' /etc/passwd
	NF      NR      username        shell
	7       1       root    /bin/bash
	7       2       bin     /sbin/nologin
	7       3       daemon  /sbin/nologin
	7       4       adm     /sbin/nologin
	7       5       lp      /sbin/nologin
	7       6       sync    /bin/sync
	......

有时会遇到一些使用特殊分隔符的文本。例如下面这个示例中,需要在命令中把FS和RS重新设置成文本中使用的的特殊分隔符(而不是默认的空格和新行)才能顺利读取文本:

	[root@localhost test]# cat students3
	2821020225#Liulu#0#A#B#0\

	2821020115#Liumi#B#C#0#0\

	2721020321#Xuli#0#D#A#0\

	2921020632#Xiayu#A#C#0#0\

	2721010409#Liwei#B#C#0#D\

	2921050313#Heli#B#0#D#0\

	2721030227#Wangtao#C#0#D#0\

	[root@localhost test]# awk 'BEGIN{FS="#"; RS="\\n\n"}{print $1,$2}' students3
	2821020225 Liulu
	2821020115 Liumi
	2721020321 Xuli
	2921020632 Xiayu
	2721010409 Liwei
	2921050313 Heli
	2721030227 Wangtao

当输出的字符为一个浮点数时,可能需要指定浮点数的输出格式,此时可以使用内置变量OFMT。例如:

	[root@localhost test]# awk 'BEGIN{OFMT="%.2f"; print 19.243564}'
	19.24

	提示:使用awk命令时,如果没有编辑语句,只有BEGIN语句,可以不必输入文件名直接运行。

有时可能在处理过程中需要引用环境变量,此时可以使用ENVIRON变量。如下是一个使用ENVIRON读取环境变量的示例:

	[root@localhost test]# awk 'END{print ENVIRON["LANG"]}' students
	en_US.UTF-8
	
	上面这个例子中虽然没有引用文件中的内容,但使用了END表达式,因此必须使用文件参数,否则这条命令将变的不完整。

按宽度指定分隔符(FIELDWIDTHS使用)

	[root@localhost test]# echo 20100117054932 | awk 'BEGIN{FIELDWIDTHS="4 2 2 2 2 3"}{print $1"-"$2"-"$3,$4":"$5":"$6}'
	2010-01-17 05:49:32
FIELDWIDTHS其格式为空格分隔的一串数字,用以对记录进行域的分隔,FIELDWIDTHS=”4 2 2 2 2 2”就表示$1宽度是4,$2是2,$3是2 …. 。这个时候会忽略:FS分隔符。

RSTART RLENGTH使用

	[root@localhost test]# awk 'BEGIN{start=match("this is a test",/[a-z]+$/); print start, RSTART, RLENGTH }'
	11 11 4

	[root@localhost test]# awk 'BEGIN{start=match("this is a test",/^[a-z]+$/); print start, RSTART, RLENGTH }'
	0 0 –1

####自定义变量####

自定义变量通常用于统计并计算某个数字字段的结果,也可用于引用字符串。

下面是一个统计当前文件夹中所有文件占用空间的示例:

	[root@localhost test]# ls -l | awk 'BEGIN{A=0}{A=A+$5}END{print "The size of all files of the current directory is:"A}'
	The size of all files of the current directory is:13232

在上面这个示例中,首先在BEGIN表达式中定义了一个变量A并初始化为 0.然后与ls命令输出的第 5 个字段相加,得到所有文件占用的空间,最后在 END 表达式中将结果输出。

变量也可以在动作语句之后添加,例如:

	[root@localhost test]# awk '$2==NAME1{print $0}' NAME1="Liwei" students
	2721010409 Liwei        Sichuan tangwei 11/21/92        98 88 85 85 356 89

	提示:awk中引用变量时,通常不需要使用引用符号。但为了便于理解和阅读,应将变量名称大写并加上引用符号。

####数组####

用awk进行文本处理,少不了就是它的数组处理。那么awk数组有那些特点,一般常见运算又会怎么样呢。我们先看下下面的一些介绍,结合例子我们会讲解下它的不同之处。在 awk 中数组叫做关联数组(associative arrays),因为下标记可以是数也可以是串。awk 中的数组不必提前声明,也不必声明大小。数组元素用 0 或空串来初始化,这根据上下文而定。

	[root@localhost test]# awk 'BEGIN{info="it is a test";split(info,tA," ");for(k in tA){print k,tA[k];}}'
	4 test
	1 it
	2 is
	3 a

	for…in 输出,因为数组是关联数组,默认是无序的。所以通过for…in 得到是无序的数组。如果需要得到有序数组,需要通过下标获得。

	[root@localhost test]# awk 'BEGIN{info="it is a test";tlen=split(info,tA," ");for(k=1;k<=tlen;k++){print k,tA[k];}}'
	1 it
	2 is
	3 a
	4 test

	注意:数组下标是从1开始,与c数组不一样。 

判读键值是否存在

	一个错误的判断方法:

	[root@localhost test]# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if(tB["c"]!="1"){print "no found";};for(k in tB){print k,tB[k];}}'
	no found
	a a1
	b b1
	c

 
	以上出现奇怪问题,tB[“c”]没有定义,但是循环时候,发现已经存在该键值,它的值为空,这里需要注意,awk数组是关联数组,只要通过数组引用它的key,就会自动创建改序列.

 

	正确判断方法:

	[root@localhost test]# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if( "c" in tB){print "ok";};for(k in tB){print k,tB[k];}}' 
	a a1
	b b1

	if(key in array) 通过这种方法判断数组中是否包含”key”键值。

删除键值

	[root@localhost test]# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";delete tB["a"];for(k in tB){print k,tB[k];}}'                    
	b b1
delete array[key]可以删除,对应数组key的,序列值。

####多维数组

awk的多维数组在本质上是一维数组,更确切一点,awk在存储上并不支持多维数组。awk提供了逻辑上模拟二维数组的访问方式。例 如,array[2,4] = 1这样的访问是允许的。awk使用一个特殊的字符串SUBSEP (\034)作为分割字段,在上面的例子中,关联数组array存储的键值实际上是2\0344。

类似一维数组的成员测试,多维数组可以使用 if ( (i,j) in array)这样的语法,但是下标必须放置在圆括号中。
类似一维数组的循环访问,多维数组使用 for ( item in array )这样的语法遍历数组。与一维数组不同的是,多维数组必须使用split()函数来访问单独的下标分量。split ( item, subscr, SUBSEP)

 
方法一

	[root@localhost test]# awk 'BEGIN{

	for(i=1;i<=9;i++)
	{
	  for(j=1;j<=9;j++) 
	  {
	tarr[i,j]=i*j;
	print i,"*",j,"=",tarr[i,j];
	  }
	}
	}'
	1 * 1 = 1
	1 * 2 = 2
	1 * 3 = 3
	1 * 4 = 4
	1 * 5 = 5
	1 * 6 = 6
   		……

可以通过array[k,k2]引用获得数组内容.

 

方法二

	[root@localhost test]# awk 'BEGIN{
	for(i=1;i<=9;i++)
	{
	  for(j=1;j<=9;j++) 
	  {
	tarr[i,j]=i*j;
	  }
	}
	for(m in tarr)             
	{

	split(m,tarr2,SUBSEP);
	print tarr2[1],"*",tarr2[2],"=",tarr[m];
	}
	}'

关于数组的需要注意的地方,见附录。

####获得普通外部变量

格式如:
	awk ‘{action}’  变量名=变量值   ,这样传入变量,可以在action中获得值。 注意:变量名与值放到’{action}’后面。
	
	[root@localhost test]# test='awk code'
	[root@localhost test]# echo | awk  '{print test}' test="$test"
	awk code
	[root@localhost test]# echo | awk  test="$test" '{print test}'
	awk: cmd. line:1: fatal: cannot open file `{print test}' for reading (No such file or directory) 

	注:这种变量在:BEGIN的action不能获得。
	[root@localhost test]# echo | awk  'BEGIN{print test}' test="$test"


格式如:
	awk –v 变量名=变量值 [–v 变量2=值2 …] 'BEGIN{action}’ 
	[root@localhost test]# test='awk code'                                
	[root@localhost test]# echo | awk -v test="$test" 'BEGIN{print test}'
	awk code
	[root@localhost test]# echo | awk -v test="$test" '{print test}'    
	awk code

此外 awk 内置变量 ENVIRON,就可以直接获得环境变量。它是一个字典数组。环境变量名
就是它的键值。

###流程控制

在awk命令中,还内置了一些流程控制语句(简称流控制语句)。使用这些流控制语句,可以完成更多复杂的抽取任务。

awk中的流控制语句语法结构:

awk命令中常见的流控制语句有:if、while、do-while和for语句,这些语句的基本语法结构与c语言中的语句类似。

  • if语句的基本格式:

      if (条件表达式)
      {
          语句块1
      }
      else if 
      {
          语句块2
      }
      else
      {
          语句块3
      }
    
  • while语句基本格式:

      while (条件表达式)
      {
          语句块
      }
    
  • do-while语句基本格式:

    do { 语句块 } while (条件表达式)

  • for语句基本格式:

    for (初始表达式;条件表达式;步长) { 语句块 }

    for(变量 in 数组) {语句}

    for(变量;条件;表达式) {语句}

  • 控制语句:

continue:用于在循环语句中控制循环走向。执行到此语句时,立即从当前执行位置跳转到循环开始处开始下一次循环。此语句用于标识后面的语句已经没有必要执行了,必须立即开始下一次循环执行过程。

break:用于跳出循环。当执行到此语句时,循环立即停止,然后执行循环后面的语句。

next:使awk读取文本的下一行。该语句告诉awk,当前行已经没有任何价值,需要立即读取文本的下一行。

exit:如果这条语句没有出现在END表达式中,则立即跳转去执行END中的语句(即直接跳过文件最后一行,执行END表达式中的语句)。如果出现在END表达式中,则awk命令立即停止执行并退出。

提示:由于循环语句会导致命令变得很长,且影响阅读,因此通常建议将循环语句放入脚本文件中执行。

####流控制语句用法示例:

next在某条件时跳过该行,对下一行执行操作。 awk ‘{gsub(//$/,””);gsub(/,/,””); if ($4>3000) next; else c4+=$4; } END {printf “c4=[%d]/n”,c4}”’ file

例如要输出文件students中,所有总成绩320以上的学生信息:

[root@localhost test]# awk 'BEGIN{OFS="\t"}{if($10>=320) print $1,$2,$10}' students
2821020225      Liulu   325
2721020321      Xuli    321
2921020632      Xiayu   334
2721010409      Liwei   356
2721030227      Wangtao 320

输出来自Sichuan的学生中,总成绩大于等于320的学生信息:

[root@localhost test]# awk 'BEGIN{OFS="\t"}{if($3=="Sichuan" && $10>=320) print $1,$2,$10}' students
2821020225      Liulu   325
2721010409      Liwei   356

计算ls命令传送过来的信息中,所有普通文件的大小和文件夹的个数:

	[root@localhost test]# cat total.awk
	BEGIN{
			#Initialize A and count.
			A=0;
			count=0;
	}
	{
			#if the first character of field 1 is '-', then add A and the  value of field 5.
			if($1 ~/^-/)
			A+=$5;
			#if the first character of field 1 is 'd', then count plus 1.
			if($1 ~/^d/)
			count++;
	}
	END{
			print "total:"A;
			#count-2:because there are current directory and up level directory.
			print count-2," directories.";
	}

	[root@localhost test]# ls -al /etc | awk -f total.awk
	total:1423743
	84  directories.

Linux系统管理员经常需要关注网络延时大小,以免对应用服务的正常运作造成影响。最好的解决办法是每隔一段时间,使用ping命令测试与某个服务器的连接,并统计延时。

下面是一个使用ping命令发送4个ICMP数据包测试双向连通性,并用awk从中计算最大延时、最小延时和平均延时的示例:

	[root@localhost test]# cat ping.awk
	BEGIN{
			#使用重新定义变量FS的方法设置域分隔符为冒号、等号及空格
			FS="[:= ]";
			AVG=0;
			MAX=0;
			MIN=0;
	}
	{
			for(I=1;I<9;I++)
			{
			       #如果当前为第二条记录,则为变量MAX、MIN、IP_ADDR赋值
			        if(NR==2)
			        {
			                MAX=$11;
			                MIN=$11;
			                IP_ADDR=$4;
			        }
			        if(NR>1 && NR<6)
			        {
			   #计算延时总和
			                AVG+=$11;
			                if($11>MAX)
			                        MAX=$11;
			                if($11<MIN)
			                        MIN=$11;
			        }
		#如果记录数大于6,则跳出循环并执行END中的表达式
			        if(NR>=6)
			                exit;
		#否则,读取系一条记录,重新开始循环
			        next;
			}
	}
	END{
			AVG=AVG/4;
			print "IP address:",IP_ADDR;
			print "Avg:",AVG,"ms";
			print "Max:",MAX,"ms";
			print "Min:",MIN,"ms";
	}

为了方便抽取数据,先在BEGIN语句中定义了3中域分隔符:冒号、等号和空格。

由于ping命令返回的第1行是由ping自动生成的简要提示信息,因此awk将忽略第1行。

第2个到第5个记录返回了TTL值和延时等信息,后面则是一些统计信息,因此awk处理过程中只处理第2个到第5个记录。

###函数

awk命令中的函数分为两种:内置函数和用户自定义函数。内置函数多是一些用来处理字符串和数学运算的函数,用户自定义函数则是由用户自己定义的功能性函数。

####内置函数####

awk命令中函数的用法与c语言中函数的用法十分相似。常见的内置函数如下:

	sub(regexp, replacement [, target]):当模式regexp第1次出现时,用replacement替换。
	gsub(regexp, repalcement [, target]):将与模式regexp匹配的内容全部用replacement替换。
	index(string, find):返回与find匹配的字符串第1次在string中出现的位置。
	length([string]):返回字符串string的长度或数组长度(字符)。
	blength([string]):返回字符串string的长度或数组长度(字节)。
	match(string, regexp [, array]):返回与regexp字符串第1次匹配的位置,如果不存在则返回0.
	split(string, array [, fieldsep]):将string按照字段分隔符filedsep进行分割,分割后的字段存入数组array,返回字符串数组元素个数。
	printf(format, expr1 [, expr2, …]):格式化输出函数。
	substr(string, start, [, length]):从string中返回从start开始,长度为length的子字符串。
	tolower(string):将string中的大写字符转换成小写。
	toupper(string):将string中的小写字符转换成大写。
	print:输出函数。

    close(): 用同一个带字符串值的 Expression 参数来关闭由 print 或 printf 语句打开的或调用 getline
             函数打开的文件或管道。如果文件或管道成功关闭,则返回 0;其它情况下返回非零值。如果打算
             写一个文件,并稍后在同一个程序中读取文件,则
    Expression | getline [ Variable ]
    getline [ Variable ] < Expression
    getline [ Variable ]

	rand():返回一个0到1之间的随机数。
	system():将传递来的字符串按shell命令执行。

	atan2(x, y):反正切函数。
	cos(x):余弦函数。
	sin(x):正弦函数。
	log(x):x的自然对数值。
	exp(x):E的x次幂。
	int(x):将x转换为整数类型。
	sqrt(x):x的平方根。
	srand( [Expr] ) 将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。

####用法示例:(示例文件students2.2内容如下)

	[root@localhost test]# cat students2.2
	2821020225      Liulu           0       A       B       0
	2821020115      Liumin          B       C       0       0
	2721020321      Xuli            0       0       B       D
	2921020632      Xiayu           0       0       0       0
	2721010409      Liwei           C       0       0       0
	2921050313      Heli            A       0       A       B
	2721030227      Wangtao         B       B       C       0

使用sub函数可以查找第1次出现的模式并替换。例如用sub函数修改匹配模式的记录:

	[root@localhost test]# awk '$1 ~/28210202/{print sub(/Liulu/,"Lilu",$0)"\n"$0}' students2.2
	1
	2821020225      Lilu            0       A       B       0

在上面的命令执行后,在第1行返回了修改的次数(对应sub函数),第2行输出了修改后的记录(对应$0)。

利用函数gsub将所有匹配到的大写字母A替换成数字95,然后返回一共修改的次数:

	[root@localhost test]# awk 'BEGIN{N=0}{N+=gsub(/A/,"95",$0); print $0}END{print N}' students2.2
	2821020225      Liulu           0       95      B       0
	2821020115      Liumin          B       C       0       0
	2721020321      Xuli            0       0       B       D
	2921020632      Xiayu           0       0       0       0
	2721010409      Liwei           C       0       0       0
	2921050313      Heli            95      0       95      B
	2721030227      Wangtao         B       B       C       0
	3

由于函数gsub执行一个记录的替换就会返回一次,因此此处用N+=gsub()。否则只会返回最后一条记录替换的次数。

利用index函数可以返回第1次匹配字符串的位置:

	[root@localhost test]# awk '{print index($0, "L"); if(NR==2) exit}' students2.2
	12
	12

利用length函数查看字符串长度:

	[root@localhost test]# awk '{print length($0); if(NR==2) exit}' students2.2
	25
	26

使用match函数查看匹配模式首次出现的位置:

	[root@localhost test]# awk 'BEGIN{print match("Hello! Welcome to Beijing!", "B")}'
	19

利用split函数将字符串按指定的分隔符拆开,然后放入指定的数组中并返回数组的元素个数:

	[root@localhost test]# awk 'BEGIN{print split("FV7H8-42D17-08D0Q-8QAG9-YPUZA",ARR,"-");for(I in ARR)print ARR[I]}'
	5
	8QAG9
	YPUZA
	FV7H8
	42D17
	08D0Q

另一种方式:

	[root@localhost test]# awk 'BEGIN{AN=split("FV7H8-42D17-08D0Q-8QAG9-YPUZA",ARR,"-");print AN;for(I=1;I<=AN;I++)print ARR[I]}'
	5
	FV7H8
	42D17
	08D0Q
	8QAG9
	YPUZA

注意:数组下标从1开始。

提示:在awk中使用数组时,可以不用先定义,也不必指出元素的个数。

利用tolower函数将字符串中的大写字符转换成小写:

	[root@localhost test]# awk '$1=="2821020225"{print tolower($0)}' students2.2
	2821020225      liulu           0       a       b       0

利用toupper函数将字符串中的小写字符转换成大写:

	[root@localhost test]# awk '$1=="2821020225"{print toupper($0)}' students2.2
	2821020225      LIULU           0       A       B       0

利用rand函数返回一个0到1之间的随机数:

	[root@localhost test]# awk 'BEGIN{print rand()}'
	0.237788

sin、cos函数可以计算正弦、余弦:

	[root@localhost test]# awk 'BEGIN{PI=3.1415926;print sin(PI/4),cos(PI/4)}'
	0.707107 0.707107

使用log函数可以返回自然对数:

	[root@localhost test]# awk 'BEGIN{print log(100)}'
	4.60517

exp函数可以返回e的x次幂:

	[root@localhost test]# awk 'BEGIN{print exp(256)}'
	1.51143e+111

sqrt函数可以计算平方根:

	[root@localhost test]# awk 'BEGIN{print sqrt(256)}'
	16

格式化输出函数printf(与c函数里的printf一样),可以使用修饰符对输出的内容进行格式控制,其基本语法如下:

	print format, [argument]…

	printf常用的修饰符:

	%c:作为一个ASCII字符输出。

	%s:作为一个字符串输出。

	%o:作为一个八进制数输出。

	%x:作为一个十六进制输出。

	%d:作为一个整数输出。

	%e:作为一个科学型浮点数输出。

	%f:作为一个浮点型数字输出。

	%g:由awk决定浮点数输出的形式。

除了以上一些修饰符外,awk也允许像c语言那样使用“-”、域宽和“.”进一步细化输出格式。

使用printf函数将数字100分别转换成字符、八进制、十六进制数:

	[root@localhost test]# awk 'BEGIN{A=100;printf "%c\n%o\n%x\n",A,A,A}'
	d
	144
	64

使用printf的修饰符可以将生成的数据转换成相应的格式:

	[root@localhost test]# awk 'BEGIN{printf "%e\n%f\n%g\n",exp(30),exp(30),exp(30)}'
	1.068647e+13
	10686474581524.462891
	1.06865e+13

有时在计算一个数字时要保留一定的精度,这时可以使用“.”指定精度,例如:

	[root@localhost test]# awk 'BEGIN{printf "%4.5f\n",sqrt(47)}'
	6.85565

从/etc/passwd中抽取用户名和用户使用的shell,使用%s对齐输出:

	[root@localhost test]# awk 'BEGIN{FS=":";printf "%-15s  %-15s\n","username","shell";printf "------------------------------\n"}{printf "%-15s       %-15s\n",$1,$7}' /etc/passwd
	username        shell          
	------------------------------
	root            /bin/bash      
	bin             /sbin/nologin  
	daemon          /sbin/nologin  
	adm             /sbin/nologin  
	lp              /sbin/nologin  
	...

为了使输出的段对齐,这里使用修饰符,例如:

	[root@localhost test]# awk '{printf "%-10s\t%-10s\t%d\t%d\t%d\t%d\t%d\n",$1,$2,$6,$7,$8,$6+$7+$8,($6+$7+$8)/3}' students
	2821020225      Liulu           89      76      88      253     84
	2821020115      Liumin          78      65      59      202     67
	2721020321      Xuli            76      81      85      242     80
	2921020632      Xiayu           78      86      92      256     85
	2721010409      Liwei           98      88      85      271     90
	2921050313      Heli            56      78      80      214     71
	2721030227      Wangtao         87      76      69      232     77

####用户自定义函数

在awk命令中,还允许使用用户自定义函数。与C语言中的函数作用范围不同,自定义函数无论出现在命令中的什么位置,作用都是全局的,而函数中定义的变量只作用于函数内部。如果需要从函数中返回一个值,可以使用return语句。

在awk命令中自定义函数的基本格式:

	function name([参数列表])
	{
		语句块;
		[return ...];
	}

在调用函数时,只需要使用函数名并加上要使用的参数列表即可,无须使用特别的语句。

下面是一个计算文件students中学生公共课总成绩和平均成绩的例子:

	[root@localhost test]# cat ex.awk 
	function Add(A,B,C)
	{
			return A+B+C;
	}
	function Avg(A,B,C)
	{
			return (A+B+C)/3;
	}
	{
			printf "%-10s\t%-10s\t%d\t%d\t%d\t%d\t%d\n",$1,$2,$6,$7,$8,Add($6,$7,$8),Avg($6,$7,$8);
	}
	END{
			printf "%s\n",FILENAME;
	}

	[root@localhost test]# awk -f ex.awk students
	2821020225      Liulu           89      76      88      253     84
	2821020115      Liumin          78      65      59      202     67
	2721020321      Xuli            76      81      85      242     80
	2921020632      Xiayu           78      86      92      256     85
	2721010409      Liwei           98      88      85      271     90
	2921050313      Heli            56      78      80      214     71
	2721030227      Wangtao         87      76      69      232     77
	students

对students中的学生成绩进行筛选,只输出学号,学生姓名和课程中的最高成绩并保存为strudents_max.

为实现上述功能,此处可以使用条件三目运算符和数组两种方法。为此因此两个脚本文件stu.awk1和stu.awk2:

	[root@localhost test]# cat stu.awk1
	function Max(Arr)
	{
			Ma=Arr[1];
			for(I in Arr)
			{
			        if(Ma<Arr[I])
			                Ma=Arr[I];
			}
			return Ma;
	}
	{
			for(J=1;J<5;J++)
			        Ar[J]=$(J+2);
			printf "%-10s\t%-10s\t%d\n",$1,$2,Max(Ar);
	}

	[root@localhost test]# cat stu.awk2
	function Max(A,B,C,D)
	{
			A>B?A:A=B;
			C>D?C:C=D;
			Ma=(A>C)?A:C;
			return Ma;
	}
	{
			printf "%-10s\t%-10s\t%d\n",$1,$2,Max($3,$4,$5,$6);
	}

使用awk的选项f调用stu.awk脚本,并将结果显示并保存到students_max中:

	[root@localhost test]# awk -f stu.awk1 students | tee students_max
	2821020225      Liulu           89
	2821020115      Liumin          78
	2721020321      Xuli            85
	2921020632      Xiayu           92
	2721010409      Liwei           98
	2921050313      Heli            80
	2721030227      Wangtao         88
	[root@localhost test]# awk -f stu.awk2 students | tee students_max2
	2821020225      Liulu           89
	2821020115      Liumin          78
	2721020321      Xuli            85
	2921020632      Xiayu           92
	2721010409      Liwei           98
	2921050313      Heli            80
	2721030227      Wangtao         88

注意:在C语言中A>B?A:(A=B)可以简写成A>B?:(A=B),但在awk中这是不允许的。

最后,引用一个使用awk监视磁盘并向用户发出警告的例子。在这个例子中,当某一个文件系统空闲不足10%时,通过system函数运行mail命令向管理员发出警告信息。

使用的脚本和执行过程如下:

	[root@localhost test]# cat df.awk
	function mess(A,B,C)
	{
			if(B>=0.9)
			{
			        system("date +'%F %r'>/root/df.tmp; df -h>>/root/df.tmp; cat /root/df.tmp | mail -s 'Disk Warning' 1025399680@qq.com,root;rm -rf /root/df.tmp");
			        printf "Disk Warning!\nFile System:%s\nUsed:%3.0f\nMounted on:",A,B*100,C;
			}
	}
	{
			if($1 ~/^\/dev\/sd/)
			{
			        N=$3/$2;
			        if(N>=0.9)
			                mess($1,N,$6);
			}
	}

	[root@localhost test]# df | awk -f df.awk

注意:在调用函数system时,一定要将shell命令放入双引号中,shell命令原本应放入双引号中的参数此时应该放入单引号中。

###参考

http://www.cnblogs.com/chengmo/archive/2013/01/17/2865479.html

###附录

awk的 array 是关联数组,由于awk允许string和numeric相互操作,所以需要小心当使用 numeric 和 string 作为 array 下标时的区别。 awk的 array 的下标都是 string 类型,对于 Non-string 类型的下标,将首先将 Non-string 转换成 string,在进行索引。 awk有一个内建变量 CONVFMT 用于完成 numeric 到 string 的转换,我们可以改变 CONVFMT 来改变转换的结果,当然对于 integer,CONVFMT不影响转换结果。默认的CONVMT为”%.6g”。

awk ‘BEGIN{ print CONVFMT }’

输出将是”%.6g”

对于使用integer和其对应的string作为索引,我们将得到同一个value

	awk 'BEGIN {
		#integer index
		a[0] = 1;
	 	#string index
	 	a["0"] = 2;
	 	print a[0]; # 2
	  	print a["0"]; # 2  }'

这里0将被转换成”0”,所以最后我们只得到一个2,1已经被覆盖了。GUN awk manual中有个例子,说明了array下标的问题。

	xyz = 12.153
	data[xyz] = 1
	CONVFMT = "%2.2f"

	if (xyz in data)
		printf "%s is in data/n", xyz
	else
		printf "%s is not in data/n", xyz

这里将输出12.153 is not in data,原因就是xyz在作为数组下标前,根据CONVFMT进行了转换,而我们在对data[xyz]赋值时, 使用的是”%.6g”,于是xyz转换为”12.153”,而在 if 判断中,我们已经改变了CONVFMT,此时xyz为”12.15”,所以被认为不在data中。

shell VS awk 效率

	[dev@vagrant-centos65 ~]$  time (awk 'BEGIN{ total=0;for(i=0;i<=10000;i++){total+=i;}print total;}')
	50005000

	real	0m0.007s
	user	0m0.004s
	sys	0m0.002s
	[dev@vagrant-centos65 ~]$ time(total=0;for i in $(seq 10000);do total=$(($total+i));done;echo $total;)
	50005000

	real	0m0.149s
	user	0m0.111s
	sys	0m0.010s

常用例子

如何将 a.txt b.txt 合并为 c.txt

	[root@localhost test]# awk 'FNR==1{print "\r\n"FILENAME}{print $0}' a.txt b.txt

	a.txt
	100     wang    man
	200 wangsan woman
	300 wangming man
	400 wangzheng man

	b.txt
	100 90 80
	200 80 70
	300 60 50
	400 70 20

	c. cat c.txt

	[root@localhost test]#  cat a.txt b.txt | sort -n -k1 |awk  'NR%2==1{fd1=$2"\t"$3;next}{print $0"\t"fd1}'
	100     wang    man     90      80
	200 wangsan woman       80      70
	300 wangming man        60      50
	400 wangzheng man       70      20

方法一

	[root@localhost test]# cat a.txt b.txt | sort -n -k1 |awk '{print}'
	100 90 80
	100     wang    man
	200 80 70
	200 wangsan woman
	300 60 50
	300 wangming man
	400 70 20
	400 wangzheng man

	[root@localhost test]# cat a.txt b.txt | sort -n -k1 |awk  'NR%2==1{fd1=$2"\t"$3;next}{print $0"\t"fd1}'
	100     wang    man     90      80
	200 wangsan woman       80      70
	300 wangming man        60      50
	400 wangzheng man       70      20

方法二

	[root@localhost test]# awk 'BEGIN{print ARGC,ARGV[0],ARGV[1],ARGV[2]}{print FILENAME,NR,FNR,$0}' a.txt b.txt
	3 awk a.txt b.txt
	a.txt 1 1 100   wang    man
	a.txt 2 2 200 wangsan woman
	a.txt 3 3 300 wangming man
	a.txt 4 4 400 wangzheng man
	b.txt 5 1 100 90 80
	b.txt 6 2 200 80 70
	b.txt 7 3 300 60 50
	b.txt 8 4 400 70 20

	[chengmo@centos5 shell]$ awk '
	BEGIN{
	if(ARGC<3)
	{
	  exit 1;
	}

	file="";
	}
	{
	aData[FILENAME,$1]=ARGV[1]==FILENAME?$0:$2"\t"$3;
	}
	END{
	for(k in aData)
	{
		split(k,idx,SUBSEP);
		if(idx[1]==ARGV[1] && (ARGV[2],idx[2]) in aData)
		{
			print aData[ARGV[1],idx[2]],aData[ARGV[2],idx[2]] | "sort -n -k1";
		}
	}
	}' a.txt b.txt

	100     wang    man 90  80
	200 wangsan woman 80    70
	300 wangming man 60     50
	400 wangzheng man 70    20

方法三

    awk '{printf("%s ", $0); getline<"b.txt" ;print $2,$3}' a.txt

方法四

    awk 'BEGIN{OFS="\t"}NR==FNR{a[$1]=1;b[$1]=$2;c[$1]=$3;next}{if(a[$1]==1) print $1,b[$1],c[$1],$2,$3}' a.txt b.txt

    gawk 'NR==FNR{a[$1]=$2;b[$1]=$3;};NR>FNR&&a[$1]{print $0, a[$1],b[$1]}' b.txt a.txt

markov算法

	# markov.awk: markov chain algorithm for 2-word prefixes
	BEGIN { MAXGEN = 10000; NONWORD = "\n"; w1 = w2 = NONWORD }

	{
        for (i = 1; i <= NF; i++) {     # read all words
			statetab[w1,w2,++nsuffix[w1,w2]] = $i
			w1 = w2
			w2 = $i
		}
	}

	END {
		statetab[w1,w2,++nsuffix[w1,w2]] = NONWORD  # add tail
		w1 = w2 = NONWORD
		for (i = 0; i < MAXGEN; i++) {  # generate
			r = int(rand()*nsuffix[w1,w2]) + 1  # nsuffix >= 1
			p = statetab[w1,w2,r]
			if (p == NONWORD)
			    exit
			print p
			w1 = w2         # advance chain
			w2 = p
		}
	}