awk
awk的简介和功能
-
awk: Linux 文本处理三剑客:grep、sed和awk。其中grep是一种文本过滤工具,sed是文本行编辑器,而 awk 是一种报表生成器,就是对文件进行格式化处理的,这里的格式化不是文件系统的格式化,而是对文件内容进行各种“排版”,进而格式化显示。
-
在 linux 之上我们使用的是 GNU awk 简称 gawk ,并且 gawk 其实就是 awk 的链接文件,因此在系统上使用 awk和 gawk是一样的。我们通过 man gawk 的相关功能说明——gawk - pattern scanning and processing language (模式扫描及处理语言),gawk 是一种过程式编程语言。gawk 还支持条件判断、数组、循环等各种编程语言中 所有可以使用的功能,因此我们还可以把 gawk 称为一种脚本语言解释器
-
awk在处理文本时也是一次读取一行文本,然后根据输入分隔符(默认为空格符)进行切片,切成 n 个片段,然后将每一片都赋予 awk 内部的一个变量当中来进行保存,这些变量名为
$1、$2、$3......
一直到最后一个,awk 就可以对这些片段单独处理,比如显示某一段、特定段,甚至可以对某些片段进行额外的加工处理,比如计数、运算等。
awk命令格式和选项
语法形式 :
-
awk [OPPTIONS] 'program' FILE1 FILE2 FILE3...
其中 program :PARTTERN{action statement} -
action statement : 动作语句,可以有多个语句组成,各语句间使用分号分割 ;
-
OPPTIONS :
-F
fs :fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:
-v
var=value : 赋值一个用户定义变量,将外部变量传递给awk
-f
scripfile : 从脚本文件中读取awk命令
输出命令print和printf
(1)print
-
用法:print item1,item2,......
- item : 字符串,用引号引用;print " readhat" , "linux"
- 变量:显示变量的值 ,可以直接使用变量的名进行引用。print var_name
- 数值:无需加引号
-
要点:
- 各 item 之间使用逗号分开;而输出时的分隔符为默认空白字符
- 输出的各item可以为字符串或数值、当前记录的字段($#)、变量或awk的表达式;数值会被隐式转换为字符串进行输出
- print后面的item省略时,相当于运行“$0”,用于输出整行;
- 输出空白字符:
print " "
(2)printf
语法:printf FORMAT item,item2,......
要点:
- 必须提供FORMAT
- 与print语句不同,printf不会自动换行,需要显示指定换行符:
\n
- FORMAT中需要分别为后面的每个item指定一个格式符,否则 item 无法显示:格式符都以
%
开头,后跟单个字符:
格式 | 描述 |
---|---|
%d ,%i | 十进制有符号整数 |
%u | 十进制无符号整数 |
%f | 浮点数 |
%s | 字符串 |
%c | 单个字符 |
%e,%E | 科学计数法显示数值 |
%x | 以十六进制表示的整数 |
%o | 以八进制表示的整数 |
awk 'BEGIN{n1=11.234;n2=12;n3=97;n4=16;printf("%.2f,%.3f,%x,%o\n",n1,n2,n3,n4);}
变量
(1)awk内置变量:
$n :当前记录的第n个字段,比如n为1表示第一个字段,n为2表示第二个字段。
$0 :这个变量包含执行过程中当前行的文本内容。
FS :输入时字段分隔符(默认是任何空格)。
RS :记录分隔符(默认是一个换行符)。
OFS :输出时字段分隔符(默认值是一个空格)。
ORS :输出记录分隔符(默认值是一个换行符)
NF :表示字段数,在执行过程中对应于当前的字段数。
NR 表示记录数,命令后跟的所有文件将统一合并基数
FNR :行数,各文件单独计数
FILENAME :当前正被awk读取的文件的文件名
ARGC :awk命令行中的参数的个数
ARGV :包含命令行参数的数组
例如:
1、打印 /etc/passwd 的第一列(用户名)和第三列(id号)信息
(1)用F指定分割符
awk -F: '{print $1,$3}' /etc/passwd
(2)输出时中间加个TAB键
awk -F: '{print $1,"\t",$3}' /etc/passwd
(3)用内置变量指定分隔符
`awk -v FS=: '{print $1,$3}' /etc/passwd`
(4)用内置变量指定分隔符,并且在打印时再次引用此变量用于输出时字段之间的分隔符
` awk -v FS=: '{print $1,FS,$3}' /etc/passwd`
(5)定义变量s,并且把此变量赋予内置变量FS
` s=:;awk -v FS=$s '{print $1,FS,$3}' /etc/passwd`
(6)输出时给每个字段前面加上描述信息
`awk -F: '{print "user: "$1,"id:"$3}' /etc/passwd`
(7)指定输出时的分割符为“---”
`awk -F: -v OFS='---' '{print $1,$3}' /etc/passwd `
2、读取每个分区的使用率
df | awk '/^\/dev\/sd/{print $5}'
df|grep "^/dev/sd"|awk '{print $5}'|awk -F% '{print $1}'
3、取 /etc/fstab 文件中的挂载点
cat /etc/fstab |grep "^UUID"|awk '{print $2}'
4、统计一下/etc/passwd/文件中每个记录(行)的字段数
(1)每行都会统计一下
awk -F: '{print NF}' /etc/passwd
(2)只在结束的时候统计一遍
awk -F: 'END{print NF}' /etc/passwd
5、统计某个文件的行数
awk 'END{print NR}' /etc/passwd
awk 'BEGIN{while(getline < "/etc/passwd" > 0) { i++ }; print i }'
6、统计/etc/passwd和/etc/fstab的行数,两文件合并统计
awk '{print NR,$0}' /etc/passwd /etc/fstab
awk 'END{print NR}' /etc/passwd /etc/fstab
9、统计/etc/passwd和/etc/fstab的行数,两文件单独统计
awk '{print FNR,$0}' /etc/passwd /etc/fstab
7、统计命令中的参数:
awk 'END{print ARGC}' /etc/passwd /etc/fstab
8、查看此命令行中的参数:
(1)查看此命令行中的第一个参数
awk 'END{print ARGV[0]}' /etc/passwd /etc/passwd
(2)查看此命令行中的第二个参数
awk 'END{print ARGV[1]}' /etc/passwd /etc/passwd
(3)查看此命令行中的第二个参数
awk 'END{print ARGV[2]}' /etc/passwd /etc/passwd
9、使用
print $NF
可以打印出一行中的最后一个字段,使用$(NF-1)
则是打印倒数第二个字段,其他以此类推:
echo -e "line1 f2 f3\nline2 f4 f5"|awk '{print $NF}'
echo -e "line1 f2 f3\nline2 f4 f5"|awk '{print $(NF-1)}'
10、计算5个数相加:
seq 5|awk 'BEGIN{sum=0;print "相加"}{print $1"+";sum+=$1}END{print "等于";print sum}'
(2)自定义变量
将外部变量值传递给var
1、借助 -v 选项,可以将外部值(并非来自stdin)传递给awk:
name=aaa;echo|awk -v var=$name 'END{print var}'
另一种传递外部变量方法:
var1=aaa
var2=bbb
echo | awk '{print v1,v2 }' v1=$var1 v2=$var
当输入来自于文件时使用:
awk '{ print v1,v2 }' v1=$var1 v2=$var2 filename
awk的运算与判断
作为一种程序设计语言所应具有的特点之一,awk支持多种运算,这些运算与C语言提供的基本相同。awk还提供了一系列内置的运算函数(如log、sqr、cos、sin等)和一些用于对字符串进行操作(运算)的函数(如length、substr等等)。这些函数的引用大大的提高了awk的运算功能。作为对条件转移指令的一部分,关系判断是每种程序设计语言都具备的功能,awk也不例外,awk中允许进行多种测试,作为样式匹配,还提供了模式匹配表达式 ~(匹配)
和~!(不匹配)
。作为对测试的一种扩充,awk也支持用逻辑运算符。
例:
awk 'BEGIN{a="2";print a++,++a}'
赋值运算符
逻辑运算符
例:
awk 'BEGIN{a=1;b=2;print(a>5 && b<=2),(a>5 || b<=2)}'
正则运算符
awk 'BEGIN{a="wangtest";if(a~/^wang*/){print "ok"}}'
关系运算符
例:
awk 'BEGIN{a=11;if(a>=9){print "ok"}}'
其它运算符
例子:
1、三目运算符(?):
awk 'BEGIN{a=1;b=2;{print a==b ? "ok" : "err"}}'
2、遍历数组
awk 'BEGIN{arr[1]="first";arr[2]="second";for(i in arr){print arr[i]}}'
流程控制语句
在 linux awk 的 while、do-while 和 for 语句中允许使用 break , continue 语句来控制流程走向,也允许使用exit这样的语句来退出。break中断当前正在执行的循环并跳到循环外执行下一条语句。if 是流程选择用法。awk中,流程控制语句,语法结构,与c语言类型。有了这些语句,其实很多shell程序都可以交给awk,而且性能是非常快的。下面是各个语句用法。
条件判断语句
if 语句:
if(表达式)
语句1
else
语句2
格式中语句1可以是多个语句,为了方便判断和阅读,最好将多个语句用{}括起来
。awk分枝结构允许嵌套,其格式为:
if(表达式)
{语句1}
else if(表达式)
{语句2}
else
{语句3}
示例:
awk 'BEGIN{
test=65;
if(test>90){
print "优秀";
}
else if(test>=60){
print "良好";
}
else{
print "不及格";
}
}'
循环语句:
(1)while 语句
while(表达式)
{语句}
示例:
awk 'BEGIN{
i=1;
test=100;
total=0;
while(i<=100){
total+=i;
i++;
}
print total;
}'
(2)for 循环
for 循环有两种格式:
格式一:
for(变量 in 数组)
{语句}
例子:
显示Socket所处不同样状态个数
ss -ant|awk '!/^S/{state[$1]++} END{for(i in state){print i,state[i]}}'
格式二:
for(变量;条件;表达式)
{语句}
例子:
awk 'BEGIN{
total=0;
for(i=0;i<=100;i++){
total+=i;
}
print total;
}'
结果:5050
do循环
do
{语句} while(条件)
例子:
awk 'BEGIN{
i=1;
total=0;
do{total+=i;i++;}while(i<=100)
print total;
}'
结果:5050
其它语句:
break: 当 break 语句用于 while 或 for 语句时,导致退出程序循环。
continue: 当 continue 语句用于 while 或 for 语句时,使程序循环移动到下一个迭代。
next: next 能够导致读入下一个输入行,并返回到脚本的顶部。这可以避免对当前输入行执行其他的操作过程。
exit:exit 语句使主输入循环退出并将控制转移到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行。
数组的定义
数字做数组索引(下标):
Array[1]="sun"
Array[2]="kai"
字符串做数组索引(下标):
Array["first"]="www"
Array["last"]="name"
Array["birth"]="1994"
读取数组的值:
{ for(item in array) {print array[item]}; }
{ for(i=1;i<=len;i++) {print array[i]}; }
数组相关函数
- 例:得到数组的长度:
awk 'BEGIN{info="it is a test";lens=split(info,array," ");print length(array),lens}'
结果:4 4
length返回字符串以及数组长度,split进行分割字符串为数组,也会返回分割得到数组长度。
- asort对数组进行排序,返回数组长度。
awk 'BEGIN{info="it is a test";split(info,array," ");print asort(array)}'
- 输出数组内容(无序,有序输出):
- (1)无序输出:
awk 'BEGIN{info="it is a test";split(info,array," ");for(i in array){print i,array[i]}}'
结果:
4 test
1 it
2 is
3 a
- (2)有序输出:
awk 'BEGIN{info="it is a test";lent=split(info,array," ");for(i=1;i<=lent;i++){print i,array[i]}}'
结果:
1 it
2 is
3 a
4 test
注意:数组下标是从1开始,与C数组不一样。
判断键值存在以及删除键值:
错误的判断方法:
awk 'BEGIN{arr["a"]="a1";arr["b"]="b1";if(arr["c"]!="1"){print "no found";};for(i in arr){print i,":"arr[i];}}'
结果:
no found
a a1
b b1
c
以上出现奇怪问题,arr[“c”]没有定义,但是循环时候,发现已经存在该键值,它的值为空,这里需要注意,awk数组是关联数组,只要通过数组引用它的key,就会自动创建改序列。
正确的判断方法:
awk 'BEGIN{arr["a"]="a1";arr["b"]="b1";if("c" in arr){print "OK"};for(i in arr){print i,":",arr[i]}}'
结果:
a : a1
b : b1
注意:if(key in array)
通过这种方法判断数组中是否包含 key 键
删除键值:
awk 'BEGIN{arr["a"]="a1";arr["b"]="b1";delete arr["a"];for(i in arr){print i,":",arr[i]}}'
结果:
b : b1
注意:delete arr[key] 可以删除对应数组 key 的序列值。
二维、多维数组的使用
awk的多维数组在本质上实际上是一维数组,更确切一点,awk在存储上并不支持多维数组,awk提供了逻辑上模拟二维数组的访问方法。例如,array[2,4]=1
这样的访问时允许的,awk使用一个特殊的字符串SUBSEP (\034)作为分割字段,在上面的例子中,关联数组array存储的键值实际上是2\034
。
类似一维数组的成员测试,多维数组可以使用if( (i,j) in array)
这样,但是下表必须放在圆括号中。类似一维数组的循环访问,多维数组使用for(item in array)
这样的语法遍历数组,与一维数组不同的是,多维数组必须使用split()
函数来访问单独的下标。
例子:
awk 'BEGIN{
for(i=1;i<=9;i++){
for(j=1;j<=9;j++){
arr[i,j]=i*j;print i,"*",j,"=",arr[i,j];
}
}
}'
结果:
1*
1 = 1
1*
2 = 2
1*
3 = 3
1 *
4 = 4
1*
5 = 5
..............
9*
3 = 27
9*
4 = 36
9*
5 = 45
9 *
6 = 54
9*
7 = 63
9*
8 = 72
9*
9 = 81
可以引用array[i,j]
引用获得数组的内容
还可以用另外一种方法:
awk 'BEGIN{
for(i=1;i<=9;i++){
for(j=1;j<=9;j++){
arr[i,j]=i*j;
}
}
for(m in arr){
split(m,tarr,SUBSEP);print arr2[1],"*",arr2[2],"=",arr[m];
}
}'
内置函数
awk 内置函数,主要分以下3中类型:算数函数、字符串函数、其它一般函数、时间函数。
算数函数:
例子:
awk 'BEGIN{OFMT="%.3f";a=sin(1);b=exp(10);c=log(10);d=int(3.1415);print a,b,c,d;}'
OFMT 设置输出数据格式是保留3位小数。
获得随机数:
awk 'BEGIN{srand();n=int(100*rand());print n;}'
字符串函数
格式 | 描述 |
---|---|
sub(Ere,Repl,[In]) | 用Repl参数指定的字符串替换In参数指定的字符串中的由Ere参数指定的扩展正则表达式的第一个具体值。sub函数返回替换的数量。 |
gsub(Ere,Repl,[in]) | 用Real参数指定的字符串替换In参数指定的字符串中的由Erect参数指定的扩展正则表达式的所有值,它和sub函数函数完全一样的执行 |
length(String) | 返回String参数指定的字符串的长度(字符形式)。如果未给出String参数,则返回整个记录的长度($0记录变量) |
blength(String) | 返回String参数指定的字符串的长度(以字节为单位)。如果未给出String参数,则返回整个记录的长度($0记录变量) |
substr(String,M,N) | 返回具有N参数指定的字符数量子串。子串从String参数指定的字符串取得,其字符以M参数指定的位置开始取。M参数会将String参数中的第一个字符作为编号1。如果未指定N参数,则子串的长度将是M参数指定的位置到String参数的末尾的长度 |
split(String,A,[Ere]) | 以Ere参数指定的分隔符(此分隔符可以通过Ere参数指定的扩展正则表达式进行,或用当前字段分隔符(FS特殊变量)来进行)去切割字符串String,并将切割后的结果保存至A表示的数组中 |
tolower(String) | 返回String参数指定的字符串,字符串中每个大写字符将更改为小写 |
toupper(String) | 返回String参数指定的字符串,字符串中每个小写字符将更改为大写 |
index(Streing1,String2) | 在由String1参数指定的字符串中,返回位置,从1开始编号,如果String2参数不在String1参数出现,则返回0 |
match(String,Ere) | 在 String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回(字符形式),从 1 开始编号,或如果 Ere 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一) |
注:Ere 都可以是正则表达式
gsub,sub使用:
awk 'BEGIN{info="this is a test2017test!";gsub(/[0-9]+/,"!",info);print info}'
结果:
this is a test!test!
在 info 中查找满足正则表达式:
/[0-9]+/
用"!"
替换,并且替换后的值赋值给info,未给info赋值默认是$0
查找字符串(index使用)
awk 'BEGIN{info="this is a test2017test!";print index(info,"test")?"包含":"不包含"}'
结果:
包含
正则表达式匹配查找(match使用)
awk 'BEGIN{info="this is a test2017test!";print match(info,/[0-9]+/)?"OK":"no found";}'
结果:
OK
截取字符串(substr)使用
法一:
awk 'BEGIN{info="this is a test2017test!";print substr(info,2,12)}'
法二:awk 'BEGIN{info="this is a test2017test!";a=substr(info,2,12);print a}'
结果:
his is a tes
从第4个字符开始,截取10个长度字符串
字符串分割(split使用)
awk 'BEGIN{info="this is a test";split(info,arr," ");print length(arr);for(i in arr){print i,arr[i];}}'
结果:
4
4 test
1 this
2 is
3 a
分割info,动态创建数组arr,这里比较有意思,
awk for ...in 循环,是一个无序的循环
。并不是从数组下标 1...n,因此使用时需要注意
一般函数
例子:
打开外部文件(close 用法)
awk 'BEGIN{while("cat /etc/passwd"|getline){print $0;}close("/etc/passwd")}'
结果:
root:x:0:0:root:/root:/bin/bash
system:x:0:0::/home/system:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
................................................
逐行读取外部文件(getline使用方法)
例1:awk 'BEGIN{while(getline < "/etc/passwd"){print $0;};close("/etc/passwd")}'
结果:
root:x:0:0:root:/root:/bin/bash
system:x:0:0::/home/system:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
…………………………………………
例2:
awk 'BEGIN{print "Enter your name";getline name;print name}'
结果:
Enter your name
王健
王健
调用外部应用程序(system使用方法)
awk 'BEGIN{b=system("ls -al");print b;}'
结果:
total 102536
dr-xr-x---. 20 root root 4096 Jul 17 10:08 .
dr-xr-xr-x. 20 root root 267 Jul 14 22:44 ..
-rwx--x--x. 1 root root 1916 May 18 08:55 anaconda-ks.cfg
drwxr-xr-x. 2 root root 6 Jun 15 11:04 backup
...........
时间函数
建指定时间(mktime使用)
awk 'BEGIN{a=mktime("2017 07 17 13 28 04");print strftime("%c",a)}'
结果:
Mon 17 Jul 2017 01:28:04 PM CST
求2个时间段中间时间差
awk 'BEGIN{a=mktime("2017 07 17 13 20 00");b=mktime("2017 07 17 13 21 00");print b-a;}'
结果:
60
awk 'BEGIN{a=mktime("2017 07 17 13 48 00");b=systime();print b-a;}'
结果:
111
strftime日期和时间格式说明符: