红联Linux门户
Linux帮助

Linux文本处理工具之awk

发布时间:2015-01-11 10:03:26来源:linux网站作者:chenlvzhou

调用方式
1.命令行
awk [-F  field-separator]  'commands'  input-file(s)
commands是真正awk命令,[-F域分隔符]是可选的。input-file(s) 是待处理的文件。在awk中,文件的每一行中,由域分隔符分开的每一项称为一个域。通常,在不指名-F域分隔符的情况下,默认的域分隔符是空格(或者tab)。
2.shell脚本方式
将所有的awk命令插入一个文件,并使awk程序可执行,然后awk命令解释器作为脚本的首行,以便通过键入脚本名称来调用。相当于shell脚本首行的:#!/bin/sh 可以换成:#!/bin/awk -f
3.将所有的awk命令插入一个单独文件,然后调用:awk -f awk-script-file input-file(s)其中,-f选项加载awk-script-file中的awk脚本,input-file(s)跟上面的是一样的。
内置变量
ARGC:命令行参数个数
ARGV:命令行参数排列
ENVIRON:UNIX环境变量
ERRNO:UNIX系统错误消息
FILENAME:awk浏览的文件名
OFMT:数字的输出格式 %.6g
FNR:浏览文件的记录数
FS:设置输入域分隔符,等价于命令行 -F选项
NF:浏览记录的域的个数
NR:已读的记录数,就是行号,从1开始
FNR:当前记录数
OFS:输出域分隔符
ORS:输出记录分隔符
RS:控制记录分隔符
$0:当前记录(作为单个变量)
$1~$n:当前记录的第n个字段,字段间由FS分隔
输出函数
print:参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已
printf:其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。具体格式如下:
%d十进制有符号整数
%u十进制无符号整数
%f浮点数
%s字符串
%c单个字符
%p指针的值
%e指数形式的浮点数
%x%X 无符号以十六进制表示的整数
%o无符号以八进制表示的整数
%g自动选择合适的表示法
获取外部变量
1.获取普通外部变量
awk ‘{action}’  变量名=变量值
2.BEGIN程序块中变量
awk –v 变量名=变量值 [–v 变量2=值2 …] 'BEGIN{action}’
3.环境变量
需要用到内置变量ENVIRON实现
运算符
1.赋值运算符
= += -= *= /= %= ^= **=
2.逻辑运算符
||:逻辑或
&&:逻辑与
3.正则运算符
~:匹配正则表达式
~!:不匹配正则表达式
4.关系运算符
< <= > >= != ==
5.算术运算符
+ - * / & ++ --
6.其它运算符
$:字段引用
空格:字符串连接符
?:C条件表达式
in:数组中是否存在某键值
字符操作
1.字符串转数字
将变量通过”+”连接运算。自动强制将字符串转为整型。非数字变成0,发现第一个非数字字符,后面自动忽略
2.数字转字符串
只需要将变量与””符号连接起来运算即可
3.字符串连接
只需要将变量与””符号连接起来运算即可
数组操作
在awk中数组叫做关联数组,因为下标既可以是数也可以是串。awk中的数组不必提前声明,也不必声明大小。数组元素用0或空串来初始化,这根据上下文而定。需要注意的是不同版本下面函数不一定相同。其相关的函数有length、asort、delete、split。当然也可以实现多维数组。
流程控制语句
1.条件判断语句(if)
if(表达式){语句1}else{语句2}
if(表达式){语句1}else if(表达式){语句2}else{语句3}
2.循环语句(while、for、do while)
while(表达式){语句}
for(变量 in 数组){语句}
for(变量;条件;表达式){语句}
do{语句}while(条件)
3.关键字
break:当break语句用于while或for语句时,导致退出程序循环。
continue:当continue语句用于while或for语句时,使程序循环移动到下一个迭代。
next:能能够导致读入下一个输入行,并返回到脚本的顶部。这可以避免对当前输入行执行其他的操作过程。
exit:语句使主输入循环退出并将控制转移到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行。
正则表达式
+:包含一个或多个字符或扩展正则表达式的具体值(在 +(加号)前)在这个字符串中,则字符串匹配。命令行:awk '/smith+ern/' testfile
?:包含零个或一个字符或扩展正则表达式的具体值(在 ?(问号)之前)在字符串中,则字符串匹配。命令行:awk '/smith?/' testfile
|:以 |(垂直线)隔开的字符串的任何一个在字符串中,则字符串匹配。命令行:awk '/allen | alan /' testfile
():在正则表达式中将字符串组合在一起。命令行:awk '/a(ll)?/' testfile
{m}:有m个模式的具体值位于字符串中,则字符串匹配。命令行:awk '/l{2}/' testfile
{m,}:至少m个模式的具体值在字符串中,则字符串匹配。命令行:awk '/t{2,}/' testfile
{m, n}:在m和n之间(包含的m和n)个模式的具体值在字符串中(其中m <= n),则字符串匹配。命令行:awk '/er{1, 2}/' testfile
[String]:正则表达式与方括号内String变量指定的任何字符匹配。命令行:awk '/sm[a-h]/' testfile
[^ String]:在 [ ](方括号)和在指定字符串开头的 ^ (插入记号) 指明正则表达式与方括号内的任何字符不匹配。这样,命令行:awk '/sm[^a-h]/' testfile
~,!~:表示指定变量与正则表达式匹配或不匹配的条件语句。命令行:awk '$1 ~ /n/' testfile
^:指定字段或记录的开头。命令行:awk '$2 ~ /^h/' testfile
$:指定字段或记录的末尾。命令行:awk '$2 ~ /y$/' testfile
.:表示除了在空白末尾的终端换行字符以外的任何一个字符。命令行:awk '/a..e/' testfile
\ :转义字符。当位于在扩展正则表达式中具有特殊含义的任何字符之前时,转义字符除去该字符的任何特殊含义。例如,命令行:/a\/\//
内置函数
1.算术函数
atan2( y, x )返回 y/x 的反正切。
cos( x )返回 x 的余弦;x 是弧度。
sin( x )返回 x 的正弦;x 是弧度。
exp( x )返回 x 幂函数。
log( x )返回 x 的自然对数。
sqrt( x )返回 x 平方根。
int( x )返回 x 的截断至整数的值。
rand( )返回任意数字 n,其中 0 <= n < 1。
srand( [Expr] )将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。
2.字符串函数
gsub( Ere, Repl, [ In ] )除了正则表达式所有具体值被替代这点,它和 sub 函数完全一样地执行,。
sub( Ere, Repl, [ In ] )用 Repl 参数指定的字符串替换 In 参数指定的字符串中的由 Ere 参数指定的扩展正则表达式的第一个具体值。sub 函数返回替换的数量。出现在 Repl 参数指定的字符串中的 &(和符号)由 In 参数指定的与 Ere 参数的指定的扩展正则表达式匹配的字符串替换。如果未指定 In 参数,缺省值是整个记录($0 记录变量)。
index( String1, String2 )在由 String1 参数指定的字符串(其中有出现 String2 指定的参数)中,返回位置,从 1 开始编号。如果 String2 参数不在 String1 参数中出现,则返回 0(零)。
length [(String)]返回 String 参数指定的字符串的长度(字符形式)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。
blength [(String)]返回 String 参数指定的字符串的长度(以字节为单位)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。
substr( String, M, [ N ] )返回具有 N 参数指定的字符数量子串。子串从 String 参数指定的字符串取得,其字符以 M 参数指定的位置开始。M 参数指定为将 String 参数中的第一个字符作为编号 1。如果未指定 N 参数,则子串的长度将是 M 参数指定的位置到 String 参数的末尾 的长度。
match( String, Ere )在 String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回位置(字符形式),从 1 开始编号,或如果 Ere 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一)。
split( String, A, [Ere] )将 String 参数指定的参数分割为数组元素 A[1], A[2], . . ., A[n],并返回 n 变量的值。此分隔可以通过 Ere 参数指定的扩展正则表达式进行,或用当前字段分隔符(FS 特殊变量)来进行(如果没有给出 Ere 参数)。除非上下文指明特定的元素还应具有一个数字值,否则 A 数组中的元素用字符串值来创建。
tolower( String )返回 String 参数指定的字符串,字符串中每个大写字符将更改为小写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。
toupper( String )返回 String 参数指定的字符串,字符串中每个小写字符将更改为大写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。
sprintf(Format, Expr, Expr, . . . )根据 Format 参数指定的 printf 子例程格式字符串来格式化 Expr 参数指定的表达式并返回最后生成的字符串。
3.时间函数
mktime( YYYY MM DD HH MM SS[ DST])生成时间格式
strftime([format [, timestamp]])格式化时间输出,将时间戳转为时间字符串,具体格式如下:
%a星期几的缩写(Sun)
%A星期几的完整写法(Sunday)
%b月名的缩写(Oct)
%B月名的完整写法(October)
%c本地日期和时间
%d十进制日期
%D日期 08/20/99
%e日期,如果只有一位会补上一个空格
%H用十进制表示24小时格式的小时
%I用十进制表示12小时格式的小时
%j从1月1日起一年中的第几天
%m十进制表示的月份
%M十进制表示的分钟
%p12小时表示法(AM/PM)
%S十进制表示的秒
%U十进制表示的一年中的第几个星期(星期天作为一个星期的开始)
%w十进制表示的星期几(星期天是0)
%W十进制表示的一年中的第几个星期(星期一作为一个星期的开始)
%x重新设置本地日期(08/20/99)
%X重新设置本地时间(12:00:00)
%y两位数字表示的年(99)
%Y当前月份
%Z时区(PDT)
%%百分号(%)
systime()得到时间戳,返回从1970年1月1日开始到当前时间(不计闰年)的整秒数
4.其他函数
close( Expression )用同一个带字符串值的 Expression 参数来关闭由 print 或 printf 语句打开的或调用 getline 函数打开的文件或管道。如果文件或管道成功关闭,则返回 0;其它情况下返回非零值。如果打算写一个文件,并稍后在同一个程序中读取文件,则 close 语句是必需的。
system(Command )执行 Command 参数指定的命令,并返回退出状态。等同于 system 子例程。
Expression | getline [ Variable ]从来自 Expression 参数指定的命令的输出中通过管道传送的流中读取一个输入记录,并将该记录的值指定给 Variable 参数指定的变量。如果当前未打开将 Expression 参数的值作为其命令名称的流,则创建流。创建的流等同于调用 popen 子例程,此时 Command 参数取 Expression 参数的值且 Mode 参数设置为一个是 r 的值。只要流保留打开且 Expression 参数求得同一个字符串,则对 getline 函数的每次后续调用读取另一个记录。如果未指定 Variable 参数,则 $0 记录变量和 NF 特殊变量设置为从流读取的记录。
getline [ Variable ] < Expression从 Expression 参数指定的文件读取输入的下一个记录,并将 Variable 参数指定的变量设置为该记录的值。只要流保留打开且 Expression 参数对同一个字符串求值,则对 getline 函数的每次后续调用读取另一个记录。如果未指定 Variable 参数,则 $0 记录变量和 NF 特殊变量设置为从流读取的记录。
getline [ Variable ]将 Variable 参数指定的变量设置为从当前输入文件读取的下一个输入记录。如果未指定 Variable 参数,则 $0 记录变量设置为该记录的值,还将设置 NF、NR 和 FNR 特殊变量。
5.排序函数
asort:对数组的值进行排序,并且会丢掉原先键值;
asorti:对数组的下标进行排序。

例如
1.内置变量
a.$0
awk '/^root/{print $0}' /etc/passwd  #/^root/为选择表达式,$0代表是逐行
b.设置字段分隔符号(FS)
awk 'BEGIN {FS=":"} /^root/ {print $1, NF}' /etc/passwd #FS为字段分隔符,可以自己设置,默认是空格,因为passwd里面是”:”分隔,所以需要修改默认分隔符。NF是字段总数,$0代表当前行记录,$1-$n是当前行,各个字段对应值。
c.记录条数(NR、FNR)
awk 'BEGIN {FS=":"} {print NR, $1, FNR}' /etc/passwd #NR得到当前记录所在行
d.设置输出字段分隔符(OFS)
awk 'BEGIN {FS=":"; OFS="~~"} /^root/ {print FNR, $1, $NF}' /etc/passwd #OFS设置默认字段分隔符
e.设置输出行记录分隔符(ORS)
awk 'BEGIN {FS=":"; ORS="~~"} {print FNR, $1, $NF}' #ORS默认是换行符,这里修改为:”~~”,所有行之间用”~~”分隔了
f.输入参数获取(ARGC,ARGV)
awk 'BEGIN {FS=":"; print "ARGC="ARGC; for (k in ARGV) {print k"="ARGV[k];}}' /etc/passwd #ARGC得到所有输入参数个数,ARGV获得输入参数内容,是一个数组
g.获得传入的文件名(FILENAME)
awk 'BEGIN {FS=":"; print FILENAME} {print FILENAME}' /etc/passwd #FILENAME,$0-$N,NF 不能使用在BEGIN中,BEGIN中不能获得任何与文件记录操作的变量。
h.获得linux环境变量(ENVIRON)
awk 'BEGIN {print ENVIRON["PATH"];}' /etc/passwd #ENVIRON是子典型数组,可以通过对应键值获得它的值。
i.输出数据格式设置(OFMT)
awk 'BEGIN {OFMT="%.3f"; print 2/3,123.11111111;}' /etc/passwd #OFMT默认输出格式是:%.6g 保留六位小数,这里修改OFMT会修改默认数据输出格式
j.按宽度指定分隔符
echo 20100117054932 | awk 'BEGIN {FIELDWIDTHS="4 2 2 2 2 3"} {print $1"-"$2"-"$3,$4":"$5":"$6}' #FIELDWIDTHS其格式为空格分隔的一串数字,用以对记录进行域的分隔,FIELDWIDTHS="4 2 2 2 2 2"就表示$1宽度是4,$2是2,$3是2  .... 。这个时候会忽略:FS分隔符
k.RSTART RLENGTH
awk 'BEGIN{start=match("this is a test",/[a-z]+$/); print start, RSTART, RLENGTH }'
awk 'BEGIN{start=match("this is a test",/^[a-z]+$/); print start, RSTART, RLENGTH }' #RSTART被匹配正则表达式首位置,RLENGTH匹配字符长度,没有找到为-1.
2.获取外部变量
a.获取普通的外部变量
test='awk code'
echo | awk '{print test}' test="$test"
echo | wawk 'BEGIN {print test}' test="$test" #传入的变量可以在action中获取值,但是变量名与值放到'{action}'后边。其中BEGIN的action是不能获得
b.BEGIN程序块的变量
test='awk code'
echo | awk -v test="$test" 'BEGIN {print test}'
echo | awk -v test="$test" '{print test}' #用-v 传入变量可以在3中类型的action 中都可以获得到,但顺序在  action前面
c.获得环境变量
awk  'BEGIN {for (i in ENVIRON) {print i"="ENVIRON[i];}}'
3.运算符
a.赋值运算符、算术运算
echo | awk 'BEGIN {a=1; b=5; c=3} {a+=5; b++; c--} END {print a, b, c, a*b}'
b.逻辑运算符
awk 'BEGIN {a=1; b=2; print (a>5 && b<=2),(a>5 || b<=2);}'
c.关系运算符
awk 'BEGIN{a="11";if(a >= 9){print "ok";}}' #> < 可以作为字符串比较,也可以用作数值比较,关键看操作数如果是字符串 就会转换为字符串比较。两个都为数字 才转为数值比较。字符串比较:按照ascii码顺序比较
d.其它运算符
awk 'BEGIN {a="b"; print a=="b" ? "ok" : "err";}'  #三目运算
awk 'BEGIN {a="b"; arr[0]="b"; arr[1]="c"; print (a in arr);}' #判断数组中是否存在该键值
3.字符操作
a.字符串转数字
awk 'BEGIN {a="100"; b="10test10"; print (a+b+0);}'
awk 'BEGIN {a="100"; b="10test10"; print a+b+0;}'
b.数字转字符串
awk 'BEGIN {a=100; b=100; c=(a""b); print c}'
awk 'BEGIN {a=100; b=100; c=a""b; print c}'
c.字符串连接
awk 'BEGIN {a="a"; b="b"; c=a""b; print c}'
3.数组操作
a.赋值
awk 'BEGIN {tB["a"]="a1"; tB["b"]=1;}'
b.数组长度(length)
awk 'BEGIN {tB["a"]="a1"; tB["b"]=1; print length(tB)}'
awk 'BEGIN {info="it is a test"; split(info,tA," "); print asort(tA);}'  #asort对数组进行排序,返回数组长度
c.字符串分割为数组(split)
awk 'BEGIN {info="it is a test"; lens=split(info,tA," "); print length(tA), lens;}' #length返回字符串以及数组长度,split进行分割字符串为数组,也会返回分割得到数组长度
d.数组输出
awk 'BEGIN {info="it is a test"; split(info,tA," "); for(k in tA){print k, tA[k];}}' #for…in 输出,因为数组是关联数组,默认是无序的。
awk 'BEGIN {info="it is a test"; tlen=split(info,tA," "); for(k=1; k<=tlen; k++){print k, tA[k];}}' #如果需要得到有序数组,需要通过下标获得。数组下标是从1开始,与c数组不一样
e.判断键值是否存在
awk 'BEGIN {tB["a"]="a1"; tB["b"]="b1"; if(tB["c"]!="1"){print "no found";}; for(k in tB){print k,tB[k];}}' #tB[“c”]没有定义,但是循环时候,发现已经存在该键值,它的值为空,这里需要注意,awk数组是关联数组,只要通过数组引用它的key,就会自动创建改序列
awk 'BEGIN {tB["a"]="a1"; tB["b"]="b1"; if( "c" in tB){print "ok";}; for(k in tB){print k,tB[k];}}' #if(key in array) 通过这种方法判断数组中是否包含”key”键值,才能正确的判断
f.删除键值
awk 'BEGIN {tB["a"]="a1"; tB["b"]="b1"; delete tB["a"]; for(k in tB){print k, tB[k];}}'
g.二维数组
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];
}
}
}'
#array[k,k2]引用获得数组内容
4.流程控制操作
a.awk 'BEGIN{
score=100;
if(score>90)
{
print "优秀";
}
else if(score>80)
{
print "良好";
}
else if(score>70)
{
print "普通";
}
else if(score>60)
{
print "及格";
}else
{
print "不及格";
}
}'
b.awk 'BEGIN{
total=0;
while(i<=100)
{
total+=i;
i++;
}
print total;
}'
c.awk 'BEGIN{
for(k in ENVIRON)
{
print k"="ENVIRON[k];
}
}'  #ENVIRON是awk常量,是数组
d.awk 'BEGIN{
total=0;
for(i=0;i<=100;i++)
{
total+=i;
}
print total;
}'
e.awk 'BEGIN{
total=0;
i=0;
do
{
total+=i;
i++;
}while(i<=100)
print total;
}'
5.内置函数
a.算术函数
awk 'BEGIN{OFMT="%.3f";fs=sin(1);fe=exp(10);fl=log(10);fi=int(3.1415);print fs,fe,fl,fi;}' #OFMT 设置输出数据格式是保留3位小数
awk 'BEGIN{srand();fr=int(100*rand());print fr;}' #获得随机数
b.字符串函数
awk 'BEGIN{info="this is a test2010test!";gsub(/[0-9]+/,"!",info);print info}' #在 info中查找满足正则表达式,/[0-9]+/ 用””替换,并且替换后的值,赋值给info 未给info值,默认是$0
awk 'BEGIN{info="this is a test2010test!";print index(info,"test")?"ok":"no found";}'
awk 'BEGIN{info="this is a test2010test!";print match(info,/[0-9]+/)?"ok":"no found";}'  #正则匹配
awk 'BEGIN{info="this is a test2010test!";print substr(info,4,10);}' #从第 4个 字符开始,截取10个长度字符串
awk 'BEGIN{info="this is a test";split(info,tA," ");print length(tA);for(k in tA){print k,tA[k];}}' #分割info,动态创建数组tA
c.时间函数
awk 'BEGIN{tstamp=mktime("2001 01 01 12 12 12");print strftime("%c",tstamp);}'
awk 'BEGIN{tstamp1=mktime("2001 01 01 12 12 12");tstamp2=mktime("2001 02 01 0 0 0");print tstamp2-tstamp1;}' #求2个时间段中间时间差
d.其他函数
awk 'BEGIN{while("cat /etc/passwd"|getline){print $0;};close("/etc/passwd");}' #打开外部文件
awk 'BEGIN{while(getline < "/etc/passwd"){print $0;};close("/etc/passwd");}' #逐行读取外部文件
awk 'BEGIN{print "Enter your name:";getline name;print name;}'
awk 'BEGIN{b=system("ls -al");print b;}' #调用外部应用程序
6.输出函数
a.printf
awk 'BEGIN{n1=124.113;n2=-1.224;n3=1.2345; printf("%.2f,%.2u,%.2g,%X,%o\n",n1,n2,n3,n1,n1);}' #类似于C语音
7.awk脚本
#!/bin/awk -f
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.在awk中有两个特别的表达式,BEGIN和END,这两者都可用于pattern中,提供BEGIN和END的作用是给程序赋予初始状态和程序结束之后执行一些扫尾的工作。任何在BEGIN之后列出的操作(在{}内)将在awk开始扫描输入之前执行,而END之后的操作将在扫描完全部的输入之后执行。因此通常BEGIN来显示变量和初始化变量,使用END来输出最终结果。
2.awk中数据类型,是不需要定义,自适应的。 有时候需要通过(+、"")强制转换。
3.awk数组下标是从1开始,与c数组不一样。
4.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)
5.awk实现的性能是比shell实现的强。