awk概述
为什么使用awk: awk 是一种程序语言。 它具有一般程序语言常见的功能。 因awk语言具有某些特点, 如:使用直译器(Interpreter)不需先行编译; 变量无类型之分(Typeless), 可使用文字当数组的下标 (Associative Array)。。。等特色。 因此,使用awk撰写程序比起使用其它语言更简洁便利且节省时间。awk还具有一些内建功能,使得awk擅于处理具数据行(Record), 字段(Field)型态的资料; 此外, awk 内建有管道(pipe) 的功能,可将处理中的数据传送给外部的Shell命令加以处理, 再将Shell命令处理后的数据传回awk程序, 这个特点也使得awk程序很容易使用系统资源。由于awk具有上述特色, 在问题处理的过程中,可轻易使用awk来撰写一些小工具;这些小工具并非用来解整个大问题,它们只扮演解个别问题过程的某些角色, 可藉由Shell所提供的管道(pipe)将数据按需要传送给不同的小工具进行处理,以解决整个大的问题。 这种解决方式, 使得这些小工具可因不同需求而被重复组合及重用(reuse); 也可藉此方式来先行测试大程序原型的可行性与正确性,将来若需要较高的执行速度时再用C语言来改写。这是awk最常被应用之处。 若能常常如此处理问题, 读者可以以更高的角度来思考抽象的问题,不会被拘泥于细节的部份。
如何取得awk: 一般的LINUX/UNIX操作系统, 都默认安装了awk。 只不过awk版本可能不太相同。
awk如何工作: 为方便解释awk程序的框架结构和有关术语(terminology), 先以一个员工薪工资档案(emp.dat ), 进行介绍,文件内容如下:
A101 chenying 100 210
A304 linxiyu 110 215
S283 degnming 130 209
S267 liuchao 125 220
B108 hejing 95 210
文件中各字段依次为:员工ID, 姓名, 薪资率,及实际工时。 ID中的第一码为部门名称代码,。 “A”,”B”,“S”分别表示”“accp部门”,“benet部门”,“市场部”。
这里主要说明awk程序的主要架构及工作原理, 并对一些重要的名词进行必要的解释。 并体会一下awk语言的主要精神和awk与其它语程序言的不同之处。 首先了解一下几个名词定义:
名词定义1 数据行: awk从数据文件上读取数据的基本单位,awk在进行数据处理的时候是以行为单位的,grep也是以行为单位进行数据处理的。。以emp.dat为例, awk读入的第一个数据行是第一行 “A125 Jenny 100 210″ ,第二个数据行是第二行 “A341 Dan 110 215″ ,其他的依次类推,
名词定义2: 字段(Field) : 为数据行上被分隔开的子字符串。 以数据行”A125 Jenny 100 210″为例, 第一栏 第二栏 第三栏 第四栏的内容分别是: “A125″ ,”Jenny”, 100, 210 ,默认情况下,awk以空格分隔各个字段。
在linux/UNIX 的命令行上输入一下格式的指令: ( “$”表Shell命令行上 的提示符号)
$awk ‘awk程序’ 数据文件名
上面这条语句中,awk会先编译该程序, 然后执行该程序来处理所指定的数据文件。
awk程序的主要结构: awk程序中主要语法是 Pattern { Actions},即模式{动作}, 所以常见的awk 程序的机构如下:
Pattern1 { Actions1 }
Pattern2 { Actions2 }
……
Pattern3 { Actions3 }
Pattern 是什么? awk 可接受许多不同型态的Pattern。 一般常使用 “关系表达式”(Relational expression) 来当成 Pattern。 例如: x > 34 是一个Pattern, 判断变x与34是否存在大于的关系。 x == y是一个Pattern, 判断变量x与变量y是否存在等于的关系。 上式中 x>34 ,x ==y 便是典型的Pattern。 awk 提供C 语言中常见的关系运算符(Relational Operators) 如 >, <, >=, <=, ==, != 。此外, awk 还提供~ (匹配match) 及 !~(非匹配not match) 二个关系运算符。 其用法与涵义如下:
如果A 为一字符串, B 为一正则表达式(Regular Expression) ,A ~ B 判断字符串A 中是否包含能匹配(match)B表达式的子字符串。 A !~ B 判断字符串A 中是否不包含能匹配(match)B表达式的子字符串。 例如:”banana” ~ /an/ 整个是一个Pattern。 因为”banana”中含有可以匹配/an/的子字符串,所以此关系式成立 (true),整个Pattern的值为true。
有少数awk文章, 把~, !~ 当成另一类的操作符(Operator),而不作为一种 Relational Operator。在这里将这两个运算符当成一种Relational Operator。
Actions 是什么?
Actions 是由许多awk指令构成。awk 的指令与C 语言中的指令十分类似。 例如:awk 的I/O指令: print, printf( ), getline……
awk的流程控制指令:
if(…){…} else{…}, while(…){…}…
awk 如何处理模式和动作的呢?(Pattern { Actions } ? ),awk 会先判断(Evaluate)Pattern的值, 如果Pattern的值为true (或不为0的数字,或不是空的字符串),则awk将执行该Pattern所对应的Actions。否则,如果Pattern之值不为true, 则awk 将不执行该Pattern所对应的Actions。
例如:如果awk程序中有下列两指令 50 > 23 {print “Hello! The word!!” } “banana” ~ /123/ { print “Good morning !” }
awk会先判断50 >23 是否成立,因为该式成立,所以awk将打印出”Hello! The word!!”。 而另一Pattern 为”banana” ~/123/, 因为”banana” 内未含有任何子字符串可匹配(match)/123/,所以Pattern 的值是false,awk将不会打印出 “Good morning !”
awk 如何处理{ Actions } 的语法是什么呢?
有时语法Pattern { Actions }中,Pattern 部分会省略,只剩下 {Actions}。这种情况表示”无条件执行这个Actions”。
awk 的字段变量
awk 所内建的字段变量及其涵意如下:
字段变量 含义
$0 一字符串, 其内容为目前awk 所读入的数据行。
$1 $0 上第一个字段的数据。
$2 $0 上第二个字段的数据
其余类推 。
读入数据行时,awk如何更新(update)这些内建的字段变量?
当awk 从数据文件中读取一个数据行时,awk 会使用内建变量$0 进行记录。当$0 被改动时(例如:读入新的数据行或改变$0的值…), awk 会立刻重新分析$0的字段情况,并将$0上各字段的数据用$1,$2…进行记录。
awk 的内建变量(Built-in Variables)
awk 提供了许多内建变量,使用者可以在程序中使用这些变量来取得相关信息。常见的内建变量有:
内建变量 含义
NF Number of Fields 为一整数,其值表示$0上所存在的字段数目。
NR Number of Records为一整数,其值表示awk 已读入的数据行数目。
FILENAME awk正在处理的数据文件文件名。
例如:awk 从资料文件emp.dat 中读入第一笔数据行“A101 chenying 100 210”之后,程序中:$0 的值就是“A101 chenying 100 210”,$1的值是 “A101″,$2 的值是 “chenying”,$3的值是100,$4的值是210,$NF的值是4,$NR 的值是1,$FILENAME 的值是”emp.dat”。
awk 的工作流程:执行awk 时,它会反复进行下列四步骤:
1、自动从指定的数据文件中读取一个数据行。
2、自动更新(Update)相关的内建变量之值。 如:NF,NR,$0…
3、依次执行程序中所有的Pattern { Actions } 指令。
4、当执行完程序中所有Pattern { Actions } 时,如果数据文件中还有未读取的数据,则反复执行步骤1到步骤4。awk会自动重复进行上述4个步骤,使用者不须于程序中编写这个循环(Loop)。
打印文件中指定的字段数据并加以计算
awk 处理数据时,它会自动从数据文件中一次读取一行记录,并将该数据切分成一个个的字段;程序中可使用$1,$2,…直接取得各个字段的内容。这个特色让使用者轻易地使用awk 编写reformatter 来改变数据格式。
例子:以文件emp.dat 为例,计算每人应发工资并打印报表。
分析:awk 会自行一次读入一行数据,所以程序中仅需告诉awk 如何处理所读入的数据行。执行如下命令:
$ awk ‘{ print $2,$3 * $4 }’ emp.dat
执行结果时,屏幕出现 :
chenying 21000
linxiyu 23650
…………….
linux/UNIX 命令行上,执行awk 的语法为:$awk ’awk程序’ 欲处理的资料文件文件名;本例中程序部分是{print $2, $3 * $4}。 把程序置于命令行时,程序之前后须以’ 括住。emp.dat 为指定给该程序处理的数据文件文件名。本程序中使用:Pattern { Actions } 语法,但是Pattern 部分被省略,表无任何限制条件,所以awk读入每笔数据行后都将无条件执行这个Actions。 print为awk所提供的输出指令,会将数据输出到标准输出stdout(屏幕)。 print 的参数间彼此以”,” (逗号) 隔开,打印出数据时彼此间会以空白隔开。
如果将上述的程序部分储存于文件pay1.awk 中,执行命令时再指定awk 程序文件的文件名。 这是执行awk 的另一种方式,适用于程序较大的情况, 其语法如下:
$ awk -f awk程序文件名 数据文件文件名
$ awk -f pay1.awk emp.dat
awk 中也提供与 C 语言中类似用法的printf()函数,使用该函数可进一步控制数据的输出格式。编辑另一个awk程序如下,并取名为pay2.awk
{ printf(“%10s Work hours: %3d Pay: %5d/n”, $2,$3, $3* $4) }
执行下列命令
$awk -f pay2.awk emp.dat
执行结果屏幕出现:
chenying Workhours: 100 and pay: 21000
linxiyu Workhours: 110 and pay: 23650
degnming Workhours: 130 and pay: 27170
liuchao Workhours: 125 and pay: 27500
hejing Workhours: 95 and pay: 19950
在awk使用方法中,Pattern{ Action }是awk使用的最主要语法。如果Pattern的值为真则执行它后方的Action。awk中常使用”关系表达式” (Relational Expression)当做Pattern。
awk 中除了>,<,==,!= 等关系运算符(Relational Operators)外,另外提供 ~(匹配match),!~(不匹配Not Match)二个关系运算符。利用这两个运算符,可判断某字符串是否包含能匹配所指定正则表达式的子字符串。 有这么多关系运算符,使用awk可以轻易的 进行字符串的比较,并编写字符串处理的程序。
例如,单位的职工如下所示:
[root@benet pub]# cat emp.dat
A101 chenying 100 210
A304 linxiyu 110 215
S283 degnming 130 209
S267 liuchao 125 220
B108 hejing 95 210
其中A开头的表示accp部门工作人员,工资加薪5%,加薪后员工薪资仍低于110的,以110计。编写awk程序打印新的员工薪资率报表。
[分析] :这个程序须先判断所读入的数据行是否合于指定条件,再进行某些动作。awk中Pattern { Actions } 的语法已涵盖这种 “if (条件) {动作} “的架构。编写awk脚本程序adjust1.awk,脚本内容如下:
[root@benet pub]# cat adjust1.awk
#!/bin/awk -f
{
if($0~/A/){$3*=1.05}
if($3<110){$3=110}
printf(“%s %-8s %d/n”,$1,$2,$3)
}
接下来,执行程序脚本:
[root@benet pub]# awk -f adjust1.awk emp.dat
结果如下:
[root@benet pub]# awk -f adjust1.awk emp.dat
A101 chenying 110
A304 linxiyu 115
S283 degnming 130
S267 liuchao 125
B108 hejing 110
通过以上输出,可以发现,数值改变的只有在工号中有A的行。下面来看一下awk执行的步骤:首先,awk从数据文件中每次读入一个数据行, 依序执行程序中所有的 Pattern{ Action }指令:
if($0~/A/){$3*=1.05}
if($3<110){$3=110}
printf(“%s %-8s %d/n”,$1,$2,$3)
再从数据文件中读取下一行记录继续进行处理。
第一个Pattern { Action }是: if($0~/A/){$3*=1.05},if($0~/A/)是一个Pattern,用来判断该笔数据行的第一行是否包含”A”。 其中~/A/是一个正则表达式Regular Expression,用来表示在整行数据中含有A的字符串。Actions部分为{$3*=1.05},$3*=1.05与$3=$3*1.05意义相 同。 运算符”*=” 的用法与C语言中的用法一样。此后与C语言中用法相同的运算子或语法将不予赘述。
第二个 Pattern { Actions } 是:if($3<110){$3=110},如果第三栏的数据内容(表薪资率)小于110, 则调整为110。
第三个 Pattern { Actions } 是:printf(“%s %-8s %d/n”,$1,$2,$3),省略了Pattern(无条件执行Actions), 故所有数据行调整后的数据都将被印出。
awk程序中允许使用字符串当做数组的下标(index),这个特点有助于资料的统计。(使用字符串当下标的数组称为Associative Array)
首先建立一个名为kecheng.dat数据文件,内容是学生选课的内容;第一栏为学生姓名,其后为该生所学课程,内容如下:
[root@benet pub]# cat kecheng.dat
zhangsan math english chinese
lisi computer chinese english
wangwu dianzi chinese math
zhaoliu huanjing english chinese
awk中数组不需要声明,也不用指定数组的大小,直接使用字符串当数组的下标(index)。 以上边学生选课为数据文件统计一下kecheng.dat 中学习各门课程的人数。
这种情况,有二项信息必须储存: (a) 课程名称, 如:math,English,共有哪些课程事先并不明确。 (b)各课程的选修人数。 如: 有几个人选修了“math”。
在awk 中只用一个数组就可同时记录上述信息。 方法如下:使用数组Number[ ]:以课程名称当Number[ ]的下标。 以Number[ ] 中不同下标所对映的元素代表各门课程的人数。 例如:有2个学生学习“math”,则以Number["math"]=2表示。如果学习math的人数增加一人,则 Number[“math”]=Number[“math”]+1 或Number[“math”]++ 将学习该门课程的人数加1。
那么如何读出数组中储存的信息呢?以math为例,声明int Arr[100]数组后,如果想显示数组中的数值,使用一个for循环就可以了,如:for(i=0; i<100; i++) printf(“%d/n”, Arr[i]); 即可。上式中:数组Arr[ ] 的下标 :0,1,2,…,99, 数组Arr[ ] 中各下标所对应的值:Arr[0],Arr[1],…,Arr[99]。
上面说了awk 中使用数组不须声明,以数组number[]为例,程序执行前,并不知有哪些课程名称可能被当成Number[ ]的下标。在awk中提供了一个指令,通过该指令awk会自动找寻数组中所有使用过的下标。以Number[ ] 为例,awk将会找到“math”,“english”,使用该指令时,只要首先指定要找寻的数组和变量即可,awk会记录从数组中找到的每一个下标。
例如:for(course in Number){…}
指定使用course 来记录awk 从Number[ ]中找到的下标,awk每找到一个下标,就用course记录该下标的值且执行{…}中的指令。
例子:统计kecheng.dat中每门课程有多少学生学习,并输出结果。
处理方法:建立使用awk编写的脚本程序,脚本的内容如下:
[root@benet pub]# cat course.awk
#!/bin/awk -f
BEGIN{
FS=” “}
{
for(i=2;i<=NF;i++){number[$i]++;}
}
END{
for(course in number)printf(“%10s %d/n”,course,number[course]);
}
在程序中需要注意,awk程序主要有三部分组成,BEGIN,中央处理部分和END三个部分,BEGIN和END是awk的保留字,后面必须是“{”,如果没有紧跟着“{”,执行程序的时候,会报错;FS是field separate的缩写,用来存储域分隔符。
执行结果如下:
[root@benet pub]# awk -f course.awk kecheng.dat
computer 1
english 3
dianzi 1
chinese 4
math 2
huanjing 1
程序解释:这程序包含三个Pattern { Actions }指令。
第一个Pattern { Actions }指令中的FS上面已经说过了,后面跟着分隔数据文件的分隔符,在这里,是以空格来进行分隔数据行的没一个部分的,所以FS=” “;如果是分隔/etc/passwd这个文件,那么FS=”:”,在以后分隔数据文件的时候,一定要选择正确的域分隔符,并用FS进行设置。
第二个Pattern { Actions }指令中省略了Pattern 部分,所以每行数据读入后,都会在Actions部分将逐次无条件执行。awk在读入第一行数据zhangsan math english chinese的时候,因为该行数据有NF=4个字段,所以Action 的for循环中i从2开始,因为第一个字段是学生的名字,不用进行统计;而其后的各个域需要统计,所以for循环中i取值为2,3,4。 Number[$i]++ 这句中,在i=2时,$2是第二个域的值,即$2=math,Number[math]的值从默认的0,++变成了1 ; i=3时$i=english,Number[english]的值默认是0,++变成了1 ; 同理,i=4时 $i=chinese,Number[chinese]的值从默认的0,++后变成了1 ; 第一行数据处理完后,再次读入下一行,根据$i的内容,如果数组中没有,就会新添加一个课程,并将选择给课程的学生数加1,如果该课程在数组中已经存在, 只将改课程的人数加1。
第三个Pattern{Actions}指令中END 为awk的保留字,而且必须是大写,是Pattern的一种。END 成立(其值为true)的条件是: “awk处理完所有数据,即将离开程序时”,平常读入数据行时,END并不成立,所以END后面的Actions 并不被执行;只有当awk读完所有数据时,Actions才会被执行。
BEGIN与END 有点类似,是awk 中另一个保留的Pattern。 唯一不同的是:“以BEGIN 为Pattern的Actions 在程序一开始的时候被执行一次”, NF 为awk 的内建变量,用来表示awk正在处理的数据行中所包含的字段的个数。
awk程序中以$开头的变量, 都是下面这种意思:以i= 2 为例,$i=$2 表示第二个字段数据(实际上,$在awk 中 为一运算符(Operator),用以取得字段数据)。
awk程序中允许使用Shell指令,使用管道在awk和系统中进行数据传递,所以awk可以很容易的使用系统资源。
比如写一个awk程序来打印出当前系统上有多少用户登录。awk的脚本文件名为usernumber.awk,脚本内容如下:
[root@benet pub]# cat usernumber.awk
#!/bin/awk -f
BEGIN{
while(“who”|getline) n++;
print n;
}
执行结果如下:
[root@benet pub]# awk -f usernumber.awk
2 #即有两个用户登录了系统
解释:awk 程序并不一定要处理数据文件,上例中没有输入任何数据文件。BEGIN 和END是awk 中的一种Pattern。以BEGIN 为Pattern的Actions,只有在awk开始执行程序,尚未输入任何文件前,执行一次且仅被执行一次) ,“|”和在shell中一样,在awk 中也表示管道。awk把|之前的字符串“who”当成Shell的命令,并将该命令送往Shell执行,执行的结果通过管道输出到awk程序中。
getline为awk所提供的输入指令。getline 一次读取一行数据,若读取成功则return 1,若读取失败则return -1,若遇到文件结束(EOF),则return 0;
getline的用法和例子:
当getline左右没有重定向符|或<时,getline读去当前文件的第一行并将数据保存到变量中,如果没有变量,则数据保存到$0中;由于awk在处理getline之前已经读入了一行,所以getline得到的返回结果是隔行的。
当getline左右有重定向符|或<时,getline作用于定向输入文件,由于该文件是刚打开,awk并没有读入一行数据,而getline读入了一行数据,那么getline返回的是该文件的第一行,而不是隔行。
root@myfreelinux pub]# awk ‘BEGIN{“cat kecheng.dat”|getline var;print var;}’
[root@myfreelinux pub]# awk ‘BEGIN{“cat kecheng.dat”|getline;print $0;}’
[root@myfreelinux pub]# awk ‘BEGIN{getline var<”kecheng.dat”;print var;}’
[root@myfreelinux pub]# awk ‘BEGIN{getline <”kecheng.dat”;print $0;}’
以上四行awk程序都是将kecheng.dat的第一行数据打印出来,结果是:zhangsan math english chinese
[root@myfreelinux pub]# awk ‘{getline var;print $0;print var;}’ kecheng.dat
zhangsan math english chinese
lisi computer chinese english
wangwu dianzi chinese math
zhaoliu huanjing english chinese
[root@myfreelinux pub]# awk ‘{getline var;print var;}’ kecheng.dat
lisi computer chinese english
zhaoliu huanjing english chinese
以上两个例子可以看出来,awk和getline是分别取数据文件中的行数据,而且是awk首先从数据文件中取数据,后getline取下一行数据。
getlin在不同环境下影响到awk中的值的对应关系如下图:
————————————————-
形式 设置
————————————————-
getline $0,NF,NR,FNR
getline var var,NR,FNR
getline<file $0,$1…$NF,NF
getline var<file var
cmd|getline $0,NF
cmd|getline var var
在这里举个例子,统计上班到达时间及迟到次数的程序。这程序每日被执行时将读入二个文件:员工当日上班时间的数据文件 ( arrive.dat ) 存放员工当月迟到累计次数的文件当程序执行执完毕后将更新第二个文件的数据(迟到次数), 并打印当日的报表。
此程序的步骤分析如下:
[6.1] 在上班数据文件arrive.dat之前增加一行标题 “ID Number Arrvial Time”,并产生报表输出到文件today_result1中。
[6.2]将today_result1上的数据按员工代号排序, 并加注执行当日日期; 产生文件today_result2
[6.3] 将awk程序包含在一个shell script文件中
[6.4] 在today_result2 每日报表上,迟到者之前加上”*”,并加注当日平均上班时间;产生文件today_result3
[6.5] 从文件中读取当月迟到次数, 并根据当日出勤状况更新迟到累计数。
上班时间数据文件arrive.dat的格式是:第一列是员工代号,第二列是到达时间,内容如下:
[root@myfreelinux pub]# cat arrive.dat
A034 7:26
A025 7:27
A101 7:32
A006 7:45
A012 7:46
A028 7:49
A051 7:51
A029 7:57
A042 7:59
A008 8:01
A052 8:05
A005 8:12
说 明:awk程序中,文件名称today_result1的前后需要用” (双引号)括起来,表示today_result1是一个字符串常量。如果没有用双括号括起来,today_result1将被awk解释为一个变量名 称。在awk中,变量不需要事先声明,变量的初始值为空字符串(Null string) 或0。因此awk程序中如果没有将today_result1用双括号括起来,那么awk将today_result1作为一个变量来使用,它的值是空字 符串,那么在执行时造成错误(Unix无法开启一个以空字符串为文件名的文件)。因此在编写awk程序时,一定要将文件名用双括号括起来。BEGIN是 awk 的保留字,是Pattern的一种。以BEGIN为Pattern的Actions在awk程序刚被执行尚未读取数据文件时被执行一次,此后便不再被执 行。本程序中若使用”>” 将数据重定向到today_result1,awk第一次执行该指令时会产生一个新文件today_result1,再执行该指令时则把数据追加到 today_result1的文件结尾,而不是每执行一次就打开一次该文件。而”>>”和“>”的差异仅在执行该指令时,如果已存在 today_result1则awk将直接把数据append在原文件的末尾。
awk 中如何利用系统资源
awk程序中可以很容易的使用系统资源。比如1、在程序中途调用Shell命令来处理程序中的部分数据;2、在调用Shell命令后将其产生的结果交回 awk 程序(不需将结果暂存于某个文件)。这一过程是使用awk 所提供的管道(类似于Unix中的管道,但又有些不同),和一个从awk 中调用Unix的Shell命令的语法来实现的。
例:承上题,将数据按员工ID排序后再输出到文件today_result2, 并在表头上添加执行时的日期。
分 析:
awk 提供了和UNIX用法类似的管道命令”|”。在awk中管道的用法和含意如下:awk程序中可接受下列两种语法:
[a语法] awk output 指令| “Shell 接受的命令” ( 如:print $1,$2 | “sort -k 1″ )
[b语法] “Shell 接受的命令” | awk input 指令 ( 如:”ls ” | getline) 。
awk的input指令只有getline 这一个输入指令。awk的output 指令有print,printf() 二个。
在a语法中,awk所输出的数据将转送往Shell ,由Shell的命令进行处理。以上例而言,print所输出的数据将经由Shell 命令”sort – k 1″排序后再输出到屏幕(stdout)。上例awk程序中,”print $1,$2″ 可能反复执行很多次,输出的结果将先暂存在pipe中,等程序结束后,才会一并进行排序”sort -k 1″。须注意二点:不论print $1,$2被执行几次,”sort -k 1″的执行时间是”awk程序结束时”,”sort -k 1″ 的执行次数是”一次”。
在b语法中,awk将先调用Shell命令,执行结果将通过pipe传送到awk程序,以上例而言,awk先让Shell执行”ls”,Shell执行后 将结果保存在pipe中,awk的输入指令getline再从pipe中读取数据。使用本语法时应注意:在上例中,awk”立刻”调用Shell 来执行 ”ls”,执行次数是一次。getline则可能执行多次(如果pipe 中存在多行数据)。
除以上a,b两种语法外,awk程序中其它地方如出现像”date”,”ls”…这样的字符串,awk只把它当成一般字符串处理。
根据以上分析,建立awk程序脚本内容如下:
[root@myfreelinux pub]# cat reformat2.awk
#!/bin/awk -f
BEGIN{
“date”|getline;#在shell中执行date指令,并将结果存储到$0中。
print “Today is”,$2,$3>”today_result2″;
print “ID Number Arrival Time”>”today_result2″;
print “=========================”>”today_result2″;
close(“today_result2″);
}
{printf(“%s/t/t%s/n”,$1,$2)|”sort -k 1 >> today_result2″;}
执行该程序脚本:
[root@myfreelinux pub]# awk -f reformat2.awk arrive.dat
执行后,程序将sort后的数据追加(Append)到文件today_result2最末端。today_result2内容如下:
[root@myfreelinux pub]# cat today_result2
Today is 06月 17日
ID Number Arrival Time
=========================
A005 8:12
A006 7:45
A008 8:01
A012 7:46
A025 7:27
A028 7:49
A029 7:57
A034 7:26
A042 7:59
A051 7:51
A052 8:05
A101 7:32
解释说明:awk程序由三个主要部分构成:1、Pattern { Action} 指令,2、函数主体。例如:function double( x ){ return 2*x },3、注释Comment ( 以# 开头识别之 )。
awk 的输入指令getline,每次读取一列数据。如果getline之后没接任何变量,读入的数据将存储到$0中,否则以所指定的变量储存储。
比如在执行”date”|getline后,$0 的值是2010年 06月 17日 星期四 10:43:16 CST,当$0被更新时,awk将自动更新相关的内建变量,如:$1,$2,…,NF。所以$2的值是”06月”,$3的值是“17日”。
本程序中printf() 指令会被执行12次( 因为有arrive.dat中有12行数据),在awk结束该程序时会close这个pipe ,此时才将12行数据一次送往系统,并由”sort -k 1 >> today_result2″进行处理,注意”>>”的左侧一定要有空格,否则在执行的时候会报错“sort:选项需要一个参数 -k”,awk还提供了另一种调用Shell命令的方法,即使用awk 函数system(“shell 命令”) ,例如:
[root@myfreelinux pub]# awk ‘BEGIN{system(“date>date.dat”);getline<”date.dat”;print “Today is “,$2,$3}’,但使用 system( “shell 命令” ) 时,awk无法直接将执行中的数据输出给Shell 命令。且Shell命令执行的结果也无法直接输入到awk 中。
执行awk 程序的几种方式
一下部分将介绍如何将awk程序直接写在shell script 之中,这样在执行的时候就不需要在命令行每次都输入” awk -f program datafile” 了。script中还可包含其它Shell 命令,这样可增加执行过程的自动化。建立一个简单的awk程序mydump.awk,如下: {print}这个程序执行时会把数据文件的内容print到屏幕上( 与cat功用类似 )。print 之后未接任何参数时,表示 “print $0″。如果要执行这个awk程序输出today_result1和today_result2 的内容时,在unix命令行上,有以下几种方式:
一、awk -f mydump.awk today_result1 today_result2
二、awk ‘{print}’ today_result1 today_result2
第二种方法将awk 程序直接写在Shell的命令行上,这种方法仅适合awk程序较短的时候。
三、使用shell脚本。建立一个shell脚本mydisplay,内容如下:
[root@myfreelinux pub]# cat mydisplay
#!/bin/bash # 注意awk 与’ 之间须有空白隔开
awk ‘{print}’ $*# 注意’ 与$* 之间须有空白隔开
在执行mydisplay 之前,需要给它添加可执行权限,首先执行如下命令: [root@myfreelinux pub]# chmod +x mydisplay,这样mydisplay才有可执行权限,[root@myfreelinux pub]# ./mydisplay today_result1 today_result2就可以执行了,当然,如果没有给他可执行权限的话,也可以使用一下方法来执行:[root@myfreelinux pub]# bash mydisplay today_result1 today_result2
说明:在script文件mydisplay 中,指令”awk”与第一个’ 之间须有空格(Shell中并无” awk’ “指令)。第一个’用来通知Shell其后为awk程序,第二个’ 则表示awk 程序结束。所以awk程序中一律以”括住字符串或字符,而不能使用’括住字符串或字符,以免引起Shell混淆。$* 是shell script 中的用法,可用来代表命令行上”mydisplay之后的所有参数”。例如执行:[root@myfreelinux pub]# ./mydisplay today_result1 today_result2事实上Shell 已先把该指令转换成:awk ‘ { print} ‘ today_result1 today_result2 。本例中,$*代表”today_result1 today_result2″。在Shell的语法中, $1代表第一个参数,$2 代表第二个参数。当不确定命令行上的参数个数时,可使用$*代表。awk命令行上可同时指定多个数据文件。以awk -f dump.awk today_result1 today_result2hf 为例,awk会先处理today_result1,再处理today_result2,如果文件不存在或无法打开,会提示相应的错误,比如awk: (FILENAME=today_result1 FNR=14) fatal: cannot open file `today_result’ for reading (没有那个文件或目录)。
有些awk程序”仅” 包含以BEGIN为Pattern的指令,这种awk 程序执行时不须要数据文件,如果在命令行上指定一个不存在的数据文件,awk不会产生”无法打开文件”的错误(事实上awk并未打开该文件) 。例如执行:[root@myfreelinux pub]#awk ‘BEGIN {print “Hello,World!!”} ‘ file_no_exist ,该程序中仅包含以BEGIN 为Pattern的指令,awk 执行时并不会开启任何数据文件; 所以不会因不存在文件file_no_exit 产生” 无法打开文件”的错误。
awk会将Shell 命令行上awk程序(或 -f 程序文件名)之后的所有字符串,视为将输入awk进行处理的数据文件文件名。如果执行awk 的命令行上”未指定任何数据文件文件名”,则将stdin作为数据源,直到输入end of file( Ctrl-D )为止。比如执行如下命令:
[root@myfreelinux pub]#awk -f mydump.awk #(未接任何数据文件文件名)
或
[root@myfreelinux pub]#./mydisplay #(未接任何数据文件文件名)
会发现:此后键入的任何数据将逐行复印一份显示到屏幕上,这种情况是因为执行时没有指定数据文件文件名,awk 便以stdin(键盘上的输入)做为数据来源。那么我们可利用这个特点,设计与awk即时聊天的程序:mrgreen: 。
改变awk 切割字段的方式& 自定义函数
awk不仅能自动分割字段,也允许使用者改变其字段切割方式以适应各种格式的需要。
例题:承接6.2的例子,如果八点为上班时间,那么请在迟到的记录前加上“*”,并计算平均上班时间。
分 析:八点整到达者,不算迟到,所以只根据到达的小时数来判断是不够得,还需要应参考到达时的分钟数。如果“将到达时间转换成以分钟为单位”,进行判断是否 迟到和计算到达平均时间比较容易。到达时间($2)的格式为dd:dd 或d:dd,数字当中含有一个“:”,awk无法对这些数据进行处理 (注:awk中字符串”26″与数字26,并无差异,可直接做字符串或数学运算,这是awk重要特色之一。 但awk对文本数字交杂的字符串无法正确进行数学运算),那么可以使用一下方法解决:
[方法一] 对到达时间($2) d:dd 或dd:dd 进行字符串运算,分别取出到达的小时数和分钟数。 首先判断到达小时数是一位还是两位字符,再调用函数分别截取分钟数和小时数,需要用到下列awk字符串函数:
length( 字符串) :返回该字符串的长度;
substr( 字符串,起始位置,长度) :返回从起始位置起,指定长度之子字符串;若未指定长度,则返回从起始位置到字符串末尾的子字符串。
所以:小时数=substr( $2,1,length($2) – 3 ) ,分钟数=substr( $2,length($2) – 2)
[root@myfreelinux pub]# awk ‘BEGIN{“date”|getline;print substr($5,1,2)*60+substr($5,4,2);}’
[方 法二]改变输入列字段的切割方式,使awk切割字段后分别将小时数及分钟数隔开于二个不同的字段。字段分隔字符FS (field seperator) 是awk 的内建变量,其默认值是空白及tab。awk每次切割字段时都会先参考FS的内容。若把”:”也当成分隔字符,则awk 便能自动把小时数及分钟数分隔成不同的字段。故令FS = “[ /t:]+” (注: [ /t:]+ 是一个正则表达式Regular Expression ) Regular Expression中使用中括号[] 表示一个字符集合,用以表示任意一个位于两中括号间的字符。所以可用”[ /t:]“表示一个空格, tab 或”:” ,Regular Expression中”+” 表示其前方的字符可出现一次或一次以上。所以”[ /t:]+” 表示由一个或多个”空格,tab 或 : ” 所组成的字符串。设定FS =”[ /t:]+” 后,数据行如:”1034 7:26″ 将被分割成3个字段,$1是1034,$2是7,$3是26,显然,awk程序中使用方法二要比方法一更简洁方便。所以本例中使用方法二,来介绍改变字段 切割方式的用法。
编写awk程序reformat3,如下:
[root@myfreelinux pub]# cat reformat3.awk
#!/bin/bash
awk ‘BEGIN{
FS=”[ /t:]+”;
“date”|getline;
print “Today is”,$2,$3 > “today_result3″;
print “==================” > “today_result3″;
print “ID Number Arrival Time” > “today_result3″;
close(“today_result3″);
}
{
arrival=HM_TO_M($2,$3);
printf(“%s/t/t%s:%s %s/n”,$1,$2,$3,arrival>”480″?”*”:” “)|”sort -k 1 >> today_result3″;
total+=arrival;
}
END{
close(“sort -k 1 >> today_result3″);
printf(“Average arrival time: %d:%d/n”,total/NR/60, total/NR%60) >> “today_result3″;
close(“today_result3″);
}
function HM_TO_M(hour,min){ return hour*60 + min }’ $*
并执行如下指令 :
[root@myfreelinux pub]# bash reformat3.awk arrive.dat
执行后,文件 today_result3 的内容如下:
[root@myfreelinux pub]# cat today_result3
Today is 06月 17日
==================
ID Number Arrival Time
A005 8:12 *
A006 7:45
A008 8:01 *
A012 7:46
A025 7:27
A028 7:49
A029 7:57
A034 7:26
A042 7:59
A051 7:51
A052 8:05 *
A101 7:32
Average arrival time: 7:49
说明:awk 中允许自定义函数,函数定义方式可参考本程序,function是awk的保留字。HM_TO_M( ) 这函数负责将传入的小时和分钟数转换成以分钟为单位的时间。在printf()中使用的arrival >480 ? “*” :” “是一个三元运算符,如果arrival 大于480则return “*” ,否则return ” “。
% 是awk的运算符(operator),作用与C 语言中的% 相同(取余数)。
NR(Number of Record) 为awk 的内建变量,表示awk执行该程序后所读入的记录笔数。
close 的语法有二种:close( filename ) 和close( 置于pipe之前的command ) 。本程序使用了两个close( ) 指令:指令close( “sort -k 1 >> today_result3″ ),意思是close程序置于”sort -k 1 >> today_result3 ” 之前的Pipe , 并立刻调用Shell 来执行”sort -k 1 >> today_result3″。 因为 Shell 排序后的数据也要写到 today_result3, 所以awk必须先关闭 使用中的today_result3 以使 Shell 正确将排序后的数据追加到 today_result3否则2个不同的 process 同时打开一个文件进行输出将会 产生不可预期的结果。 读者应留心上述两点,才可正确控制数据输出到文件中的顺序。指令close(“sort -k 1 >> today_result3″)中字符串 “sort +0n >> today_result3″ 必须与pipe |后方的Shell Comman名称一字不差,否则awk将视为二个不同的pipe。
?使用getline 来读取数据
范例: 承上题,从文件中读取当月迟到次数,并根据当日出勤状况更新迟到累计数。(按不同的月份累计于不同的文件)
分 析: 程序中自动抓取系统日期的月份名称,连接上”late.dat”, 形成累计迟到次数的文件名称(如:09月late.dat,。。。), 并以变量late_file记录该文件名。累计迟到次数的文件中的数据格式为:员工代号(ID) 迟到次数。例如,执行本程序前文件09月late.dat 的内容如下:
[root@myfreelinux pub]# cat late.dat
A005 2
A006 1
A008 2
A012 0
A025 0
A028 1
A029 2
A034 0
A042 0
A051 0
A052 3
A101 0
编写程序reformat4 如下:[root@myfreelinux pub]# cat reformat4.awk
#!/bin/bash
awk ‘BEGIN{
sys_sort=”sort -k 1 >> today_result4″;
result=”today_result4″;
FS=”[ /t:]+”;#改变字段切割的方式
“date”|getline;#令Shell执行”date”;getline读取结果,并以$0记录结果
print “Today is”,$2,$3 > result;
print “=======================”>result;
print “ID Number Arrival Time”> result;
close(result);
late_file=$2″late.dat”;
while(getline<late_file >0) #从文件按中读取迟到数据,并用数组cnt[]记录。
cnt[$1]=$2 #数组cnt[]中以员工代号为下标,所对应的值为该员工之迟到次数
close(late_file)
}
{arrival=HM_TO_M($2,$3);#已更改字段切割方式,$2表小时数,$3表分钟数
if(arrival>480)
{mark=”*”; #若当天迟到,应再增加其迟到次数,令mark为”*”
cnt[$1]++;}
else
mark=” “;
message=cnt[$1]?cnt[$1]“times”:” “;# message用以显示该员工的迟到累计数,若未曾迟到message为空字符串
printf(“%s/t/t%2s:%2s %5s %s/n”,$1,$2,$3,mark,message)|sys_sort;
total+=arrival;
}
END{
close(result);
close(sys_sort);
printf(“Arrivage arrival time: %d:%d/n”,total/NR/60,total/NR%60) >> result;
for(any in cnt) #将数组cnt[]中新的迟到数据写回文件中
print any,cnt[any] > late_file;
}
function HM_TO_M(hour,minute){return hour*60+minute;}
‘ $*
执行后结果如下:
[root@myfreelinux pub]# bash reformat4.awk arrive.dat
[root@myfreelinux pub]# cat today_result4
Today is 06月 18日
=======================
ID Number Arrival Time
A005 8:12 * 1times
A006 7:45
A008 8:01 * 1times
A012 7:46
A025 7:27
A028 7:49
A029 7:57
A034 7:26
A042 7:59
A051 7:51
A052 8:05 * 1times
A101 7:32
Arrivage arrival time: 7:49
06月late.dat的内容如下:
[root@myfreelinux pub]# cat 06月late.dat
A028
A029
A012
A005 1
A042
A051
A006
A101
A052 1
A025
A034
A008 1
说 明:由于文件06月late.dat中保存了一些数据内容,在这里我没有做更多的修改,所以每次执行[root@myfreelinux pub]# bash reformat4.awk arrive.dat的时候,迟到次数都会增加,您可以根据实际情况,再做一下修改,做的更完美。
late_file是一变量,记录迟到次数的文件的文件名。late_file值由两部分构成,前半部是当月月份名称(由调用”date”取得)后半部固 定为”late.dat” 如:06月late.dat。指令getline < late_file 表示从late_file所代表的文件中读取一笔记录,并存放于$0。awk会自动对新置入$0 的数据进行字段分割,之后程序中可用$1, $2,。。来表示数据的第一栏,第二 栏,。。,
注: 有少数awk版本不容许用户自行将数据置于$0,这种情况可改用gawk或nawk。执行getline指令时, 若成功读取记录,它会返回1;若遇到文件结束,它返回0;无法打开文件则返回-1。利用 while( getline < filename >0 ) {…}可读入文件中的每一笔数据并进行处理。这是awk 中用户自行读取数据文件的一个重要模式。数组 cnt[ ] 以员工ID,下标(index)对应值表示其迟到的次数。执行结束后,利用 for(Variable in array ){。。。}的语法 for( any in cnt ) print any, cnt[any] > late_file 将更新过的迟到数据重新写回记录迟到次数的文件
awk 每次从数据文件中只读取一行数据进行处理,这是因为awk中有一个内建变量RS(Record Separator) ,RS将文件中的数据分隔成以行为单位的记录record。RS默认值以”/n”(跳行符号)分隔数据文件中的信息,所以默认情况下awk 中一行数据就是一行Record。但有些文件中一行Record涵盖了多行数据,这种情况下不能再以”/n” 来分隔Records。最常使用的方法是相邻的Records之间改用一个空白行来分隔。在awk程序中,令RS= “”(空字符串)后,awk把会空白行当成来文件中Record的分隔符。显然awk对RS=”"另有深意,简单来说是这样的,当RS=”" 时:多个相邻的空白行,awk仅作为一个Record Saparator(awk不会在多个相邻的空白行之中选取一行做为空的Record) ;awk会略过(skip)文件头和文件尾的空白行,所以不会因为有这样的空白行,造成awk多读了二行空的数据。下面举个例子看一下,首先建立一个数据 文件myfreelinux.dat,内容如下:
[root@myfreelinux pub]# cat myfreelinux.dat
wanger
linux_basic
lisan
linux_server
windows_server
zhaosi
awk_tools
grub
regular_expression
该文件的开头有3行空白行, 各行Record之间分别用2个和1个空白行隔开。那么下面,通过几个例子来看一下。首先编辑一个awk程序脚本report1.awk,内容如下:[root@myfreelinux pub]# cat report1.awk
#!/bin/sh
awk ‘BEGIN{
FS=”/n”;
RS=”";
split(“one:.two:.three:.four:.five:.six:.seven:.eight:.nine:.ten:.”,number,”.”);
}
{
printf(“/n%s reporter is : %s/n”,number[NR],$1);
for(i=2;i<=NF;i++)
printf(“%d %s/n”,i-1,$i);
}’ $*
执行该程序脚本和产生的结果如下:
[root@myfreelinux pub]# bash report1.awk myfreelinux.dat
one: reporter is : wanger
1 linux_basic
two: reporter is : lisan
1 linux_server
2 windows_server
three: reporter is : zhaosi
1 awk_tools
2 grub
3 regular_expression
解释说明:上面这个程序的字段分隔字符是( FS= “/n” ),这样的话一行数据就是一个field,而且RS=“”,所以这三个用户的记录是通过空行来分隔的。那么awk读入的第一行Record 为
wanger
linux_basic
其中$1的值是”wanger”,$2的值是:“ linux_basic”,程序中的number[ ]是一个数组(array),用来记录英文数字,比如number[1]=one:,number[2]=two:等等,这个是使用awk的字符串函 数split()来把英文数字放进数组number[ ]中的。
函数split( )用法如下:
split( 原字符串,数组名,分隔字符(field separator) )
awk将根据指定的分隔字符(field separator)分隔原字符串成一个个的字段(field), 并将各字段记录到数组中。
在执行编写的awk程序时,awk会自动从数据文件中读取数据并进行处理,直到文件结束。实际上,只要将awk读取数据的来源改成键盘输入,那么就可以设计与awk 交互的程序了。
首先看一个交互的程序。这个系程序能够实现输入一个英文单词,程序打印出该词对应的汉语意思,并继续等待用户输入新的英文单词。首先编辑一个数据文档data.dat,内容如下:
[root@myfreelinux pub]# cat data.dat
man 男人
girl 女孩
boy 男孩
rose 玫瑰
apple 苹果
banana 香蕉
编写一个互动的awk程序,内容如下:
[root@myfreelinux pub]# cat china-eng.awk
#!/bin/bash
awk ‘BEGIN{
while(getline<ARGV[1])
{
English[++n]=$1;#从数据文件中读取需要使用的数据保存在两个数组中
Chinese[n]=$2;#n最后的值作为题目数量,在question中使用
}
ARGV[1]=”-”;# “-”表示由stdin(键盘输入)
srand(); # 以系统时间为随机数启动的种子
question(); #产生考题
}
{#awk读入数据,即回答的答案
if($1!=English[ind]) print “Try again!”
else {print “/n You are right!! Press Enter to continue……”;
getline;
question();
}
}
function question()
{ind=int(rand()*n)+1;#以随机数选取考题
system(“clear”); #系统清屏
print “Press /”ctrl+d/” to exit”;
printf(“/n %s”,Chinese[ind] ” 的英文字是:”)}’ $*
下面执行一下这个程序:
[root@myfreelinux pub]# bash china-eng.awk data.dat
Press “ctrl+d” to exit
香蕉 的英文字是:apple
You are right!! Press Enter to continue……
Press “ctrl+d” to exit
男人 的英文字是:men
Try again!
man
You are right!! Press Enter to continue……
说明: 参数data.dat (ARGV[1])是存储考题数据的数据文件文件,awk从数据文件上取得数据后将英文数据存储到English的数组中,将中文数据存储到 Chinese的数组中,然后将ARGV[1] 改成”-”,”-” 表示从stdin键盘读入数据,对于ARGV在awk的第八部分中有很多说明。对于srand(); # 以系统时间为随机数启动的种子,在这个程序里,其实没有多大的用处,作为一个知识点,先介绍一下。
在BEGIN中,最后一行是question(),此函数是自定义的一个函数,此函数首先产生一个随机数,是通过 ind=int(rand()*n)+1,这个语句产生的,对于n,和数据文件有关,在这里,数据文件只有六行,所以n为6,对于随机函数取整后+1,是 因为English和Chinese这两个数组的下表是从1开始的,而rand()函数产程的数值从0~1,所以int(rand()*n)的值是从 0~5,+1后的下标才和两个数组的下标值的范围相同。system(“clear”)是awk调用系统的清屏函数。printf(“/n %s”,Chinese[ind] ” 的英文字是:”)是输出一个汉语单词,等待用户输入英文单词,输入后的数据存储在$1中,并和同下标的English数组中的数据比较,如果正 确, Press Enter to continue……提示按任何键退出,否则会提示再输入一次答案,知道答对为止,答对后,会再次调用question()函数,产生下一个问题,知道在 键盘输入结束符号 (End of file)是ctrl+d,当awk 读到ctrl+d时就停止由键盘读取数据,程序结束。
awk 的数学函数中提供两个与随机数有关的函数。
srand( ):以当前的系统时间作为随机数的种子
rand( ) :返回介于0与1之间的(近似)随机数值。
在linux/unix中大部分的应用程序都允许用户在命令之后增加一些参数,在执行awk 程序是,也可以在awk程序后增加一些参数,这些参数一般是用来指定数据文件的文件名。这里,我们看一下awk程序是如何使用这些参数的。 建立文件analyse.awk,内容如下:
root@myfreelinux pub]# cat analyse.awk
#!/bin/bash
awk ‘BEGIN{
for(i=0;i<ARGC;i++)
print ARGV[i];# 依次印出awk所记录的参数
}’ $*
执行结果如下:
[root@myfreelinux pub]# bash analyse.awk first-arg second-arg
awk
first-arg
second-arg
解释说明:ARGC,ARGV[ ]是awk的内建变量。
ARGC :是一整数,代表命令行上除了选项-v, -f 及其对应的参数之外所有参数的个数。
ARGV[ ] 是一字符串数组,ARGV[0],ARGV[1],。。。ARGV[ARGC-1]分别代表命令行上相对应的参数。
比如在这里执行的命令[root@myfreelinux pub]# bash analyse.awk first-arg second-arg,ARGC的值是3,ARGV[0]是”awk”,ARGV[1]的值为”first-arg”, ARGV[2]的值是”second-arg”。
再比如#awk -vx=21-f program fir-data sec-data
和
#awk ‘{ print $1 ,$2 }’ fir-data sec-data
这 两条ARGC 值都是3,ARGV[0]是”awk”,ARGV[1]是”fir-data”,ARGV[2]是”sec-data”,命令行上的”-f program”,” -vx=21″,程序部分’{ print $1, $2}’ 都不会被列入ARGC和ARGV[ ]中。
awk 利用ARGC 来判断要打开的数据文件的个数,但是用户可以强行更改ARGC的值;比如将ARGC的值被用户设置为1,那么awk将被蒙骗,误以为命令行上没有数据文件 文件, 所以不会以 ARGV[1],ARGV[2]等作为文件名来打开文件并读取数据;但是在程序中可以使用ARGV[1],ARGV[2]等变量来取得命令行 上数据文件的数据。
现在有一个awk程序内容如下:
[root@myfreelinux pub]# cat test1.awk
#!/bin/awk -f
BEGIN{
for(i=0;i<ARGC;i++)
print ARGV[i];
}
执行以上程序的结果如下:
[root@myfreelinux pub]# awk -f test1.awk arrive.dat today_result1
awk
arrive.dat
today_result1
加入将test1.awk的内容更改成test2.awk的内容如下:
[root@myfreelinux pub]# cat test2.awk
#!/bin/awk -f
BEGIN{
number=ARGC; #用number 记住实际的参数个数
ARGC=2;#设置ARGC=2,awk将以为只有一个资料文件
for(i=0;i<ARGC;i++)
print ARGV[i];
}
执行并查看运行结果:
[root@myfreelinux pub]# awk -f test2.awk arrive.dat today_result1 today_result2
awk
arrive.dat
这个时候会发现,虽然同样ARGC=3,但是人为的设置ARGC=2,后,awk在执行的时候,只认为有一个参数arrive.dat。
将test2.awk修改成以下内容:
[root@myfreelinux pub]# cat test3.awk
#!/bin/awk -f
BEGIN{
number=ARGC;
ARGC=2;
for(i=0;i<ARGC;i++)
print ARGV[i];
for(i=ARGC;i<number;i++)
print ARGV[i];
}
运行并查看运行结果如下:
[root@myfreelinux pub]# awk -f test3.awk arrive.dat today_result1 today_result2
awk
arrive.dat
today_result1
显 然,通过修改ARGC可以修改awk能够识别的参数的个数,但是实际存在的ARGV的内容,仍然可以访问的。比如在这里ARGC设置为2后,awk只能打 开ARGV[1]=arrive.dat,但是我们可以使用ARGV[2],ARGV[3]取得命令行上的参数 today_result1,today_result2。
awk 通过判断模式(Pattern)的值来决定是否执行其后对应的动作(Actions)。首先来看一下awk中几个常见的模式,在前十部分中,有一些模式已经做了介绍,在这里再总结一下:
1、BEGIN是awk 的保留字,是一种特殊的模式。
BEGIN 成立(其值为true)的时机是:“awk 程序一开始执行,还没有读取任何数据之前”。 所以在BEGIN{ Actions} 语法中,Actions只在程序一开始执行时被执行一次。当awk 从数据文件读入数据行后,BEGIN 便不再成立,所以不论数据文件有多少数据行数据,Actions也不会被再次执行。一般情况下,把“与数据文件内容无关”和“只需执行ㄧ次”的部分放在以 BEGIN 为模式的Actions中。
比如:[root@myfreelinux pub]# cat BEGIN.awk
#!/bin/awk -f
BEGIN{
FS=”[ /t:]+”; #设置awk分割字段的默认方式
RS=”" #设置awk分割数据行的方式
count=10; #设置count的初始值
print “====This is title====” #打印标题行
}
有些awk程序不需要读入任何数据行,这情况可把整个程序写在以BEGIN 为模式的函数中,有时候也可以写在以END为模式的函数中,END后面介绍。
例如:[root@myfreelinux pub]# awk ‘BEGIN{print “hello world!”}’会打印出“hello world!”,在awk语句后面则不需要有数据文件,这就是BEGIN模式的特点。
2、END模式
END是awk 的保留字,也是一个特殊的模式。END 成立(其值为true)的时机和BEGIN成立的时机正好相反,END成立的时机是:“awk 处理完所有数据, 即将离开程序时”,在平常读入数据行时,END模式不成立,所以END对应的Actions 并不被执行;只有当awk处理完所有数据后,END对应的Actions才会被执行。
和BEGIN模式一样,不管数据文件有多少行数据,该Actions只被执行一次。
3、关系表达式
awk 中提供了很多关系运算符(Relation Operator)
运算符 含意
> 大于
< 小于
>= 大于或等于
<= 小于或等于
== 等于
!= 不等于
~ 匹配 match
!~ 不匹配not match
以上关系运算符除~(match)与!~(not match)外,和C 语言中的含意一样。 ~(match) 与!~(match) 在awk的含意简述如下: 如果A 为一字符串,B 为一正则表达式,那么A~B就是判断字符串A 中是否包含能匹配(match)B式样的子字符串;而A!~B是判断 字符串A 中是否没有包含能匹配(match)B式样的子字符串。
例如:[root@myfreelinux pub]# awk ‘BEGIN{A=”abcdef”;B=”cd”;if(A~B){print A}}’,是判断A中是否有可以匹配B的子字符串,即“abcdef中是否包含有“cd”,如果有,则打印出A字符串。
再比如$0 ~ /program[0-9]+/.c/,模式中被用来比对的字符串为$0 时,可只以正则表达式部分表示整个模式。所以这个表达式可以将模式部分$0 ~/program[0-9]+/.c/仅用/program[0- 9]+/.c/来表示,因为正则表达式中“.”有特殊含义,所以在这里使用/转义一下。
4、正则表达式
在awk中可以直接使用正则表达式当成模式;这是$0~ 正则表达式的简写。这种模式用来判断$0(数据行) 中是否含有匹配该正则表达式的子字符串;如果含有则成立(true)并执行其对应的Actions。
例如:
[root@myfreelinux pub]# echo “123″>inte
[root@myfreelinux pub]# awk ‘{if(/^[0-9]+$/)print “this line is integer!”;}’ inte
this line is integer!
与
[root@myfreelinux pub]# awk ‘{if($0~/^[0-9]+$/)print “this line is integer!”;}’ inte 相同
5、混合模式
以上介绍的4中模式计算后结果为一逻辑值(True or False)。awk 各个逻辑值间可通过&&(and), ||(or), !(not) 结合成一个新的逻辑值,所以可以将不同的逻辑值通过上述结合符号来结合成一个新的模式,这样可进行复杂的条件判断。
例如: FNR >= 23 && FNR <= 28 { print “ ” $0 }
上式利用&& (and) 将两个模式求值的结果合并成一个逻辑值,该式将数据文件中第23行到28行向右移5格(先输出5个空白字符) 后输出。
关于FNR在这里介绍一下,FNR同NR一样都是awk 的内建变量,awk有时候处理多个文件,NR会记录所有文件的行数,而FNR则每打开一个文件,FNR会重新计数打开文件的行数,举个例子看看:
[root@myfreelinux pub]# cat inte
123
324
[root@myfreelinux pub]# cat integer
222
333
444
[root@myfreelinux pub]# awk ‘{print NR,FNR}’ inte integer
1 1
2 2
3 1
4 2
5 3
上边结果中,左侧是NR输出地是行数,一直在增加,右侧是每个文件的行数,这就是NR和FNR的区别。
6、模式1 , 模式2
遇到这种模式,awk 会设立一个switch(或flag)。当awk读入的数据行使得模式1 成立时,awk 会打开(turn on)这switch。当awk读入的数据行使得模式2 成立时,awk 会关上(turn off)这个switch。该模式成立的条件是:当这个switch 被打开(turn on)时 (包括模式1, 或模式2 成立的情况)
例如:FNR >= 23 && FNR <= 28 { print “ ” $0 } 可改写为
FNR == 23 , FNR == 28 { print “ ” $0 }
说明:当FNR >= 23 时,awk 就turn on 这个switch;因为随着数据行的读入,awk不停的累加FNR。当 FNR = 28 时,模式2 (FNR == 28) 便成立,这时awk 会关上这个switch。 当switch 打开期间,awk 会执行 print “ ” $0 ,关闭后,就不再打印了,也同样达到了相同的效果。
awk 中除了函数的参数列(Argument List)上的参数(Arguments)外,所有变量无论在什么地方出现,均被视为全局变量。全局变量的生命周期持续到程序结束。全局变量不论在 function外还是function内都可以使用,只要变量名称相同,所使用的就是同一个变量。但是递归函数会调用会调用到函数本身,所以编写这里函 数是需要特别注意。
例如:编辑一个awk脚本程序,内容如下:
[root@myfreelinux pub]# cat argument.awk
#!/bin/awk -f
BEGIN{
x=35;
y=45;
test_variable(x)
printf(“Return to main: arg1=%d,x=%d,y=%d,z=%d/n”,arg1,x,y,z)
}
function test_variable(arg1)
{
arg1++;#arg1是参数列上的参数,是local variable,离开此函数后将消失
y++;#改变主程序中的变量y
z=55;#z为该函数中新使用的变量,主程序中的变量z 仍可被使用
printf(“Inside the function: arg1=%d,x=%d,y=%d,z=%d/n”,arg1,x,y,z);
}
执行以上awk程序,并查看结果如下:
[root@myfreelinux pub]# awk -f argument.awk
Inside the function: arg1=36,x=35,y=46,z=55
Return to main: arg1=0,x=35,y=46,z=55
由上以上结果可推断出:在自定义的函数内部可以随意使用主程序中的任何变量,在自定义函数内使用的变量(除参数外),在该函数外仍然可以使用。这种特性喜忧搀半,坏处是主程序中的变量不易被保护,特别是递归调用本身,执行子函数时会破坏父函数内的变量。
一个变通的方法是:在函数的参数列中虚列一些参数。函数执行中使用这些虚列的参数来记录不想被破坏的数据,这样执行子函数时就不会破坏父函数中的参数。此外awk 并不会检查调用函数时所传递的参数个数是否一致。
例如定义递归函数如下:
function demo( arg1 )
{ # 最常见的错误例子
……………..
for(i=1; i< 20 ; i++)
{function(x) # 又呼叫本身。因为i 是global variable,所以执行完该子函数后原函数中的i已经被坏,所以本函数无法正确执行
………………}
………………..}
可将上列函数中的i虚列在该函数的参数列上,这样i便是一个局部变量,不会因执行子函数而被破坏。将上列函数修改如下:
function function( arg1,i )
{…………….
for(i=1; i< 20; i++)
{ function(x)#awk不会检查呼叫函数时,所传递的参数个数是否一致
…………..} }
$0,$1,…,NF,NR等都是awk的全局变量global variable,在递归函数中如果使用了这些awk的内部变量,可以新建一些局部变量来保存内部变量的值,以免这些内部变量的值遭到破坏。
例如:要求输入一串数据(各数据间用空白隔开) 然后打印出这些数据的所有可能的排列。使用awk编程如下
[root@myfreelinux pub]# cat sort.awk
#!/bin/bash
awk ‘
BEGIN{
print “please input some word,and each word separate with space:”;
getline;
sort($0,”");
printf(“/n There are %d way to permutation these word/n”,counter);
}
function sort(all_word,buffer,new_all_word,nf,i,j)
{
$0=all_word; #all_word给$0之后awk将自动进行字段分割,默认使用空格分割
nf=NF; #分割后NF是all_word上的单词个数
if(nf==1) #只有最后一个单词时,
{
print buffer all_word;#buffer的内容再加上all_word是完成一次排列的结果
counter++;
return;
}
#一般情况:每次从all_word中取出一个元素放到buffer中,再用all_word中剩下的元素new_all_word往下进行排列
else for(i=1;i<=nf;i++)
{
$0=all_word;#$0作为全局变量,内容发生改变,所以重新把all_word赋给$0,令awk再做一次字段分割
new_all_word=”";
for(j=1;j<=nf;j++)#连接new_all_word
if(j!=i) new_all_word=new_all_word ” ” $j;
sort(new_all_word,buffer ” ” $i);
}
}’
$*
执行该程序,并查看运行结果:
[root@myfreelinux pub]# bash sort.awk
please input some word,and each word separate with space:
my you he
my you he
my he you
you my he
you he my
he my you
he you my
There are 6 way to permutation these word
解释说明:某些旧版awk,不允许更 改$0的内容,可改用gawk或nawk,也可以使用split() 函数来分割all_word。为避免执行子函数时破坏new_all_word,nf,i,j,所以将这些变量也写在参数列上。这样 new_all_word,nf,i,j将被当成局部变量, 不会受到子函数中同名变量的影响。
在声明函数时,参数列上可以将这些“虚列的参数”与真正用于传递信息的参数间以较长的空白隔开,以便于区别。awk 中要将字符串concatenation(连接)时,直接将两字符串放在一起用空格隔开即可(Implicit Operator)。
比如:[root@myfreelinux pub]# awk ‘BEGIN{a=”I”; b=” am”; c=a b ” a student”; print c}’
I am a student
需要注意c=a b “ a student”,这一句中a,b “ a student”之间要有空格将他们隔开,否则awk把他们当成一个单词了。
awk编写的函数可以重复使用,比如把函数部分单独编写在一文件中,当需要用到这个函数时使用include,将这个函数文件包含进来,例如: $ awk -f 函数文件名 -f awk主程序文件名 数据文件名,这样,以前编写的函数程序就可以使用了,而不必重复编写这个函数了。
awk的Actions一般由下列指令(statement)所组成:
1、 表达式 ( function calls, assignments..)
2、print 表达式列表
3、printf( 格式化字符串, 表达式列表)
4、if( 表达式 ) 语句 [else 语句]
5、while( 表达式 ) 语句
6、do 语句 while( 表达式)
7、for( 表达式; 表达式; 表达式) 语句
8、for( variable in array) 语句
9、delete
10、break
11、continue
12、next
13、exit [表达式]
14、语句
在awk 中大部分指令和C 语言中的用法一致,这里仅介绍比较常用或容易混淆的指令的用法。
一、流程控制指令
if 指令
语法:if (表达式) 语句1 [else 语句2 ]
范例:
[root@myfreelinux pub]# cat inte
123
324
[root@myfreelinux pub]# cat action.awk
#!/bin/awk -f
{
if($0>100)
print “the number is :” $0
else
print “the number add 100 is :” $0+100
}
[root@myfreelinux pub]# awk -f action.awk inte
上例中if语句的用法和C 语言中相同,1、如果表达式计算(evaluate)的值小于100, 则执行 print “the number is :” $0;否则执行print “the number add 100 is :” $0+100
2、进行逻辑判断的表达式所返回的值有两种,如果逻辑值为true,则返回1,否则返回0。
3、语法中else 语句2以[ ] 前后括住表示该部分根据需要加入或省略。
while 指令
语法:while( 表达式 ) 语句
范例:
while( match(buffer,/[0-9]+/.c/ ) )
{ print “Find :” substr( buffer,RSTART,RLENGTH)
buff = substr( buffer,RSTART + RLENGTH) }
上例找出buffer中所有能匹配/[0-9]+.c/(数字之后接上 “.c”的所有子字符串)。while以函数match( )所返回的值做为判断条件。如果buffer中含有匹配指定条件的子字符串(match成功),则match()函数返回1,while 将持续进行其后的语句,while执行的条件是只要条件为真就会执行,知道判断条件为假停止执行判断语句。
do-while 指令
语法:
do 语句 while(表达式)
范例:
do{ print “Enter y or n ! ” getline data }
while( data !~ /^[YyNn]$/)
1、上例要求用户从键盘上输入一个字符,如果该字符不是Y,y,N或n,则会不停执行该循环,直到读取正确字符为YyNn为止。
2、do-while 指令与while 指令最大的差异是:do-while指令会先执行一次语句,然后再判断是否应继续执行。 所以,无论条件是否成立,语句至少会执行一次。
for循环语句
语法一:for(variable in array ) statement
范例:执行一下awk语句:
[root@myfreelinux pub]# awk ‘BEGIN{a["one"]=1;a[2]=2;a["three"]=3;for(one_array in a) printf(“a[%s]=%d/n”,one_array,a[one_array]);}’
a[three]=3
a[2]=2
a[one]=1
1、for循环可以查找数组中所有的下标值,并依次使用所指定的变量记录这些下标值,以本例而言,变量one_array将依次代表 “three”,“2”,“one”
2、for循环指令查找出的下标值之间没有任何次续关系。
语法二 :
for(expression1; expression2; expression3) statement
范例:for(i=1; i<=10; i++) sum=sum + i
说明:1、上例用来计算1加到10的总和。
2、expression1常用于设定该for 循环的起始条件,如 i=1;expression2 用于设定该循环的停止条件,比如i<=10;expression3 常用于改变expression1的值,直到使表达式2满足条件跳出循环,如i++。
break指令
break指令用以强迫中断(跳离) for,while,do-while 等循环。
范例:
while(getline < “datafile” > 0 )
{ if( $1 == 0 ) break
else print $2/$1 }
上例中,awk 不断地从数据文件datafile中读取资料,当$1等于0时,停止该执行循环。
continue 指令
循环中的运行一部分是,执行continue指令来跳过循环中尚未执行的语句。
范例:
for( index in X_array)
{ if( index !~ /[0-9]+/ )
continue
print “Juest print the digital index”, index }
上例中如果index不是数字则执行continue,即跳过后面的print语句。continue和break 是不同的:continue只是跳过后面没有执行的语句,但不会离开该循环。
next 指令
执行next指令时,awk会跳过next指令之后的所有语句,包括next之后的所有Pattern { Actions },接着读取下一笔数据行,继续从第一个 Pattern {Actions} 执行起。
范例:
[root@myfreelinux pub]# cat integer
222 111
333 111
444 111
[root@myfreelinux pub]# cat action.awk
#!/bin/awk -f
{
if(/^[ /t]*$/)
{
print “this is a blank line,nothing here!”;
next;
}
$2!=0
{
printf(“$1 is %d,$2 is %d,$2/$1 is %d/n”,$1,$2,$1/$2);
}
}
[root@myfreelinux pub]# awk -f action.awk integer
$1 is 222,$2 is 111,$2/$1 is 2
$1 is 333,$2 is 111,$2/$1 is 3
this is a blank line,nothing here!
$1 is 444,$2 is 111,$2/$1 is 4
说明:awk 读入的数据行为空白行时( match /^[ /]*$/ ),除打印消息外只执行next,即awk将跳过其后的指令,继续读取下一行数据,从头开始并再次执行if(/^[ /t]*$/)语句
exit 指令
执行exit 指令时,awk将立刻跳离(停止执行)awk程序。
awk 中的I/O指令
printf 指令
printf指令与C 语言中的用法相同,使用这个指令可以控制数据输出时的格式。
语法:printf(“format”, item1, item2,.. )
范例: printf(“$1 is %d,$2 is %d,$2/$1 is %d/n”,$1,$2,$1/$2);
1、format 部分是由一般的字串(String Constant) 和格式控制字符(Formatcontrol letter,其前会加上一个%字符)所构成。%s 和%d是最常用的格式控制字符.
2、一般字串将被原封不动地打印出来,遇到格式控制字符时,依序把format后方的item转换成所指定的格式后进行打印。
3、print和printf后民可使用> 或>>将输出到stdout 的数据重定向到其它文件,
print 指令
范例: print “Juest print the digital index”, index
1、这个例子是上面打印数组下标的例子
2、print 之后可接上字串常数(Constant String)或变量,用”,” 隔开。
getline 指令
getline var < file,file为数据文件,变量var是数据存放的变量,var省略时,数据存放到$0。
getline var pipe 变量 ,var省略时,数据存放到$0中。
getline 一次读取一行资料,若读取成功则return 1,若读取失败则return -1, 若遇到文件结束(EOF), 则return 0
close 指令 该指令用以关闭一个打开的文件,或pipe
范例:[root@myfreelinux pub]# cat reformat3.awk
#!/bin/bash
awk ‘BEGIN{
FS=”[ /t:]+”;
“date”|getline;
print “Today is”,$2,$3 > “today_result3″;
close(“today_result3″);
}
{
arrival=HM_TO_M($2,$3);
printf(“%s/t/t%s:%s %s/n”,$1,$2,$3,arrival>”480″?”*”:“ ”)|“sort -k 1 >> today_result3″;
total+=arrival;
}
END{
close(“sort -k 1 >> today_result3″);
printf(“Average arrival time: %d:%d/n”,total/NR/60, total/NR%60) >> “today_result3″;
close(“today_result3″);
}
function HM_TO_M(hour,min){ return hour*60 + min }’ $*
说明:1、上例中,一开始执行print “Today is”,$2,$3 > “today_result3″;指令输出今天的月份和日期,使用了I/O 的数据重定向( > )将数据转输出到today_result3,所以today_result3处于打开状态
2、printf(“%s/t/t%s:%s %s/n”,$1,$2,$3,arrival>”480″?”*”:” “)不停的将输出的资料送往pipe(|),awk 在程序在结束时会呼叫shell,使用指令“sort -k 1 >> today_result3″来处理管道中的数据。而不是将数据输出到管道的时候就开始排序,这一点和Unix 中pipe 的用法不完全相同。
3、最后希望在文件的末尾加上“Average arrival time”,但此时,Shell还没有执行“sort -k 1 >> today_result3″,所以数据行排序结果还没有写入today_result3中,或者可以认为是today_result3处于打开状态,所 以awk应该提前通知Shell执行命令“sort -k 1 >> today_result3″,来处理pipe中的资料。awk 中这个动作称为close pipe,是由执行close ( “shell command” )来完成。需要注意的是close( )指令中的shell command 需与”|”后方的shell command 完全相同(一字不差),比较好的方法是先将该字串定义一个简短的变量,程序中再以此变量代替该shell command
4、为什么执行close(“today_result3″)?这是因为sort完后的资料也将写到today_result3中,这个文件正为awk所 打开使用(write)中,所以awk程序中要先关闭today_result3,以免造成因二个进程同时打开一个文件进行输出(write)产生的错 误。
5、close使用的时机是:在第二次写入前,应该关闭前一次写入时打开的文件,close的时候一定于上次打开文件是的打开方式相同。
system 指令
该指令用来执行Shell上的command。
比如:
path=/etc/local/apache2
system( “rm -rf” path)
说明:
system(“字符串”)指令接受一个字符串当成Shell 的命令,上例中,使用一个字串常数”rm -rf” 连接(concate)一个变量path,形成Shell 执行命令的格式,Shell实际执行的命令是“rm -rf /etc/local/apache2”。
“|” pipe指令
“|” 配合awk 输出指令,可以把输出到标准输出即屏幕的资料继续转送给管道后面,作为管道后面的标准输入。”|”配合awk的getline指令,可呼叫Shell执行某一命令,再将getline指令所得到的资料读进awk 程序中。
例如:
“date”|getline;
print “Today is”,$2,$3 > “today_result3″;可参考6.2程序例子部分。
awk 释放所占用内存的指令
awk程序中常使用数组(Array)来存储大量数据,delete 指令可以用来释放数组所占用的内存空间。
比如:for( any in X_arr ) delete X_arr[any] ,需要注意的是:delete 指令一次只能释放数组中的一个元素.。
awk 中的数学运算符(Arithmetic Operators)
+(加), -(減), *(乘), /(除), %(求余数), ^(指数) 与C 语言中用法相同
awk 中的赋值运算符(Assignment Operators)
=, +=, -=, *= , /=, %=, ^=
x += 5 的意思是x = x + 5, 其余类推,其实和C语言中的用法相同
awk 中的条件运算符(Conditional Operator)
语法: 判断条件? value1 :value2
如果判断条件成立(true) 则返回value1,否则返回value2,这个语句也就是C语言中的三元运算符(?:)
awk 中的逻辑运算符(Logical Operators)
&&( and ), ||(or), !(not),正则表达式中使用”|” 表示||,比较容易混淆
awk 中的关系运算符(Relational Operators)
>, >=, <, < =, ==, !=, ~, !~
awk 中其它的运算符
+(正号),-(负号), ++(自加),–(自减)
awk 中各运算符的运算级
按优先高低排列:
$(栏位运算元,例如:i=3; $i表示第3栏);
^(指数运算) ;
+ ,- ,! (正,负号,及逻辑上的 not);
* ,/ ,% (乘,除,余数) ;
+ ,- (加,減) ;
>, > =,< , < =, ==, != (关系运算符);
~, !~ (match, not match) ;
&& (逻辑and)
|| (逻辑上的 or )
? : (条件运算符)
= , +=, -=,*=, /=, %=, ^= (赋值运算符)
awk 的內建函数(Built-in Functions)
一、字符串函数
语法:index( 原字符串,寻找的子字符串):
解释:若原字符串中含有欲找寻的子字符串,则返回该子字符串在原字符串中第一次出现的位置,如果没有出现该子字符串则返回0。
例如执行:
[root@myfreelinux pub]# awk ‘BEGIN{print index(“0411-8888-9999″,”-8″)}’
5 是返回值,实际上检索到“-8”时,“-”在第五位,所以返回值就是5了。
语法:length(字串)
解释:返回该字串的长度。
例如执行:
[root@myfreelinux pub]# awk ‘BEGIN{print length(“0411-8888-9999″)}’
14 是返回值
语法:match( 原字串,寻找对比的正则表达式)
解释:awk会在原字串中寻找复合正则表达式的子字符串,如果复合正则表达式的字符串有多个,以原字符串中最左侧的子字符串为准。
awk找到该字符串后会根据字符串设定以下awk内部变量的值,比如RSTART和RLENGTH
RSTART=符合条件的子字符串在原字符串中的位置,如果=0表示没有找到合条件的子字符串。
RLENGTH = 符合条件的子字符串长度,如果=-1表示没有找到符合条件的子字符串。
比如看一下两个例子:
[root@myfreelinux pub]# awk ‘BEGIN{match(“banana”,”an”);print RSTART,RLENGTH}’
2 2
[root@myfreelinux pub]# awk ‘BEGIN{match(“banana”,/(an)+/);print RSTART,RLENGTH}’
2 4
可以看到这两条语句执行的结果不一样,因为第一条的正则表达式只是寻找“an”,而第二条是寻找”an”的多个多个重复组合,所以有两组”an”,长度是4。
语法:split( 原字符串,数组名称,分隔字符)
解释: awk将根据指定的分隔字符(field separator)来分隔原字符串,将原字符串分割成一个个的域(field),并以指定的数组保存各个域的值。
例如:
[root@myfreelinux pub]# awk ‘BEGIN{str=”root:x:0:0:root:/root:/bin/bash”;split(str,array,”:”);for(one in array) print one,array[one];}’
4 0
5 root
6 /root
7 /bin/bash
1 root
2 x
3 0
语法:sprintf(格式字符串,项1,项2,….)
解释:该函数的用法与awk或C语言的输出函数printf()相似,不同的是sprintf()会要求打印出的结果当成一个字符串返回。一般常用sprintf()来改变资料格式。例如:x为一数值,若欲将其变成一个含二位小数的数值,可执行如下指令:
[root@myfreelinux pub]# awk ‘BEGIN{x=2;x=sprintf(“%.2f”,x);print x}’,执行结果是2.00
语法:sub( 比对用的正则表达式,新字符串,原字符串)
解释:sub( )将原字符串中第一个(最左边)符合正则表达式的子字符串替换为新字符串。第二个参数“新字符串”中可用”&”来表示“符合条件的字符串”。承上例,执行下列指令:
[root@myfreelinux pub]# awk ‘BEGIN{A=”a6b12anan212.456an12″;sub(/(an)+[0-9]*/,”[&]“,A);print A}’
a6b12[anan212].456an12
[root@myfreelinux pub]# awk ‘BEGIN{A=”a6b12anan212.456an12″;sub(/(an)+[0-9]*/,”|&|”,A);print A}’
a6b12|anan212|.456an12
[root@myfreelinux pub]# awk ‘BEGIN{A=”a6b12anan212.456an12″;sub(/(an)+[0-9]*/,”",A);print A}’
a6b12.456an12
[root@myfreelinux pub]# awk ‘BEGIN{A=”a6b12anan212.456an12″;sub(/(an)+[0-9]*/,”999″,A);print A}’
a6b12999.456an12
由以上四个例子可以看出,&表示匹配的字符串,对于新字符串,需要用双引号引起来,比如“999”,那么匹配的字符串就会替换成999,而如果双引号内没有任何字符的时候,就是将匹配的字符串给删除。
sub() 与match()搭配使用,可依次取出原字符串中符合指定条件的所有子字符串。 例如执行下列程式:
[root@myfreelinux pub]# vi submatch.awk
#!/bin/awk
BEGIN{
data=”p12-p24 p56-p66″;
while(match(data,/[0-9]+/)>0){
print substr(data,RSTART,RLENGTH);#RSTART表示匹配的位置,RLENGTH表示匹配的长度,这一行是打印匹配的数字
sub(/[0-9]+/,”",data);#将匹配的数字用空字符串代替
print data;} #匹配的数字打印后,用空字符串代替,原data字符串内容发生变化
}
执行并查看运行结果如下:
[root@myfreelinux pub]# awk -f submatch.awk
12
p-p24 p56-p66
24
p-p p56-p66
56
p-p p-p66
66
p-p p-p awk ‘ BEGIN { data = “p12-P34 P56-p61″
sub( )中第三个参数(原字符串)如果没有指定,默认是$0。比如可用sub( /[9-0]+/,”digital” ) 表示sub(/[0-9]+/,”digital”, $0 )
语法:gsub(正则表达式,新字符串,原字符串)
解释: 这个函数与sub()一样,是进行字符串取代的函数。不同点是gsub()会取代所有合条件的子字符串,而sub函数只会取代同一行中第一个符合条件的字符串,gsub()会返回被取代的子字符串个数。
同样是一个程序,只将sub函数替换为gsub函数,再来看一下执行效果:
[root@myfreelinux pub]# vi submatch.awk
#!/bin/awk
BEGIN{
data=”p12-p24 p56-p66″;
while(match(data,/[0-9]+/)>0){
print substr(data,RSTART,RLENGTH);
gsub(/[0-9]+/,”",data);#其他的都不变,只将sub函数替换为gsub函数,输出结果变化很大
print data;}
}
[root@myfreelinux pub]# awk -f submatch.awk
12
p-p p-p #输出结果只有两行,这就是sub和gsub的不同点
通过上面这个例子,对比一下sub中的例子,就能明显的区分出sub和gsub函数的区别。
语法:substr( 字符串,起始位置 [,长度] )
解释: 返回从起始位置起,指定长度的子字符串,如果没有指定长度,则返回起始位置到字符串末尾的子字符串。 看下例:
[root@myfreelinux pub]# awk ‘BEGIN{A=”I love todays life”;print substr(A,3);}’
love todays life
[root@myfreelinux pub]# awk ‘BEGIN{A=”I love todays life”;print substr(A,3,4);}’
love
注意空格也算字符。
(二)、 数学函数
语法:int(x)
解释:返回x的整数部分,去掉小数。看下例:
[root@myfreelinux pub]# awk ‘BEGIN{a=5.5;b=-5.5;print int(a), int(b);}’ #注意int函数是向零取整,而不是四舍五入
5 -5
语法:sqrt(x)
解释:返回x的平方根。 看下例:
[root@myfreelinux pub]# awk ‘BEGIN{a=4;b=-9;print sqrt(a),sqrt(b);}’
awk: warning: sqrt: called with negative argument -9
2 nan
通过上例看以看到,如果是一个负数,比如-9,系统会提示非法参数(negative argument),并输出nan。
语法:exp(x)
解释:将返回e 的x次方。 看下例:
[root@myfreelinux pub]# awk ‘BEGIN{print exp(1),exp(2);}’
2.71828 7.38906
语法:log(x)
解释:返回x以e为底的对数值。看下例:
[root@myfreelinux pub]# awk ‘BEGIN{print log(2.71828),log(-2);}’
awk: warning: log: received negative argument -2
0.999999 nan
可以看出,和执行sqrt(x)一样,x同样不能是负数,否则提示参数错误,并返回nan值。
语法:sin(x)
解释:x 须以弧度为单位,sin(x)将返回x的sin函数值。
语法:cos(x)
解释:x 须以弧度为单位,cos(x)将返回x的cos函数值
语法:atan2(y,x)
解释:返回y/x 的tan反函数之值,返回值系以弧度为单位。
语法:rand()
解释:返回介于0与1之间的(近似)随机数值,0 <rand()<1。除非自己指定rand()函数起始的种子,否则每次执行awk程序时,rand()函数都将使用同一个內定的种子,来产生随机数。
语法:srand([x])
解释:指定以x为rand( )函数起始的种子。如果省略了x,则awk会以执行时的日期与时间为rand()函数起始的种子。
awk的内部变量的个数不多,在这里介绍的时候就不按照字母顺序排列了,而是按相关性分类说明。
ARGC
ARGC表示命令行上除了选项-F,-v,-f等选项及其所对应的参数之外的所有参数的个数。如果将“awk程序”直接写在命令行上,那么ARGC是不会把“awk程序”计算在内的。
ARGV
ARGV是一个数据,用来记录命令行上的参数的名称。 执行下列命令:
[root@myfreelinux pub]# awk ‘BEGIN{printf(“ARGC=%d/n”,ARGC);for(a in ARGV)printf(“ARGV[%d]=%s/n”,a,ARGV[a]);}’ inte integer
ARGC=3
ARGV[0]=awk
ARGV[1]=inte
ARGV[2]=integer
需要注意当ARGC = 3 时,命令列上只指定了2 个文件。
awk的参数-F/t 表示以tab 为栏位分隔字符FS(field seporator);-v a=8 是用以初始化程序中的变量。
FILENAME
FILENAME用来表示目前正在处理的文件名。
FS域分隔字符
$0 表示目前awk所读入的数据行的内容,$1,$2…示所读入的数据行经过FS指定分割符分割后的第一域,第二域…的记过。
说明:当awk读入一行数据行”A123 8:15″ 时,会先以$0 记录,即$0 = “A123 8:15″,如果程序中进一步使用了$1,$2…或 NF等内部变量时,awk才会自动分割 $0,以便取得各域的数据。 切割后各个域的数据会分別用$1,$2, $3…等存储。
awk默认的(default)域分隔字符(FS)为空白字符(空格及tab)。以上例来说,如果没有改变FS的值,那么分割后:第一个域 ($1)=”A123″,第二个域($2)=”8:15″。也可以使用正则表达式来定义FS,比如FS=/[ /t:]+/,表示0或多个空格、tab、:分别或他们三个任意组合成的字符串作为分割符,awk每次需要分割数据行时,就会参考目前FS的值。 那么这定FS后,$0 = “A123 8:15″,将被分割为,第一个域($1) = “A123″,第二个域($2) = “8″,第三个域($3) = “15″。
NR (number record)
NR表示awk 开始执行该程序后所读取的数据行数。
FNR (file number record)
FNR 与NR功用类似;不同的是awk在处理多个数据文件的时候,每打开一个新的文件,FNR便从0重新累计,而NR是一直累加,看个列子更直观些,见下列:
[root@myfreelinux pub]# cat inte
123
324
[root@myfreelinux pub]# cat integer
222 111
333 111
444 111
[root@myfreelinux pub]# awk ‘BEGIN{print NR,FNR,$0}’ inte integer
0 0
[root@myfreelinux pub]# awk ‘{print NR,FNR,$0}’ inte integer
1 1 123
2 2 324
3 1 222 111
4 2 333 111
5 3
6 4 444 111
NF (number field)
NF表示当前行被域分隔符分割成的域的个数。awk 每读入一笔数据后,在程序中用NF记录该行数据包含的域的个数。在下一行数据被读入之前,NF不会改变。但如果使用$0来记录数据,例如:使用getline,此时NF将代表新的$0上数据的域的个数。
OFS (output file separate)
OFS输出域分隔字符。默认是” “(一个空白)
ORS (output Record separate)
ORS输出数据行分隔字符。默认值是”/n”(换行符)。
OFMT(output format)
OFMT数值数据的输出格式。默认值”%.6g”(若须要时最多印出6位小数)。
当使用print指令一次打印出多项数据时,例如:print $1,$2,输出时,awk会自动在$1与$2之间补上一个域分隔符的值(OFS 之值);每次使用print输出后,awk会自动补上h行分隔符的值(ORS 之值)。使用print 输出数值数据时,awk将采用 OFMT 之值为输出格式。例如:
[root@myfreelinux pub]# awk ‘BEGIN{print 2/3;OFS=”:”;OFMT=”%.2g”;print 2/3,1;}’
0.666667
0.67:1
程序中通过改变OFS和OFMT的值,改变了指令print的输出格式。
RS
RS( Record Separator) :awk从文件上读取数据时,将根据RS的定义把数据切割成许多Records,awk一次只读入一个Record进行处理。RS 的默认值是换行符”/n”,所以一般awk一次仅读入一行数据。有时一个Record含括了几行数据(Multi-line Record),这情況下不能再以”/n” 来分隔相邻的Records,可改用空白行来分隔,即令RS = “”,表示以空白行来分隔相邻的Records。
RSTART
RSTART与使用字串函数match( )有关的变量,是匹配的字符的开始的位置。
RLENGTH
RLENGTH与使用字串函数match( )有关的变量,RLENGTH是匹配的字符串的长度。当使用match() 函数后,awk会将match() 执行的结果以RSTART和RLENGTH记录。看下面的例子:
[root@myfreelinux pub]# awk ‘BEGIN{match(“banana”,”an”);print RSTART,RLENGTH}’
2 2
[root@myfreelinux pub]# awk ‘BEGIN{match(“banana”,/(an)+/);print RSTART,RLENGTH}’
2 4
[root@myfreelinux pub]# awk ‘BEGIN{match(“banana”,/(na)+/);print RSTART,RLENGTH;}’
3 4
SUBSEP
SUBSEP(Subscript Separator) 数组下标的分隔字符,默认值为”/034″实际上,awk中的数组只接受字串当它的下标,比如: Arr["John"]。但awk中仍然可使用数字当数组的下标,甚至可使用多维的数组(Multi-dimenisional Array),比 如:Arr[2,20]。事实上,awk在接受Arr[2,20]之前,就已先把其下标转换成字串”2/03420″,之后便以Arr["2 /03420"] 代替Arr[2,20]。可参考下例:
[root@myfreelinux pub]# awk ‘BEGIN{arr[2,20]=13;print arr[2,20];print arr["2/03420"];idx=2 SUBSEP 20;print arr[idx];}’
13
13
13
再看下面这个例子,统计每门课有几个学生选修,用课程名称作为数组的下标:
[root@myfreelinux pub]# cat kecheng.dat
zhangsan math english chinese
lisi computer chinese english
wangwu dianzi chinese math
zhaoliu huanjing english chinese
[root@myfreelinux pub]# cat kecheng.awk
#!/bin/awk -f
{
for(i=2;i<=NF;i++)
array[$i]++;
}
END{
for(one_array in array)
print one_array,array[one_array];
}
[root@myfreelinux pub]# awk -f kecheng.awk kecheng.dat
computer 1
english 3
dianzi 1
chinese 4
math 2
huanjing 1
为什么要使用正则表达式
linux/UNIX中提供了许多命令和工具,它们可以在文件中查找(Search)字符串或替换(Replace)字符串的功能。像 grep,vi,sed,awk等,不论是查找字符串还是替换字符串,都得先告诉这些命令所要查找(被替换)的字符串是什么,如果未能事先明确知道所要查 找(被替换)的字符串是什么,只知道这个字符串存在的范围或特征时,例如:(一)查找”T0.c”,”T1.c”,”T2.c”…”T9.c” 当中的任一字符串。(二)查找至少存在一个”A”的任意字符串。这种情況下,如何告诉执行查找字符串的命令所要查找的字符串是什么。例(一) 中,要查找任一在”T”与”.c” 之间存在一个阿拉伯数字的字符串;当然可以用列举的方式,一一把所要查找的字符串告诉执行命令的命令。但例(二) 中符合该条件的字符串有无限种可能,势必无法一一列举。此时,便需要另一种字符串表示的方法。
什么是正则表达式:正则表达式(以下简称Regexp)是一种字符串表达的方式。可以指定具有某特征的所有字符串。
注:为了与一般字符串区别,在这里,在正则表达式的字符串之前皆加 “Regexp”。
awk程序中常以/…/括住Regexp,以区別于一般字符串。
组成正则表达式的元素:普通字符除了 . * [ ] + ? ( ) / ^ $ 外的所有字符。由普通字符所组成的Regexp的意义与原字符串字面意义相同。例如:Regexp “the” 与一般字符串的”the” 代表相同的意义。
(Meta character) :用来代表任意一字符。在linux/unix Shell中使用 “*”表示0个或任意长度的字符。
在Regexp中:
“.” 代表任意一个字符
“*” 另有其它涵意,并不代表任意长度的字符串
^ 表示该字符串必须出现于行首
$ 表示该字符串必须出现于行末。
例如:Regexp/^The/用来表示所有出现于行首的字符串”The”。Regexp/The$/ 用来表示所有出现于行末字符串”The”。
/将特殊字符还原成字面意义的字符(Escape character) ,Regexp中特殊字符将被解释成特定的意义。如果要表示特殊字符的字面(literal meaning)意义时,在特殊字符之前加上”/”即可。例如:使用Regexp来表示字符串”a.out”时,不可写成 /a.out/,因为”.”是特殊字符,表示任意一个字符。可符合Regexp / a.out/的字符串将不只 “a.out” 一个;字符串”a2out”,”a3out”,”aaout”…都符合 Regexp /a.out/, 正确的用法为:/ a/.out/。
[...]字符集合,用来表示两中括号间所有的字符当中的任一个。
例如:Regexp/[Tt]/可用来表示字符”T” 或 “t”。所以Regexp/[Tt]he/,表示字符串”The” 或 “the”。字符集合[...] 內不可随意留空白。例如:Regexp/[ Tt ]/,其中括号內有空白字符,除表示”T”,”t” 中任一个字符,也可代表一个” “(空白字符) 。
- 字符集合中可使用”-” 来指定字符的区间,其用法如下:Regexp /[0-9]/等于/[0123456789]/,用来表示任意一个阿拉伯数字。同理Regexp/[A-Z]/ 用来表示任意一个大写英文字母。但要注意,Regexp /[0-9a-z]/并不等于/[0-9][a-z]/;前者表示一个字符,后者表示二个字符。Regexp/[-9]/或/[9-]/只代表字 符,”9″或 “-”。
[^...]使用[^...] 产生字符集合的补集(complement set)。其用法如下:例如:要指定”T”或”t”之外的任一个字符,可用/[^Tt]/表示。同理Regexp/[^a-zA-Z]/,表示英文字母之外的任一个字符。
注意”^” 的位置:”^”必须紧接在”["之后,才代表字符集合的补集。例如:Regexp /[0-9/^]/只是用来表示一个阿拉伯数字或字符”^”。
* 表示字符重复次数的特殊字符。”*” 表示它前方之字符可出现0次或任意多次,即字符大于等于0次。例如:Regexp/T[0-9]*/.c/中,*表示其前[0-9](一个阿拉伯数字)出现的次数可为0次或多次。所以Regexp /T[0-9]*/.c/可用来表示”T.c”,”T0.c”,”T1.c”…”T19.c” 。
+表示其前的字符出现一次或一次以上。例如:Regexp /[0-9]+/ 用来表示一位或一位以上的数字。
? 表示其前的字符可出现一次或不出现。例如:Regexp /[+-]?[0-9]+/ 表示数字(一位以上)之前可出现正负号或不出现正负号。
(…)用来括住一群字符,且把他当成一个group。例如:Regexp /12+/ 表示字符串”12″,”122″,”1222″,”12222″,…。egexp /(12)+/ 表示字符串 “12″,”1212″,”121212″,”12121212″…。上式中12 以( )括住,所以”+” 所表示的是12,重复出现的也是12。
| 表示逻辑上的”或”(or) 。例如:Regexp/ Oranges?|apples?|water/可用来表示:字符串 “Orange”,”Oranges” 或 “apple”,”apples” 或 “water” 。
match是什么? 讨论Regexp时,经常遇到”某字符串匹配( match )某Regexp”的字眼。意思是: “这个Regexp可被解释成该字符串”。[ 例如] : 字符串”the” 匹配(match) Regexp /[Tt]he/。因为 Regexp /[Tt]he/ 可解释成字符串 “the” 或 “The”,故字符串 “the” 或 “The”都匹配(match) Regexp /[Th]he/。
awk 中提供二个关系运算符(Relational Operator):~ ,!~,它们也称之为match,not match。但函义与一般常说的match略有不同。其定义如下:A 表一字符串,B 表一Regular Expression,只要A 字符串中存在有子字符串可match( 一般定义的 match) Regexp B ,则A ~B 就算成立,其值为true,反之则为false。! ~ 的定义与~恰好相反。例如:”another” 中含有子字符串 “the” 可match Regexp /[Tt]he/,所以 “another” ~ /[Tt]he/ 之值为 true
有些地方不把( ~,!~)与Relational Operators 归为一类。
应用Regular Expression 解题的简例:下面列出一些应用Regular Expression 的简例
例1:将文件中所有的字符串 “Regular Expression” 或 “Regular expression” 换成 “Regexp”
[root@myfreelinux pub]# cat regexp
Regular expression
Regular Expression
[root@myfreelinux pub]# awk ‘{gsub(/Regular[ /t]+[eE]xpression/,”Regexp”);print $0;}’ regexp
Regexp
Regexp
例2:去除文件中的空白行(或仅含空白字符或tab 的行)
[root@myfreelinux pub]# cat regexp
Regular expression
Regular Expression
[root@myfreelinux pub]# awk ‘{if($0!~/^[ /t]+$/) print $0;}’ regexp
Regular expression
Regular Expression
例3:在文件中具有ddd-dddd (电话号码型态,d 表digital)的字符串前加上”TEL :
[root@myfreelinux pub]# cat regexp
83786550
83786450
[root@myfreelinux pub]# awk ‘{gsub(/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/,”TEL: &”);print $0;}’ regexp
TEL: 83786550
TEL: 83786450
例4:从文件的Fullname 中分离出路径与文件名
[root@myfreelinux pub]# awk ‘BEGIN{Fullname=”/usr/local/apache2/bin/apachectl”;match(Fullname,/.*///);path=substr(Fullname,1,RLENGTH-1);filename=substr(Fullname,RLENGTH+1);print Fullname;print path;print filename}’
/usr/local/apache2/bin/apachectl
/usr/local/apache2/bin
apachectl
例5:将某一数值改以现金表示法表示(整数部分每三位加一撇,且含二位小数)
[root@myfreelinux pub]# awk ‘BEGIN{number=1234567890;number=sprintf(“¥%.2f”,number); while(match(number,/[0-9][0-9][0-9][0-9]/)) sub(/[0-9][0-9][0-9][.,]/,”,&”,number); print number}’
结果输出 ¥1,234,567,890.00
例6: 把文件中所有具”program数字.f”形态的字符串改为”[Ref :program数字.c]”
[root@myfreelinux pub]# cat program
program1.f
program2.f
program34.f
program67.f
[root@myfreelinux pub]# awk ‘{while(match($0,/program[0-9]+/.f/)){replace=”[Ref: "substr($0,RSTART,RLENGTH-2)".c]“; sub(/program[0-9]+/.f/,replace);} print $0}’ program
[Ref: program1.c]
[Ref: program2.c]
[Ref: program34.c]
[Ref: program67.c]
解 释说明:以program1.f为例,while(match($0,/program[0-9]+/.f/))是匹配以下”program1.f”的文 件名,匹配后结果会保存到RSTART=0和RLENGTH=10中;substr($0,RSTART,RLENGTH-2)就是去 program1.f的前8位,即program1;replace=”[Ref: "substr($0,RSTART,RLENGTH-2)".c]“的结果就是replace=“[Ref: program1.c]“;sub函数是用来进行代替的,也就是用[Ref: program1.c]代替program1.f。所以输出结果为上式的值。
如果文件a中包含文件b,则将文件b的记录打印出来输出到c文件里
文件a:
10/05766798607,11/20050325191329,29/0.1,14/05766798607
10/05767158557,11/20050325191329,29/0.08,14/05767158557
文件b:
05766798607
05766798608
05766798609
通过文件a和文件b对比,导出这样的文件出来.
10/05766798607,11/20050325191329,29/0.1,14/05766798607
本人查了很多网上的答案都是错误码的
正确答案应该:
方法一: awk -F'[/,]' 'ARGIND==1{a[$0]}ARGIND>1{if ($2 in a)print $0}' b a >c
方法二: awk -F'[/,]' 'NR==FNR{a[$0]}NR>FNR{if ($2 in a) print $0}' b a >c
这两种方法是用数组处理的,速度比较快,处理9万行只需4秒。
还有一种方法是通过while 每次用read 命令从b中读一条记录与a中$2比较如果相等则输出到c中
root@TestAs4 zlwt]# more for3.sh
#!/bin/bash
while read line ; do
awk -F'[/,]' '$2 == '$line' {print $0}' a >>c
done < b;
这种方法很好理解,但速度非常慢,每次只读取一条记录,9万行需5个小时处理。
例二 awk数组处理两个文件索引的问题(替代法)
[root@TestAs4 zlwt]# more a
deptA
deptB
deptC
deptD
[root@TestAs4 zlwt]# more b
aaa 0
bbb 1
ccc 2
ddd 0
eee 2
fff 2
[root@TestAs4 zlwt]# awk 'NR==FNR {k[i++]=$1} NR>FNR { print $1,k[$2]}' a b
aaa deptA
bbb deptB
ccc deptC
ddd deptA
eee deptC
fff deptC
NR==FNR {k[i++]=$1} #先把a文件的值赋给数组k,下标从0自动增长
NR>FNR { print $1,k[$2] #其中 $1,$2是b中的第一,二个域,k[$2]为a的值
下面方法是r2007版主的其实是一样的
[root@TestAs4 zlwt]# awk '{if(NR==FNR)k[i++]=$0;else print $1,k[$2]}' a b
aaa deptA
bbb deptB
ccc deptC
ddd deptA
eee deptC
fff deptC
另外一个例子
awk ' BEGIN{FS="[|]";OFS="|"}
FNR==NR{a[$1]=$2}
FNR<NR{if(!a[$1]) {$1="13";print}
else {$1=a[$1];print}}
' wj wj1>wj2
文件1
1|name1
2|name2
3|name3
5|name5
6|name6
文件2
1|name11
2|name22
3|name33
4|name44
5|name55
6|name66
7|name77
8|name88
输出结果
name1|name11
name2|name22
name3|name33
13|name44
name5|name55
name6|name66
13|name77
13|name88
它在处理2个以|分割的文件
例如
文件1 wj 格式
id1|desc1
文件2 wj1格式
id2|desc2
FNR==NR{a[$1]=$2} 意思是处理第一个文件时 把 desc1 赋值给 数组 a 的 a[id1] 单元。
FNR<NR 条件是在处理第2文件成立。这样在处理第2 文件时
{if(!a[$1]) {$1="13";print}
else {$1=a[$1];print
如果a[$1] 是空,就把第2文件那行的第1列替换为 13 输出 如: 13|desc2
如果a[$1]非空,就是这个数组值已经在处理第1文件赋过值。就把$1替换为 a[$1] 即 文件1对应的$2。输出的就是 desc1|desc2
归纳一句 就是在文件2中以id2在文件1中查id1=id2的对应desc1 ,
找到输出 desc1|desc2
找不到输出 13|desc2
例:把数组中如1331131***** 批量替换成861331131*****
#cat a.txt
13994623***
13394660***
13394660***
13394671***
13394672***
13394690***
13394692***
15304863***
#awk '{print "86"$1}' a.txt > b.txt
8613994623***
8613394660***
8613394660***
8613394671***
8613394672***
8613394690***
8613394692***
8615304863***
#awk '{print substr($1,3,11)}' b.txt 把86去掉
13994623***
13394660***
13394660***
13394671***
13394672***
13394690***
13394692***
15304863***
------------------------------------------------------------------------------
两个文件关联处理
[root@TestAs4 cwm]# awk '{print $1}' 153mdn.txt |uniq -c
4 七台河
5 伊春
18 佳木斯
13 双鸭山
66 哈尔滨
1 大兴安岭
32 大庆
20 牡丹江
19 绥化
16 鸡西
15 鹤岗
10 黑河
19 齐齐哈尔
[root@TestAs4 cwm]# awk '{print $1,substr($1,1,7)}' hlj_jifei >hlj_temp
[root@TestAs4 mdn]# more hlj_temp
13009700055 1300970
13009700495 1300970
13009701075 1300970
13009701282 1300970
[root@TestAs4 mdn]# ls
2 3 awk_script cwm hlj_jifei hlj_temp newmdn_table.TXT temp test1
[root@TestAs4 mdn]# more test1
1300019 510 020 广州
1300101 110 010 北京
1300103 110 010 北京
1300104 110 010 北京
1300106 110 010 北京
[root@TestAs4 mdn]# awk 'NR==FNR{a[substr($1,1,7)]=$4}NR>FNR&&a[b=substr($1,1,7)]{print $1,a[b]}' test1 hlj_temp |more
或
[root@TestAs4 mdn]# awk 'NR==FNR{a[$1]=$4}NR>FNR&&a[b=substr($1,1,7)]{print $1,a[b]}' test1 hlj_temp
13009700055 哈尔滨
13009700495 哈尔滨
13009701075 哈尔滨
13009701282 哈尔滨
--------------------------------------------------------------------------------------
[root@TestAs4 mdn]# more temp
1300970 13009700055
1300970 13009700495
1300970 13009701075
1300970 13009701282
--------------------------------------------------------------------------------
[root@TestAs4 mdn]# more awk_script
BEGIN { while ((getline < "test1") > 0){ lines[$1]=$4 };OFS=" " }
{
if($1 in lines){
$1=lines[$1] #把test1文件的$4替换到temp文件的$1上
print $0
}
}
#要求把test1文件的第四个字段插入到temp文件的相应条目的第一个子段中
#利用getline获取test1文件的第四个字段,并且放到一个数组中。
[root@TestAs4 mdn]# ls
2 3 awk_script cwm hlj_jifei hlj_temp newmdn_table.TXT temp test1
[root@TestAs4 mdn]# awk -f awk_script temp |wc -l
63440
[root@TestAs4 mdn]# awk -f awk_script temp |more
哈尔滨 13009700055
哈尔滨 13009700495
awk又一个例子: 统计某一列所有值的和
把所有第二列的值求和
[root@TestAs4 ~]# more cwm.txt
cwm 123
zbl 124
yhh 2
cj 1
[root@TestAs4 ~]# awk '{a[x++]=$2};END{for(i=1; i<=NR; i++) b=b+a[i-1];print b }' cwm.txt
250
[root@TestAs4 ~]# awk '{a[NR]=$2;b=0};END{for(i=1; i<=NR; i++) b=b+a[i];print b }' cwm.txt
250
显示文件的从第m行到n行
[root@TestAs4 ~]# sed -n '2,10'p mdn.txt
[root@TestAs4 ~]# awk 'NR==2,NR==10{print $0}' mdn.txt
给手机号码分G网C网
1.C网(C网是133或153开头的号)
awk '$1 ~/^133/ || $1 ~/^153/' file.txt >C网.txt
2.G网(由于G网比较多非133非153开头的都是)
awk '$1 !~/^133/ && $1 !~/^153/' file.txt >G网.txt
给两个文件每行对应连接
[root@TestAs4 cwm]# more tep_01.txt
cwm 13911320988
zbl 13931095233
chen 12333333333
cwm 12233333333
cwm 45555555555
[root@TestAs4 cwm]# more tep_02.txt
cwm1 111320988
zbl1 131095233
chen1 133333333
cwm1 133333333
cwm1 455555555
awk 'NR==FNR {a[FNR]=$0} NR>FNR { print $0,a[FNR]}' tep_01.txt tep_02.txt
cwm1 111320988 cwm 13911320988
zbl1 131095233 zbl 13931095233
chen1 133333333 chen 12333333333
cwm1 133333333 cwm 12233333333
cwm1 455555555 cwm 45555555555
还有一个命令 paste
[root@TestAs4 cwm]# paste tep_01.txt tep_02.txt
cwm 13911320988 cwm1 111320988
zbl 13931095233 zbl1 131095233
chen 12333333333 chen1 133333333
cwm 12233333333 cwm1 133333333
cwm 45555555555 cwm1 455555555
awk 处理HAN开头下一个HAN的上一行数字为结尾的文件 ... 或者中提取任一文件段 以HAN开头,下一个HAN的上一行数字段为结尾的一段 生成HAN1等这样的文件
[root@TestAs4 cwm]# more file1.txt
HAN 1
12 23 34 45
23 45 56
HAN 2
12 23 34 45
23 45 56
12 23 34 45
HAN 3
12 23 34 45
23 45 56 44
12 23 34 45
23 45 56
HAN 4
12 23 34 45
23 45 56
HAN n
awk '{ if ($1=="HAN" && NF==2) fn=$2; print $0>>"HAN" fn;}' file1.txt
awk '{fn=$2; print $1 >>fn"hb"}' hbuse.txt 这是所有记录以$2归类。
-----------------------找出两文件相同及不同的值----------------------------------
awk 'NR==FNR{a[$0]++} NR>FNR&&!a[$0]' file1 file2 找出文件2中不同的值
awk 'NR==FNR{a[$0]++} NR>FNR&&a[$0]' file1 file2 找出两文件中相同的值
或
awk 'NR==FNR{a[$0]}NR>FNR{ if(!($1 in a)) print $0}' file1 file2 找出文件2中不同的值
awk 'NR==FNR{a[$0]}NR>FNR{ if($1 in a) print $0}' file1 file2 找出两文件中相同的值
------------------------awk按字段分类统计----------------------------------------
1300018 广东
1300019 广东
1300100 北京
1300101 北京
1300126 北京
1300127 北京
1300128 北京
1300129 北京
1300130 天津
1300131 天津
1300132 天津
1300133 天津
想得到三个文件:
广东2.txt
1300018
1300019
北京6.txt
1300100
1300101
1300126
1300127
1300128
1300129
天津4.txt
1300130
1300131
1300132
1300133
awk '{a[$2]++;print $1 > $2} END {for (i in a) {print "mv " i " " i""a[i]".txt" }}' ufile|sh
得到
明白是awk是顺序处理file1、file2、file3...
所以新手来解释下高手ywlscpl代码
1、NR=已处理的记录数;FNR= 当前文件处理的记录数,明确了这个,那么处理第一个文件时,NR是等于FNR的,处理第二个文件时,NR>FNR
2、所以高手ywlscpl的代码处理第一个文件file2时,只是数组赋值,因为此时NR没有>FNR,即为:
a[巴巴]=c
a[红]=b
a[西瓜]=d
a[中西]=f
3、继续处理第二个文件file1,这时满足NR>FNR的判断条件,所以打印print $1,a[$2],即为:
print 23,a[中西],a[中西]=d,所以输出是23,f
print 98,a[红],a[红]=b,所以输出是98,b
....................................................34,d
.....................................................53,c
关于awk的多文件处理:
awk的数据输入有两个来源,标准输入和文件,后一种方式支持多个文件,如
1、 shell的Pathname Expansion方式:awk '{...}' *.txt # *.txt先被shell解释,替换成当前目录下的所有*.txt,如当前目录有1.txt和2.txt,则命令最终为awk '{...}' 1.txt 2.txt
2、直接指定多个文件: awk '{...}' a.txt b.txt c.txt ...
awk对多文件的处理流程是,依次读取各个文件内容,如上例,先读a.txt,再读b.txt....
那么,在多文件处理的时候,如何判断awk目前读的是哪个文件,而依次做对应的操作呢?
1、当awk读取的文件只有两个的时候,比较常用的有两种方法
一种是awk 'NR==FNR{...}NR>FNR{...}' file1 file2 或awk 'NR==FNR{...}NR!=FNR{...}' file1 file2
另一种是 awk 'NR==FNR{...;next}{...}' file1 file2
了解了FNR和NR这两个awk内置变量的意义就很容易知道这两种方法是如何运作的
对于awk 'NR==FNR{...}NR>FNR{...}' file1 file2
读入file1的时候,已读入file1的记录数FNR一定等于awk已读入的总记录数NR,因为file1是awk读入的首个文件,故读入file1时执行前一个命令块{...}
读入file2的时候,已读入的总记录数NR一定>读入file2的记录数FNR,故读入file2时执行后一个命令块{...}
对于awk 'NR==FNR{...;next}{...}' file1 file2
读入file1时,满足NR==FNR,先执行前一个命令块,但因为其中有next命令,故后一个命令块{...}是不会执行的
读入file2时,不满足NR==FNR,前一个命令块{..}不会执行,只执行后一个命令块{...}
2、当awk处理的文件超过两个时,显然上面那种方法就不适用了。因为读第3个文件或以上时,也满足NR>FNR (NR!=FNR),显然无法区分开来。
所以就要用到更通用的方法了:
1、ARGIND 当前被处理参数标志: awk 'ARGIND==1{...}ARGIND==2{...}ARGIND==3{...}... ' file1 file2 file3 ...
2、ARGV 命令行参数数组: awk 'FILENAME==ARGV[1]{...}FILENAME==ARGV[2]{...}FILENAME==ARGV[3]{...}...' file1 file2 file3 ...
3、 把文件名直接加入判断: awk 'FILENAME=="file1"{...}FILENAME=="file2"{...}FILENAME=="file3"{...}...' file1 file2 file3 ... #没有前两种通用
例:显示文本文件myfile 中第七行到第十五行中以字符%分隔的第一字段,第三字段和第
七字段:
awk -F % 'NR==7,NR==15 {printf $1 $3 $7}'
例:显示文件myfile 中的行号和第1字段:
$awk '{printf"%03d%s\n",NR,$1}' myfile
例:显示文本文件 mydoc 匹配(含有)字符串"sun"的所有行。
$awk '/sun/{print}' mydoc
例:下面是一个较为复杂的匹配的示例:
$awk '/[Ss]un/,/[Mm]oon/ {print}' myfile
它将显示第一个匹配Sun 或sun 的行与第一个匹配Moon 或moon 的行之间的行,并显示到标准输出上。
例:下面的示例显示了内置变量和内置函数 length()的使用:
$awk 'length($0)>80 {print NR}' myfile
该命令行将显示文本myfile 中所有超过80 个字符的行号,在这里,用$0 表示整个记录(行),同时,内置变量NR 不使用标志符'$'。
awk 中允许进行多种测试,如常用的==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)等等,同时,作为样式匹配,还提供了~(匹配于)和!~(不匹配于)判断,awk 也支持用逻辑运算符:!(非)、&&(与)、||(或)和括号()进行多重判断,这大大增强了awk 的功能。