《Linux命令、编辑器与Shell编程》读书笔记4.2-格式化文本工具(awk)
来源:互联网 发布:马尔文粒径分析软件 编辑:程序博客网 时间:2024/06/04 22:38
格式化文本数据抽取工具awk
该工具主要是从具有一定格式的文本中抽取数据、然后以另一种方式展现。可以理解成数据库中的“视图”
【命令格式】
awk [-F分隔符] 'command' input-filename 使用-F指定被读取文件中的格式分隔符,默认是空格。
awk -f script-filename input-filename 使用-f调用已经写好的脚本文件,去处理被读取文件。
【处理原理】
以/etc/passwd文件为例,其内容格式如下:
at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash
awk -F: '{print "username:" $1}' /etc/passwd
当passwd文件被awk命令读入后,每行被分隔符(本例中是:)分成若干个域,从第一个域(可以理解成第一列)开始,被记为$1, $2...(如果要表示整条记录,则使用$0);
接着,awk按照{}里面的动作指令对每个域进行处理,直到处理完所有记录为止。
简单举例:使用awk输出包含文本头、尾的“视图”信息,这里用到了BEGIN和END(在编辑语句开始执行前后,输出指定的文本头尾信息)
假设存在以下文件:
awk 'BEGIN{print "Studen ID\tName\n---------------------"} {print $1"\t"$2}END{print "---------END--------"}' student2.2 | tee student2.2_awk
便得到了如下格式的新文件:
【正则表达、元字符、运算符和关系运算符】
举例:
1. 使用/28/匹配所有28开头的行:
awk '/28/{print $0}' student2.2
2. 先匹配所有28开头的行,然后打印这些行的1、2、3列:
awk '/28/{print $1"\t"$2"\t"$3}' student2.2
3. 输出第二列值为Liulu的行:
awk '$2=="Liulu"{print $0}' student2.2
4. 输出第二列值不为Liulu的行 :
awk '$2!="Liulu"{print $0}' student2.2
或者:
awk '$2 !~/[Ll]iulu/{print $0}' student2.2
5. 输出第一列以3、4结尾的行:
awk '$1 ~/^........[3,4]/{print $0}' student2.2
或者:
awk '$1 ~/^........[3-4]/{print $0}' student2.2
6. 输出第二列值为Xuli或Heli的行:
awk '$2 ~/(Xuli|Heli)/{print $0}' student2.2
或者:
awk '$2 ~/(Xuli|Heli)/' student2.2
*输出所有行的动作{print $0},可以省略,如上。
7. 加入算术运算符、并将计算结果生成新的一列:
假设存在如下list.txt文件:
计算每一行第四、五、六列的总和和平均值,并生成第七、八列;最后将新的文本内容保存到list_new.txt中:
awk '{print $1"\t"$2"\t"$3"\t"$4"\t"$5"\t"$3+$4+$5"\t"($3+$4+$5)/3}' list.txt | tee list_new.txt
8. 加入逻辑运算符实现更复杂的匹配操作:
针对上述新生成的list_new.txt文件,查找第二列值包含aa、且第七列值大于150的行:
awk '($2 ~/^aa*/ && $6>150){print $0}' list_new.txt
*总之,使用awk时,一定要将执行编辑的语句放入单引号内,将多个模式和条件放在小括号中,函数和流控制语句放入大括号内,避免产生错误。
【在awk命令中使用内部变量】
awk的变量分两种,内部变量(Built-in Variables)和自定义变量。内部变量通常用于控制输出和保存awk当前工作状态等信息,在引用这些变量时通常不需使用$符号。
1. 关于awk全部的内部变量说明,可以使用man awk查看,在Built-in Variables部分有详细说明(本人使用awk版本为GNU Awk 3.1.8)。下面列举一些常用的
FILENAME:保存被读取文件的文件名
NF:保存当前正在处理记录的域个数(列数),即使只读取本行记录的某几个域,这个值也会把这行中所有符合分隔符条件的列数记录下来
NR:读取到的行数,当有多个输入文件、读取新文件时,不会被重置
FNR:保存读取当前文件的记录数(行数),当有多个输入文件、读取新文件时,awk会重置这个变量
OFS:设置输出记录的分割符,默认为空格
FS:设置输入文件中的字段是以何种分隔符分割
ORS:The output record separator, by default a newline.
RS:The input record separator, by default a newline.
OFMT:数字的输出格式,The output format for numbers, "%.6g", by default.
ENVIRON:An array containing the values of the current environment. The array is indexed by the environment variables, each element being the value of that variable (e.g., ENVIRON["HOME"] might be /home/arnold). Changing this array does not affect the environment seen by programs which gawk spawns via redirection or the system() function.(读取环境变量)
举例:
(1) 手动设置输入文件的分隔符,并输出NF、FR、输入文件的文件名:
> awk 'BEGIN{FS=":";print "username\t\tShell"}{print NF,NR,$1"\t\t"$7}END{print FILENAME}' /etc/passwd
(2) 假设存在以下文件:
现在设置域(列)分隔符为#,记录(行)分隔符为回车:
> awk 'BEGIN{FS="#";RS="\\n\n"}{print $1,$2}' student3
如果不设置RS(行分隔符)为回车,则展示出来的结果会变成这样:
(3)指定输出数字的格式,比如要输出浮点型、小数点后保留2位的小数:
> awk 'BEGIN{OFMT="%.2f";print 3.14159}'
(4)引用环境变量:
awk 'END{print ENVIRON["LANG"]}' student3
或者:
awk 'BEGIN{print ENVIRON["LANG"]}'
*第一句由于使用了END表达式,所以最后必须引用一个文件名;如果不想引用文件名,把END去掉就好。
*上述两句可以合在一起写:
awk 'BEGIN{OFMT="%.2f";print 3.14159}END{print ENVIRON["LANG"]}' student3
【自定义变量】
(1)建立变量A并初始化为0,然后A与ls -l命令输出的第五列相加,得到所有文件占用的空间,最后在END表达式中将结果输出:
> ls -l | awk 'BEGIN{A=0}{A=A+$5}END{print "the total size of all files is : "A}'
变量也可以放到后面定义,比如,分别显示每个文件(夹)的大小:
> ls -l | awk '{print "the total size of "$9" is : "A}{A=0}{A=A+$5}'
*awk中引用变量时,通常不需要使用引用符号。但为了便于理解和阅读,应将变量名称大写并加上引用符号。
【awk命令的流程控制】
与C语言中的语法类似,包括if, while, do-while, for等。
1. if语句,允许多个if嵌套执行:
if (条件表达式)
{program1}
else
{program2}
2. 循环:
while (条件表达式)
{program}
或者:
do
{program}
while (条件表达式)
或者:
for (初始表达式;循环条件;步长)
{program}
3. 用于控制循环的执行过程:
continue:立即跳转到循环初始位置开始新一次循环;这表示循环语句{program}内、它后面的语句在本次循环将不再执行。
break:立即停止循环
next:告诉awk立即读取文本的下一行,本行已经没有处理的必要
exit:如果出现在END语句中,则终止awk命令;如果不在END语句中,awk则直接跳到END处执行END表达式中的语句。
<举例>
假设存在如下文本:
1. 输出第六列值大于150的行(仅输出符合条件行的第1,2,6列):
awk 'BEGIN{OFS="\t"}{if($6>=150)print $1,$2,$6}' list_new.txt
2. 输出第二列值以aa开头、且第六列值大于140的行(仅输出符合条件行的第1,2,6列):
awk 'BEGIN{OFS="\t"}{if($6>=140 && $2 ~/^aa/)print $1,$2,$6}' list_new.txt
3. 计算ls -l命令传递来的信息中,所有普通文件的大小和文件夹个数:
被调用的awk脚本文件如下:
> cat total.awk
BEGIN{
#在BEGIN表达式中初始化变量A和count
A=0;
count=0;
}
{
#如果域1的第一个字符为减号-,则将域5的值与变量A相加
if($1 ~/^-/)
A+=$5;
#如果域1的第一个字符为d,则将变量count自加
if($1 ~/^d/)
count++;
}
#在END表达式中输出计算结果和当前目录下文件夹总数
END{
print "total:"A;
#变量count-2,是因为ls -al命令输出中含有相对路径的当前目录和上级目录,如果使用ls -l则不需要-2
print "directory counts:" count-2;
}
执行:
ls -al /etc | grep -f total.awk
*这里千万注意,BEGIN和END后面的{一定要紧跟在BEGIN后面,不能为了美观换行,否则调用时会报错
4. 使用ping命令发送4个ICMP数据包测试逐级之间的双向连通性,并用awk计算其中的最大时延、最小时延和平均时延
脚本ping.awk内容如下:
BEGIN{
#使用重定义变量FS的方法设置域分隔符为空格,这里要根据ping命令实际的输出修改,不能照抄
FS=" ";
#初始化变量AVG,MAX,MIN
AVG=0;
MAX=0;
MIN=0;
}
{
#设置for语句循环次数
for(I=1;I<9;I++)
{
#如果当前为第二条记录,则为变量MAX,MIN,IP_ADDR赋值
if(NR==2)
{
MAX=$10;
MIN=$10
IP_ADDR=$4;
}
#如果当前记录数大于1且小于6
if(NR>1 && NR<6)
{
#计算时延总和,这里要注意,由于shell类型不同,尽量不要用AVG=+$10这样的简写;老老实实用下面的表达式就行
AVG=AVG+$10;
#如果当前记录的时延小于变量MIN中记录的值,则更新MIN中的值
if($10<MIN)
MIN=$10;
}
#如果记录数大于6,则跳出循环并执行END中的表达式
if(NR>=6)
exit;
#否则拂去吓一跳记录,并重新开始循环
next;
}
}
END{
#计算平均时延
AVG=AVG/4;
#输出IP地址,平均时延,最大时延和最小时延
print "IP address:",IP_ADDR;
print "Avg time:",AVG,"ms";
print "Max time:",MAX,"ms";
print "Min time:",MIN,"ms";
}
接下来先看下ping命令的输出结果:
因为冒号:和等号=影响了域(列)的分割,所以在调用上述脚本前,要先用sed命令把ping输出结果整理一下,最终命令为:
ping -c4 www.baidu.com | sed -e 's/=/ /g' -e 's/: / /g' | awk -f ping.awk
*FS变量支持设置多个域分隔符,如果将上述ping.awk脚本的第三行改写为:
FS="[:= ]"; #注意方括号里还有一个空格
并将下面的$10替换为$11,就可以省去sed转换的过程直接被调用:
> ping -c4 www.baidu.com | awk -f ping.awk
IP address: 180.97.33.107
Avg time: 5.94 ms
Max time: 7.84 ms
Min time: 4.18 ms
【awk内置函数】
举例:
以下面的文本list_new.txt为例:
1. 搜索第一列值为4234的行,并把该行中第一个aa2替换为AA2:
> awk '$1 ~/4234/{print sub(/aa2/,"AA2",$0) "\n"$0}' list_new.txt
1
4234 AA2 aa2 38 68 131 43.6667
输出结果第一行的1,就是内部函数sub(/aa2/,"AA2",$0)执行的结果,执行了1次替换;之后的\n则表示换行,最后再把这行替换后的记录打印出来
*sub函数只针对每行的第一个符合匹配条件的字符串进行处理,一旦替换成功,则直接开始处理下一行
2. gsub:替换所有匹配到的字符(串),比如将上述文本中所有的aa替换成AA,并将替换次数N打印出来:
awk 'BEGIN{N=0}{N=N+gsub(/aa/,"AA",$0);print $0}END{print N}' list_new.txt
*由此可见,gsub函数的返回值为1。
3. 求出每行的字符数:
> awk '{print length($0)}' list_new.txt
只求出第一行的字符数:
awk '{print length($0);if(NR==1)exit}' list_new.txt
4. 查找某字符(串)第一次在本行中出现的位置:
> awk '{print index($0,"aa2")}' list_new.txt
*这里输出的结果数字,表示的是字符序号,不是列号;
5. 计算某字符(串)在给定字符串中首次出现的位置:
> awk 'BEGIN{print match("hello\!welcome to nanjing\!","nan")}'
给出结果为:nan在上述语句中出现的位置为18
6. split:将字符串按指定的分隔符拆开,然后放入指定的数组中并返回数组下标,比如:
> awk 'BEGIN{print split("FV7H8-FDH32-DSOJR-923IF-WEU32",ARR,"-");for(I in ARR)print ARR[I]}'
第一个5,表示给定的字符串被split使用-符号分割为5列,亦即split函数的返回值,后面的for语句则将数组ARR中的每个元素都打印出来。
*awk中的数组可以不用定义,也不必指定元素的个数;
7. 将匹配记录中的字符从大写转换为小写:
> awk '$1=="6790"{print tolower($0)}' list_new.txt
将文本中所有的大写字母转换成小写:
> awk '{print tolower($0)}' list_new.txt
8. 将匹配记录中的字符从小写转换为大写:
> awk '$1=="1234"{print toupper($0)}' list_new.txt
将文本中所有的小写字母转换成大写:
> awk '{print toupper ( $0 ) }' list_new.txt
9. 返回0至1之间的随机数:
> awk 'BEGIN{print rand()}'
10. 计算sin、cos等函数的值:
> awk 'BEGIN{pi=3.1415926;print sin(pi/4),"\n",cos(pi/4)}'
> awk 'BEGIN{print log(100)}'
#计算e的709次方:
> awk 'BEGIN{print exp(709)}'
> awk 'BEGIN{print sqrt(256)}'
【使用printf函数对输出字符串进行格式控制后再输出】
printf的使用格式:printf "修饰符 ",[参数1,参数2,...]
常用的修饰符:
1. 将数字100分别用用字符(也就是ascii码为100对应的字符)、八进制、十六进制数输出:
> awk 'BEGIN{printf "%c\n%o\n%x\n",100,100,100}'
2. 用不同的精度输出e的30次方的计算结果:
> awk 'BEGIN{printf "%e\n%f\n%g\n",exp(30),exp(30),exp(30)}'
3. 使用.号指定精度%m.n
m表示最小字符宽度,当实际字符显示,小于该值时,自动补空格,正数时数字右对齐,负数时,左对齐;(下面用#代替空格,方便查看)例如%3d 对应 1 的话,就是##1 %-3d 对应 1 的话,就是1##当实际的显示大于m,那就按实际输出,也就是m无意义了;例如%3d 对应1234的话,就是1234,没有空格;还有要注意,这里说的是字符的宽度,所以小数点.也要算进去。4. 使用%s对齐输出,下例中的-15表示在对应字符串凑够15个字符宽度后、再打印下一个字符串(如果不够15,则在字符串后面补空格),%15s则表示如果不够15,则在字符串前面补空格:
> awk 'BEGIN{FS=":";printf "%-15s %-15s\n","username","Shell";printf "---------------------------\n"}{printf "%-15s %-15s\n",$1,$7}' /etc/passwd
5. 多列、多格式同时输出,还以上面的list.txt文件为例:
> awk '{printf "%-10s\t%-10s\t%d\t%d\t%d\t%d\t%d\n",$1,$2,$3,$4,$5,$3+$4+$5,($3+$4+$5)/3}' list.txt
这样输出的结果中、平均分一列就不会有小数了。
【awk用户自定义函数】
awk自定义函数的基本格式如下:
function name[(参数1,参数2,...)]
{
语句块;
[return 函数返回值];
}
1. 计算三个数总和与平均值的函数,并通过awk调用:
> cat sum_avg.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,$3,$4,$5,Add($3,$4,$5),Avg($3,$4,$5);}#输出文件名END{printf "%s\n",FILENAME;}#调用脚本处理list.txt:
> awk -f sum_avg.awk list.txt
2. 筛选每行中指定列的最值并返回:
> cat max.awk
function Max(Arr){ Ma=Arr[1]; for(I in Arr) { if(Ma<Arr[I]) Ma=Arr[I]; } return Ma;}#使用for循环将不同列依次放入数组Ar中{ for(J=1;J<5;J++) Ar[J]=$(J+2); printf "%-10s\t%-10s\t%d\n",$1,$2,Max(Ar);}> awk -f max.awk list.txt
就可以将list.txt文件中每行3、4、5列的最大值抽取出来。
*另一种算法:
> cat max2.awk
function Max(a,b,c){ a>b?a:a=b; ma=a>c?a:a=c; return ma;}{ printf "%-10s\t%-10s\t%d\n",$1,$2,Max($3,$4,$5)}3. 使用awk监视磁盘状况并向用户发出警告:当某一个文件系统空余空间不足90%时,通过system函数运行mail命令向管理员告警
> cat df.awk
function mess(a,b,c){ #if usage is more than 0.1, alarm to root. if(b>=0.1) { #use 'system' to excute Shell command #First, use 'date' to find out current time, disk status, and put them in the temp file 'df.tmp' #Then, send the content in 'df.tmp' to 346178152@qq.com and root user. #Finally, delete the temp file 'df.tmp'. system("date +'%F %r'>df.tmp;df -h>>df.tmp;cat df.tmp|mail -s 'Disk Warning' 346178152@qq.com,root;rm -rf df.tmp"); printf "Disk Warning!\nFile system: %s\nUsed:%3.0f%\nMounted on:",a,b*100,c; }}{ #Judge if the current line begins with Disk name if($1 ~/^\/dev\/sd/) { #calculate the usage rate N=$3/$2; if(N>=0.1) mess($1,N,$6); }}调用该脚本并执行:
> df | awk -f df.awk
使用root用户登录,可以看到如下提示:
使用type命令阅读当前最新的未读邮件:
- 《Linux命令、编辑器与Shell编程》读书笔记4.2-格式化文本工具(awk)
- 《Linux命令、编辑器与Shell编程》读书笔记10-vim编辑器
- 《Linux命令、编辑器与Shell编程》读书笔记4.1-查找和筛选工具(grep,find,sed)
- 《Linux命令、编辑器与Shell编程》读书笔记2-linux常用命令
- 《Linux命令、编辑器与Shell编程》读书笔记7-Linux系统管理
- 《Linux命令、编辑器与Shell编程》读书笔记11-Shell编程基础
- 《Linux命令、编辑器与Shell编程》读书笔记1-linux系统入门命令
- 《Linux命令、编辑器与Shell编程》读书笔记3-linux命令中的特殊字符和正则表达式
- 《Linux命令、编辑器与Shell编程》读书笔记14-Shell实例及调试
- 《Linux命令、编辑器与Shell编程》读书笔记8-Linux数据备份与应用管理
- <Linux命令,编辑器和shell编程>之命令部分读书笔记
- 《Linux命令、编辑器与Shell编程》读书笔记9-Linux网络管理
- Linux命令、编辑器与Shell编程
- 实用程序--linux命令、编辑器与shell编程
- 《Linux命令、编辑器与Shell编程》读书笔记5-用户和文件权限管理
- 《Linux命令、编辑器与Shell编程》读书笔记6-磁盘和文件系统管理
- 《Linux命令、编辑器与Shell编程》读书笔记12-函数和脚本
- 《Linux命令、编辑器与Shell编程》读书笔记13-系统脚本和登录环境
- Linux内核中实现IPV4整数转换成字符串
- 检测是否开启推送通知功能
- HDOJ 2602 Bone Collector(背包问题)
- poj--3905--Perfect Election(2-sat)
- 教程: Play Framework 2 with Scala, Anorm, JSON, CoffeeScript, jQuery & Heroku
- 《Linux命令、编辑器与Shell编程》读书笔记4.2-格式化文本工具(awk)
- C++ Primer 学习笔记_33_STL实践与分析(7) --容器适配器
- 关于Ubuntu10.04在开发驱动模块及其他模块时printk无法打印到图形界面终端的问题
- 【Android实战】播放assets或者raw文件夹下的视频文件
- 一起talk C栗子吧(第五十九回:C语言实例--字符串概述)
- vxworks下辅助时钟aux clk的使用示例
- oc swift 混编
- 如何阅读一本书读后总结
- 算法分析