`
被触发
  • 浏览: 34792 次
文章分类
社区版块
存档分类
最新评论

linux awk命令详解

 
阅读更多
awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本信息
awk处理过程: 依次对每一行进行处理,然后输出
awk命令形式:
awk [-F|-f|-v] ‘BEGIN{} //{command1; command2} END{}’ file
[-F|-f|-v]大参数,-F指定分隔符,-f调用脚本,-v定义变量 var=value
'  ' 引用代码块
BEGIN初始化代码块,在对每一行进行处理之前,初始化代码,主要是引用全局变量,设置FS分隔符
//  匹配代码块,可以是字符串或正则表达式
{}  命令代码块,包含一条或多条命令
; 多条命令使用分号分隔
END结尾代码块,在对每一行进行处理之后再执行的代码块,主要是进行最终计算或输出结尾摘要信息

特殊要点:
$0  表示整个当前行
$1  每行第一个字段
NF 字段数量变量
NR 每行的记录号,多文件记录递增
FNR  与NR类似,不过多文件记录不递增,每个文件都从1开始
\t制表符
\n  换行符
FS BEGIN时定义分隔符
RS 输入的记录分隔符, 默认为换行符(即文本是按一行一行输入)
~匹配,与==相比不是精确比较
!~  不匹配,不精确比较
==等于,必须全部相等,精确比较
!=  不等于,精确比较
&&   逻辑与
|| 逻辑或
+匹配时表示1个或1个以上
/[0-9][0-9]+/两个或两个以上数字
/[0-9][0-9]*/ 一个或一个以上数字
FILENAME 文件名
OFS输出字段分隔符, 默认也是空格,可以改为制表符等
ORS  输出的记录分隔符,默认为换行符,即处理结果也是一行一行输出到屏幕
-F'[:#/]'定义三个分隔符

print & $0
print 是awk打印指定内容的主要命令
awk '{print}'  /etc/passwd==awk '{print $0}'  /etc/passwd 
awk '{print " "}' /etc/passwd //不输出passwd的内容,而是输出相同个数的空行,进一步解释了awk是一行一行处理文本
awk '{print "a"}'/etc/passwd //输出相同个数的a行,一行只有一个a字母
awk -F":" '{print $1}'  /etc/passwd
awk -F: '{print $1; print $2}'/etc/passwd //将每一行的前二个字段,分行输出,进一步理解一行一行处理文本
awk  -F: '{print $1,$3,$6}' OFS="\t" /etc/passwd  //输出字段1,3,6,以制表符作为分隔符

-f指定脚本文件
awk -f script.awk  file
BEGIN{
FS=":"
}
{print $1}//效果与awk -F":" '{print $1}'相同,只是分隔符使用FS在代码自身中指定

awk 'BEGIN{X=0} /^$/{ X+=1 } END{print "I find",X,"blank lines."}' test
I find 4 blank lines.
ls -l|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is",sum}'  //计算文件大小
total size is 17487

-F指定分隔符
$1 指指定分隔符后,第一个字段,$3第三个字段, \t是制表符
一个或多个连续的空格或制表符看做一个定界符,即多个空格看做一个空格
awk -F":" '{print $1}'  /etc/passwd
awk -F":" '{print $1 $3}'  /etc/passwd  //$1与$3相连输出,不分隔
awk -F":" '{print $1,$3}'  /etc/passwd  //多了一个逗号,$1与$3使用空格分隔
awk -F":" '{print $1 " " $3}'  /etc/passwd//$1与$3之间手动添加空格分隔
awk -F":" '{print "Username:" $1 "\t\t Uid:" $3 }' /etc/passwd //自定义输出 
awk -F: '{print NF}' /etc/passwd  //显示每行有多少字段
awk -F: '{print $NF}' /etc/passwd//将每行第NF个字段的值打印出来
awk -F: 'NF==4 {print }' /etc/passwd  //显示只有4个字段的行
awk -F: 'NF>2{print $0}' /etc/passwd  //显示每行字段数量大于2的行
awk '{print NR,$0}' /etc/passwd//输出每行的行号
awk -F: '{print NR,NF,$NF,"\t",$0}' /etc/passwd//依次打印行号,字段数,最后字段值,制表符,每行内容
awk -F: 'NR==5{print}'  /etc/passwd //显示第5行
awk -F: 'NR==5 || NR==6{print}'  /etc/passwd //显示第5行和第6行
route -n|awk 'NR!=1{print}'//不显示第一行

//匹配代码块
//纯字符匹配!//纯字符不匹配~//字段值匹配 !~//字段值不匹配~/a1|a2/字段值匹配a1或a2
awk '/mysql/' /etc/passwd
awk '/mysql/{print }' /etc/passwd
awk '/mysql/{print $0}' /etc/passwd //三条指令结果一样
awk '!/mysql/{print $0}' /etc/passwd//输出不匹配mysql的行
awk '/mysql|mail/{print}' /etc/passwd
awk '!/mysql|mail/{print}' /etc/passwd
awk -F: '/mail/,/mysql/{print}' /etc/passwd//区间匹配
awk '/[2][7][7]*/{print $0}' /etc/passwd//匹配包含27为数字开头的行,如27,277,2777...
awk -F: '$1~/mail/{print $1}' /etc/passwd  //$1匹配指定内容才显示
awk -F: '{if($1~/mail/) print $1}' /etc/passwd  //与上面相同
awk -F: '$1!~/mail/{print $1}' /etc/passwd //不匹配
awk -F: '$1!~/mail|mysql/{print $1}' /etc/passwd 

IF语句
必须用在{}中,且比较内容用()扩起来
awk -F: '{if($1~/mail/) print $1}' /etc/passwd//简写
awk -F: '{if($1~/mail/) {print $1}}'  /etc/passwd  //全写
awk -F: '{if($1~/mail/) {print $1} else {print $2}}' /etc/passwd//if...else...


条件表达式
==!=>>= 
awk -F":" '$1=="mysql"{print $3}' /etc/passwd 
awk -F":" '{if($1=="mysql") print $3}' /etc/passwd //与上面相同
awk -F":" '$1!="mysql"{print $3}' /etc/passwd  //不等于
awk -F":" '$3>1000{print $3}' /etc/passwd //大于
awk -F":" '$3>=100{print $3}' /etc/passwd//大于等于
awk -F":" '$3<1{print $3}' /etc/passwd //小于
awk -F":" '$3<=1{print $3}' /etc/passwd //小于等于

逻辑运算符
&& ||
awk -F: '$1~/mail/ && $3>8 {print }' /etc/passwd//逻辑与,$1匹配mail,并且$3>8
awk -F: '{if($1~/mail/ && $3>8) print }' /etc/passwd
awk -F: '$1~/mail/ || $3>1000 {print }' /etc/passwd //逻辑或
awk -F: '{if($1~/mail/ || $3>1000) print }' /etc/passwd

数值运算
awk -F: '$3 > 100' /etc/passwd
awk -F: '$3 > 100 || $3 < 5' /etc/passwd 
awk -F: '$3+$4 > 200' /etc/passwd
awk -F: '/mysql|mail/{print $3+10}' /etc/passwd  //第三个字段加10打印
awk -F: '/mysql/{print $3-$4}' /etc/passwd  //减法
awk -F: '/mysql/{print $3*$4}' /etc/passwd  //求乘积
awk '/MemFree/{print $2/1024}' /proc/meminfo//除法
awk '/MemFree/{print int($2/1024)}' /proc/meminfo  //取整

输出分隔符OFS
awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt
awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 
//输出字段6匹配WAIT的行,其中输出每行行号,字段4,5,6,并使用制表符分割字段

输出处理结果到文件
①在命令代码块中直接输出 route -n|awk 'NR!=1{print > "./fs"}'
②使用重定向进行输出  route -n|awk 'NR!=1{print}'  > ./fs

格式化输出
netstat -anp|awk '{printf "%-8s %-8s %-10s\n",$1,$2,$3}'
printf表示格式输出
%格式化输出分隔符
-8长度为8个字符
s表示字符串类型
打印每行前三个字段,指定第一个字段输出字符串类型(长度为8),第二个字段输出字符串类型(长度为8),
第三个字段输出字符串类型(长度为10)
netstat -anp|awk '$6=="LISTEN" || NR==1 {printf "%-10s %-10s %-10s \n",$1,$2,$3}'
netstat -anp|awk '$6=="LISTEN" || NR==1 {printf "%-3s %-10s %-10s %-10s \n",NR,$1,$2,$3}'

IF语句
awk -F: '{if($3>100) print "large"; else print "small"}' /etc/passwd
small
small
small
large
small
small
awk -F: 'BEGIN{A=0;B=0} {if($3>100) {A++; print "large"} else {B++; print "small"}} END{print A,"\t",B}' /etc/passwd
//ID大于100,A加1,否则B加1
awk -F: '{if($3<100) next; else print}' /etc/passwd //小于100跳过,否则显示
awk -F: 'BEGIN{i=1} {if(i
awk -F: 'BEGIN{i=1} {if(i
另一种形式
awk -F: '{print ($3>100 ? "yes":"no")}'  /etc/passwd
awk -F: '{print ($3>100 ? $3":\tyes":$3":\tno")}'  /etc/passwd

while语句
awk -F: 'BEGIN{i=1} {while(i
7 root 1
7 x 2
7 0 3
7 0 4
7 root 5
7 /root 6

数组
netstat -anp|awk 'NR!=1{a[$6]++} END{for (i in a) print i,"\t",a[i]}'

netstat -anp|awk 'NR!=1{a[$6]++} END{for (i in a) printf "%-20s %-10s %-5s \n", i,"\t",a[i]}'

9523 1 
9929 1 
LISTEN 6 
7903 1 
3038/cupsd 1 
7913 1 
10837  1 
9833 1 

应用1
awk -F: '{print NF}' helloworld.sh //输出文件每行有多少字段
awk -F: '{print $1,$2,$3,$4,$5}' helloworld.sh//输出前5个字段
awk -F: '{print $1,$2,$3,$4,$5}' OFS='\t' helloworld.sh  //输出前5个字段并使用制表符分隔输出
awk -F: '{print NR,$1,$2,$3,$4,$5}' OFS='\t' helloworld.sh  //制表符分隔输出前5个字段,并打印行号

应用2
awk -F'[:#]' '{print NF}'  helloworld.sh  //指定多个分隔符: #,输出每行多少字段
awk -F'[:#]' '{print $1,$2,$3,$4,$5,$6,$7}' OFS='\t' helloworld.sh//制表符分隔输出多字段

应用3
awk -F'[:#/]' '{print NF}' helloworld.sh  //指定三个分隔符,并输出每行字段数
awk -F'[:#/]' '{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12}' helloworld.sh  //制表符分隔输出多字段

应用4
计算/home目录下,普通文件的大小,使用KB作为单位

ls -l|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is:",sum/1024,"KB"}'

ls -l|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is:",int(sum/1024),"KB"}'//int是取整的意思

应用5
统计netstat -anp 状态为LISTEN和CONNECT的连接数量分别是多少

netstat -anp|awk '$6~/LISTEN|CONNECTED/{sum[$6]++} END{for (i in sum)
printf "%-10s %-6s %-3s \n", i," ",sum[i]}'

应用6
统计/home目录下不同用户的普通文件的总数是多少?
ls -l|awk 'NR!=1 && !/^d/{sum[$3]++} END{for (i in sum)
printf "%-6s %-5s %-3s \n",i," ",sum[i]}'
mysql  199
root  374
统计/home目录下不同用户的普通文件的大小总size是多少?

ls -l|awk 'NR!=1 && !/^d/{sum[$3]+=$5} END{for (i in sum)
printf "%-6s %-5s %-3s %-2s \n",i," ",sum[i]/1024/1024,"MB"}'

应用7
输出成绩表
awk 'BEGIN{math=0;eng=0;com=0;printf "Lineno.Name No. MathEnglishComputer Total\n";printf "------------------------------------------------------------\n"}{math+=$3; eng+=$4; com+=$5;printf "%-8s %-7s %-7s %-7s %-9s %-10s %-7s \n",NR,$1,$2,$3,$4,$5,$3+$4+$5} END{printf "------------------------------------------------------------\n";printf "%-24s %-7s %-9s %-20s \n","Total:",math,eng,com;printf "%-24s %-7s %-9s %-20s \n","Avg:",math/NR,eng/NR,com/NR}' test0

[root@localhost home]# cat test0
Marry2143 78 84 77
Jack 2321 66 78 45
Tom  2122 48 77 71
Mike 2537 87 97 95
Bob  2415 40 57 62

1、查询整张表记录,where 条件过滤,关键词:where
select * from user; awk 1 user;
select * from consumer where cost > 100;
awk '$2>100' consumer


2、对某个字段去重,或者按记录去重,关键词:distinct
select distinct(date) from consumer;
awk '!a[$3]++{print $3}' consumer
select distinct(*) from consumer;
awk '!a[$0]++' consumer


3、记录按序输出,关键词:order by
select id from user order by id;
awk '{a[$1]}END{asorti(a);for(i=1;i<=length(a);i++){print a[i]}}' user


4、取前多少条记录,关键词:limit
select * from consumer limit 2;
awk 'NR<=2' consumer
awk 'NR>2{exit}1' consumer # performance is better


5、分组求和统计,关键词:group by、having、sum、count
select id, count(1), sum(cost) from consumer group by id having count(1) > 2;
awk '{a[$1]=a[$1]==""?$2:a[$1]","$2}END{for(i in a){c=split(a[i],b,",");if(c>2){sum=0;for(j in b){sum+=b[j]};print i"\t"c"\t"sum}}}' consumer


6、模糊查询,关键词:like(like属于通配,也可正则 REGEXP)
select name from user where name like 'wang%';
awk '$2 ~/^wang/{print $2}' user
select addr from user where addr like '%bei';
awk '/.*bei$/{print $3}' user
select addr from user where addr like '%bei%';
awk '$3 ~/bei/{print $3}' user


7、多表 join 关联查询,关键词:join
select a.* , b.* from user a inner join consumer b  on a.id = b.id and b.id = 2;
awk 'ARGIND==1{a[$1]=$0;next}{if(($1 in a)&&$1==2){print a[$1]"\t"$2"\t"$3}}' user consumer


8、多表水平联接,关键词:union all
select a.* from user a union all select b.* from user b;
awk 1 user user
select a.* from user a union select b.* from user b;
awk '!a[$0]++' user user


9、随机抽样统计,关键词:order by rand()
SELECT * FROM consumer ORDER BY RAND() LIMIT 2;
awk 'BEGIN{srand();while(i<2){k=int(rand()*10)+1;if(!(k in a)){a[k];i++}}}(NR in a)' consumer



1、 统计07、08年每月交易发生笔数,按月排序
select substr(date,1,6),count(*) from mytable where date between '20070101' and '20081231'group by substr(date,1,6) order by substr(date,1,6)

awk -F "|" '$1>=20070101&&$1<=20081231{a[substr($1,1,6)]++}END{for (i in a) print i,a[i]}'file | sort -k1,1n

2、统计07、08年各类交易发生的笔数、金额
select zy,count(*),sum(je) from mx where date between '20070101' and '20081231' group by zy
awk -F "|" '$1>=20070101&&$1<=20081231{a[$2]+=$4;b[$2]++}END{for (i in a) print i,b[i],a[i]}' file

3、嗯,在我的存折明细中,按月统计下07、08年每个操作员、每月的交易发生笔数吧,扣电费、电话费(czy为auto)的不统计,结果按月份、操作员号排序

select substr(date,1,6)\"月份\",czy,count(*)\"笔数\" from mytable where czy
<>'auto' and date between '2007010' and '20081231' group by substr(date,1,6),czy
order by substr(date,1,6),czy

awk -F "|" '$6!="auto"&&substr($1,4,1)~/7|8/{a[substr($1,1,6)" "$6]++}END{for (i in a) print i,a[i]}
' file | sort -k1,1n -k2,2

4、又想到稍微复杂点的,用到了sql语句的having筛选。
统计每年发工资的总额,显示超过750元的年份。
select substr(date,1,4),sum(je) from mytable where zy='工资' group by substr(date,1,4)having sum(je)>750

awk -F "|" '$2=="工资"{a[substr($1,1,4)]+=$4}END{for (i in a) if (a[i]>750) print i,a[i]}'file

1、统计出2007年1月份发生额总和大于2000的客户,列出帐号、姓名、月份、发生额合计
select a.acct,a.name,substr(b.date,1,6),sum(b.je) from khxx a,mx b where a.acct=b.acct and substr(b.date,1,6)='200701' group by a.acct,a.name,substr(b.date,1,6) having sum(b.je)>2000

awk -F, 'NR==FNR&&substr($2,1,6)=="200701"{a[$1]+=$4}NR>FNR&&a[$1]>2000{print $1,$2,"200701",a[$1]}' mx.txt khxx.txt
1010002 李四 200701 2950

awk如何实现sql语句的group分组功能呢?
关键是定义好数组,如:第1例中sql对月份(substr(date,1,6))分组,那awk中就定义数组a[substr($1,1,6)]。至于 要给该数组赋怎样的值,看统计需求。
如例1统计分组后的次数,就a[substr($1,1,6)]++,表示 a[substr($1,1,6)]=a[substr($1,1,6)]+1;
若要合计金额,如例2,则a[$2)]+=$4,等价于 a[$2]=a[$2]+$4,$4表示第4字段,是金额字段;至于例3,又稍微复杂了点,要根据两个条件分组(月份substr($1,1,6)、操作 员$6),那定义的数组就是a[substr($1,1,6)" "$6],注意下标中的" ",是为了输出时显示效果

1、同时指定多个分割符
这时应该把分隔符写成放到方括号中,如$awk -F'[ :/t]' '{print $1,$3}'  test 
此时指定了空格,:号,tab三个作为分隔符

2、awk的key的变态用法
awk '{a[$1,"/t", $2] += $4} END {for (uin in a) printf("%s/t%d/n", uin, a[uin])  }'  test
用$1"/t"$2组成一维数组的key,这样可以用这种方式来处理很多复杂的二维数据逻辑

3、awk 的范围模板
范围模板匹配从第一个模板的第一次出现到第二个模板的第一次出现之间所有行。如果有一个模板没出现,则匹配到开头或末尾。

如$ awk '/root/,/mysql/' test将显示root第一次出现到mysql第一次出现之间的所有行。
4、awk的重定向
awk 可使用shell的重定向符进行重定向输出,如:$ awk '$1 = 100 {print $1 > "output_file" }' test。上式表示如果第一个域的值等于100,则把它输出到output_file中。也可以用>>来重定向输出,但不清空文件,只做追加操作。
这样可以利用重定向,可以把不同的结果集写入到不同的文件里
比如,我经常要跑出vip1,2,3,4,5,6的6份文件,那么就可以写一个脚本,一次性都跑出来了

5、 awk -F"|" 'NR == FNR { a[$1] = $2 } NR > FNR { if (a[$1]!=""){ a[$1] = $2 - a[$1]; if(a[$1] > 0 && $2 == 2) print $0;} }' test test1
还有涉及2个文件的时候,NR,FNR一起用,也比较少见的
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics