描述awk命令和函数的用法及示例
一、简介
awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。
awk有3个不同版本: awk、nawk和gawk,未作特别说明,一般指gawk,gawk 是 AWK 的 GNU 版本。
二、命令的用法
命令格式
gawk [options] 'program...' FILE ...
常见选项
-F fs:指明字段分隔符;
-v var=value:自定义变量;
program语句
常见格式:PATTERN{ACTION STATEMENTS} #语句之间用分号分隔
- 逗号分隔符
- 输出的各item可以是字符串、数值、记录的字段、变量或awk表达式
- 省略item,打印全部字段,相当于print $0。
- 变量
- 内建变量
FS:输入字段分隔符,默认为空白字符;
OFS:输出字段分隔符,默认为空白字符;
RS:输入时的换行符;
ORS:输出时的换行符;
NF:每行的字段数,$NF表示最后一个字段;
NR:表示行数;
FNR:若提供多个文件,则分别计算各文件的行数;
FILENAME:当前文件名;
ARGC:命令行参数的个数;
ARGV:参数数组,保存的是命令行给定的各参数; - 自定义变量
(1) -v var=value #变量名区分字符大小写;
(2) 在program中直接定义
- printf命令
- 格式化输出:printf FORMAT, item1, item2, ...
(1) FORMAT必须给出;
(2) 不会自动换行,需要显式给出换行控制符,\n
(3) FORMAT中需要分别为后面的每个item指定一个格式化符号; - 格式符:
%c: 显示字符的ASCII码;
%d, %i: 显示十进制整数;
%e, %E: 科学计数法数值显示;
%f:显示为浮点数;
%g, %G:以科学计数法或浮点形式显示数值;
%s:显示字符串;
%u:无符号整数;
%%: 显示%自身; - 修饰符:
#[.#]:第一个数字控制显示的宽度;第二个#表示小数点后的精度。 %3.1f
-: 左对齐
+:显示数值的符号
-
操作符
算术操作符: x+y, x-y, x*y, x/y, x^y, x%y,-x,+x
字符串操作符:没有符号的操作符,字符串连接
赋值操作符:=, +=, -=, *=, /=, %=, ^=, ++, --
比较操作符:>, >=, <, <=, !=, ==
模式匹配符::是否匹配,!:是否不匹配
逻辑操作符:&&,||,!
函数调用:function_name(argu1, argu2, ...)
条件表达式:selector?if-true-expression:if-false-expression -
PATTERN
(1) empty:空模式,匹配每一行;
(2) /regular expression/:仅处理能够被此处的模式匹配到的行;
(3) relational expression: 结果为真才会被处理;真为非0值,非空字符串;
(4) line ranges:行范围,startline,endline:/pat1/,/pat2/
注意: 不支持直接给出数字的格式
~]# awk -F: '(NR>=2&&NR<=10){print $1}' /etc/passwd
(5) BEGIN/END模式
BEGIN{}: 仅在开始处理文件中的文本之前执行一次;
END{}:仅在文本处理完成之后执行一次; -
常用的action
(1) Expressions
(2) Control statements:if, while等;
(3) Compound statements:组合语句;
(4) input statements
(5) output statements -
控制语句
-
if(condition) {statments}
if(condition) {statments} else {statements}
while(conditon) {statments}
do {statements} while(condition)
for(expr1;expr2;expr3) {statements}
break
continue
delete array[index]
delete array
exit
{ statements } -
if-else:if(condition) statement [else statement]
~]# awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd
~]# awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
~]# awk '{if(NF>5) print $0}' /etc/fstab
~]# df -h | awk -F[%] '/^/dev/{print $1}' | awk '{if($NF>=20) print $1}'
使用场景:对awk取得的整行或某个字段做条件判断; -
while:while(condition) statement
条件“真”,进入循环;条件“假”,退出循环;
~]# awk '/^[[:space:]]linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
~]# awk '/^[[:space:]]linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)}; i++}}' /etc/grub2.cfg
使用场景:对一行内的多个字段逐一类似处理时使用;对数组中的各元素逐一处理时使用; -
do-while:do statement while(condition)
意义:至少执行一次循环体 -
for:for(expr1;expr2;expr3) statement
for(variable assignment;condition;iteration process) {for-body}
~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg特殊用法:
能够遍历数组中的元素;
语法:for(var in array) {for-body} -
switch
switch(expression) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; ...; default: statement} -
break和continue
break [n]
continue -
next
提前结束对本行的处理而直接进入下一行;
~]# awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd -
array
关联数组:array[index-expression]
index-expression:
(1) 可使用任意字符串;字符串要使用双引号;
(2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”;
若要判断数组中是否存在某元素,要使用"index in array"格式进行;
weekdays[mon]="Monday"
若要遍历数组中的每个元素,要使用for循环;
for(var in array) {for-body}
~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
注意:var会遍历array的每个索引;
如果数组不存在,我们引用数组,会创建,值为空,引用数组的值为0。
- 函数
1.内置函数
数值处理:
rand():返回0和1之间一个随机数
字符串处理:
length([s]):返回指定字符串的长度;
sub(r,s,[t]):以r表示的模式来查找t所表示的字符中的匹配的内容,并将其第一次出现替换为s所表示的内容;t中有没有r,有替换成s
gsub(r,s,[t]):以r表示的模式来查找t所表示的字符中的匹配的内容,并将其所有出现均替换为s所表示的内容;
split(s,a[,r]):以r为分隔符切割字符s,并将切割后的结果保存至a所表示的数组中;
~]# netstat -tan | awk '/^tcp>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'
2.自定义函数
三、入门实例
1.把文本分片后排列整齐,并加上标签
[root@localhost sctript]# cat awk01
Mike Harrington:(510) 548-1278:250:100:175
Christian Dobbins:(408) 538-2358:155:90:201
Susan Dalsass:(206) 654-6279:250:60:50
Archie McNichol:(206) 548-1348:250:100:175
Jody Savage:(206) 548-1278:15:188:150
Guy Quigley:(916) 343-6410:250:100:175
Dan Savage:(406) 298-7744:450:300:275
Nancy McNeil:(206) 548-1278:250:80:75
John Goldenrod:(916) 348-4278:250:100:175
Chet Main:(510) 548-5258:50:95:135
Tom Savage:(408) 926-3456:250:168:200
Elizabeth Stachelin:(916) 440-1763:175:75:300
[root@localhost sctript]# awk -F: 'BEGIN{printf "%-20s %-15s %-4s %-4s %-4s\n","Name","PHone","Jan","Feb","MAR"}BEGIN{print"--------------------------------------------------"}{printf "%-20s %-15s %-4s %-4s %-4s\n",$1,$2,$3,$4,$5}' awk01
Name PHone Jan Feb MAR
--------------------------------------------------
Mike Harrington (510) 548-1278 250 100 175
Christian Dobbins (408) 538-2358 155 90 201
Susan Dalsass (206) 654-6279 250 60 50
Archie McNichol (206) 548-1348 250 100 175
Jody Savage (206) 548-1278 15 188 150
Guy Quigley (916) 343-6410 250 100 175
Dan Savage (406) 298-7744 450 300 275
Nancy McNeil (206) 548-1278 250 80 75
John Goldenrod (916) 348-4278 250 100 175
Chet Main (510) 548-5258 50 95 135
Tom Savage (408) 926-3456 250 168 200
Elizabeth Stachelin (916) 440-1763 175 75 300
2.删除行内与第一列字符相同的字符
[root@localhost sctript]# cat c
a b c a d a
s d d d x s a
h j s a s h j h
j d f j a s j k j
[root@localhost sctript]# awk '{a=$1;gsub(" ?"a,"");print a""$0}' c
a b c d
s d d d x a
h j s a s j
j d f a s k
a=$1
把第一列赋值变量a。
gsub(" ?"a,"")
用函数gsub进行行内全局替换," ?"a是正则,表示前面的空格可以没有也可以有一次,把匹配到的全部替换成空格。
print a""$0
打印,在行首添加上变量a,也就字段$1。
3.打印学生的平均成绩
[root@localhost sctript]# cat d
Sophia huaxue 90
Faye huaxue 80
Sophia wuli 70
Faye wuli 60
[root@localhost sctript]# awk '{b[$1]=$1} {a[$1]++;c[$1]+=$3} END {for(i in b);for(i in a);for(i in c){y=c[i]/a[i];printf("%-8s平均成绩为:\t%2.2f\n",b[i],y)}}' d
Sophia 平均成绩为: 80.00
Faye 平均成绩为: 70.00
分析分两步:
1.先打印名字
awk '{b[$1]=$1}END{for(i in b)print b[i]}' d
Sophia
Faye
{b[$1]=$1}
以名字为变量b的下标,并在里面赋值名字。
END{for(i in b)print b[i]}
在最后,遍历数组b,并打印每一个元素。
2.打印平均分数
]# awk '{a[$1]++;c[$1]+=$3}END{for(i in a){y=c[i]/a[i];print y}}' d
80
70
{a[$1]++;c[$1]+=$3}
以名字为变量a的下标,自增计数。以名字为变量c的下标,拿自己和$3分数的相加在赋值给自己。
{y=c[i]/a[i];print y}
总分除以计数的到平均分
把两个整合
awk '{b[$1]=$1} {a[$1]++;c[$1]+=$3} END {for(i in b);for(i in a);for(i in c){y=c[i]/a[i];printf("%-8s平均成绩为:\t%2.2f\n",b[i],y)}}' d
4.文本a记录姓名、学号,文本b记录学号、学科、成绩。把a和b组合成一张表格。
[root@localhost sctript]# cat a
Sophia|0001
Faye|0002
[root@localhost sctript]# cat b
0001|Chinese|77
0001|Music|90
0002|Chinese|62
0002|Music|80
[root@localhost sctript]# awk -F \| 'NR==FNR{a[$2]=$1;next}{printf "%-8s %-6s %-8s %-2s\n",a[$1],$1,$2,$3}' a b
Sophia 0001 Chinese 77
Sophia 0001 Music 90
Faye 0002 Chinese 62
Faye 0002 Music 80
NR表示a数据和b数据的总行数,FNR表示a和b分别计数
NR==FNR为真时表示读入的是a数据,执行之后的代码a[$2]=$1;next
a[$2]=$1;next
表示把a文件切片的字段1放入数组a,数组a下标用字段2的值
NR==FNR为假时表示读入的是b数据,执行printf中的代码
printf "%-8s %-6s %-8s %-2s\n",a[$1],$1,$2,$3
格式化输出b文件切片后的值
a[$1],$1,$2,$3,其中第一个a[$1]是读取a文件时新建的数组a,这是个关联数组,文件a中$2的值等于文件b中$1的值,所以就可以把文件a存入数组的值和文件b的各字段数值一起打印出来。
5.把文本分片后按字母顺序输出相同两个字符的字段
[root@localhost sctript]# cat sting
asf|zz|123|bb|q23|cc|py|dd|ab|ee|x6|ff|ffq|gg|aaa|hh|aa
[root@localhost sctript]# awk -F \| '{for(i=1;i<=NF;i++){if($i~/^([a-z]){2}$/) {a=substr($i,1,1);b=substr($i,2,2);if(a==b) print $i | "sort"}}}' sting
aa
bb
cc
dd
ee
ff
gg
hh
zz
文本切割后按照字段个数挨个循环,用模式匹配出只有2个字母的字段,对这个字段的两个字母对比,相等时打印并传递到sort命令中排序。
6.把grades中每行的字段分别从小到大排序
[root@localhost sctript]# cat grades
44 55 66 22 77 99
100 22 77 99 33 66
55 66 100 99 88 45
[root@localhost sctript]# vim sorter.awk
#!/bin/awk -f
#这里我用选择排序算法进行排序
{for(i=0;i<NF;i++){array[i]=$(i+1)}}
sort(array)
function sort (temp){
for(i=0;i<length(temp)-1;i++){
min=i
for(j=i+1;j<length(temp);j++){
if(temp[min]>temp[j]){
min=j
}
}
if(min!=i){
temp1=temp[i]
temp[i]=temp[min]
temp[min]=temp1
}
}
for(i=0;i<NF;i++){
printf("%d ", temp[i])
}
printf "\n"
}
[root@localhost sctript]# ./sorter.awk grades
22 44 55 66 77 99
22 33 66 77 99 100
45 55 66 88 99 100