SHELL三剑客之awk
目录:
awk介绍
awk是一个处理文本的编程语言工具,能用简短的程序处理输入文件、数据排序、计算以及生产报表等等。
在Linux系统下默认awk是gawk,它是awk的GNU版本。可以通过命令查看应用的版本:ls -l /bin/awk
基本的命令语法:awk option 'pattern {action}' file
- pattern表示AWK在数据中查找的内容,pattern参数可以是egrep正则表达式中的任何一个,使用/re/再加上一些样式匹配,匹配方式与sed类似,也可以使用","分开两样式以选个某个范围
- action是在找到匹配内容时所执行的一系列命令
- {}用于根据特定的模式对一系列指令进行分组
awk处理的工作方式与数据库类似,支持对记录和字段处理,这也是grep和sed不能实现的。在awk中,缺省的情况下将文本文件中的一行视为一个记录,而将一行中的某一部分作为记录中的一个字段。用1,2,3...数字的方式顺序的表示行(记录)中的不同字段。用$后跟数字,引用对应的字段,以逗号分隔,0表示整个行。
选项
选项 | 描述 |
---|---|
-f program-file | 从文件中读取 awk 程序源文件 |
-F fs | 为输入字段分隔符 |
-v var=value | 变量赋值 |
--posix | 只支持 POSIX 正则表达式 |
常用模式
模式 | 描述 | ||
---|---|---|---|
BEGIN{} | 给程序赋予初始状态,先执行的工作 | ||
END{} | 程序结束之后执行的一些扫尾工作 | ||
/regular expression/ | 为每个输入记录匹配正则表达式 | ||
pattern && pattern | 逻辑 and,满足两个模式 | ||
pattern | pattern | 逻辑 or,满足其中一个模式 | |
! pattern | 逻辑 not,不满足模式 | ||
pattern1, pattern2 | 范围模式,匹配所有模式1的记录,直到匹配到模式2 |
选项示例
示例操作文件
[root@why ~]# tail /etc/services
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
从文件读取 awk 程序处理文件
我们通过-f指定awk的程序来处理
[root@why ~]# tail -n3 /etc/services | awk -f test.awk
48556/udp
48619/tcp
48619/udp
[root@why ~]# cat test.awk
{print $2}
指定分隔符,打印指定字段
[root@why ~]# tail -n3 /etc/services | awk -F '/' -f test.awk
udp # com-bardac-dw
tcp # iqobject
udp # iqobject
指定多个分隔符,作为同一个分隔符处理
[root@why ~]# tail -n3 /etc/services | awk -F '[/#]' '{print $2}'
udp
tcp
udp
[root@why ~]# tail -n3 /etc/services | awk -F '[ /#]+' '{print $2}'
48556
48619
48619
[]元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了,超过两个就需要+来进行匹配
变量赋值
[root@why ~]# awk -v a=123 'BEGIN{print a}'
123
系统变量作为 awk 变量的值
[root@why ~]# a=123
[root@why ~]# awk -v a=$a 'BEGIN{print a}'
123
使用单引号引用系统变量
[root@why ~]# awk 'BEGIN{print '$a'}'
123
在SHELL脚本中使用变量
why='wanghongyu'
echo 1 | awk '{print w}' w="$why"
BEGIN和END
- BEGIN 模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标题
- END 模式是在程序处理完才会执行
打印页眉
[root@why ~]# tail /etc/services |awk 'BEGIN{print"Service\t\tPort\t\t\tDescription\n======================================================"}{print $0}'
Service Port Description
======================================================
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
打印页尾
[root@why ~]# tail /etc/services |awk '{print $0}END{print"=================================================\nEND......"}'
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
=================================================
END......
/re/正则匹配
匹配包含tcp的行
[root@why ~]# tail /etc/services |awk '/tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
匹配第一个字段是8个字符的行
[root@why ~]# tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'
[root@why ~]# tail /etc/services |awk --posix '/^[a-z0-9]{8} /{print $0}'
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
逻辑 and、or 和 not
匹配记录中包含blp5和tcp的行
[root@why ~]# tail /etc/services | awk '/tcp/ && /blp5/ {print $0}'
blp5 48129/tcp # Bloomberg locator
匹配记录中包含blp5或tcp的行
[root@why ~]# tail /etc/services | awk '/tcp/ || /blp5/ {print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
不匹配开头是#和空行
[root@why ~]# awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf
[root@why ~]# awk '! /^#|^$/' /etc/httpd/conf/httpd.conf
[root@why ~]# awk '/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf
匹配从blp5到iqobject的范围
[root@why ~]# tail /etc/services |awk '/^blp5/,/^iq/'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
内置变量
变量名 | 描述 |
---|---|
FS | 输入字段分隔符,默认是空格或制表符 |
OFS | 输出字段分隔符,默认是空格 |
RS | 输入记录分隔符,默认是换行符\n |
ORS | 输出记录分隔符,默认是换行符\n |
NF | 统计当前记录中字段个数 |
NR | 统计记录编号,每处理一行记录,编号就会+1 |
FNR | 统计记录编号,每处理一行记录,编号也会+1,与NR 不同的是,处理第二个文件时,编号会重新计数。 |
ARGC | 命令行参数数量 |
ARGIND | 当前正在处理的文件索引值。第一个文件是1,第二个文件是2,以此类推 |
ARGV | 命令行参数数组序列数组,下标从0开始,ARGV[0]是 awk |
ENVIRON | 当前系统的环境变量 |
FILENAME | 输出当前处理的文件名 |
IGNORECASE | 忽略大小写 |
SUBSEP | 数组中下标的分隔符,默认为"\034" |
FS和OFS
在程序开始前重新赋值FS变量,改变默认分隔符为冒号,与-F一样
[root@why ~]# awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5
root x
bin x
daemon x
adm x
lp x
使用-v来重新赋值FS变量
[root@why ~]# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5
root x
bin x
daemon x
adm x
lp x
OFS默认以空格分隔,指定分隔符
[root@why ~]# awk 'BEGIN{FS=":";OFS="++"} {print$1,$2}' /etc/passwd | head -n5
root++x
bin++x
daemon++x
adm++x
lp++x
通过字符串拼接实现分隔
[root@why ~]# awk 'BEGIN{FS=":"} {print$1"->"$2}' /etc/passwd | head -n5
root->x
bin->x
daemon->x
adm->x
lp->x
RS和ORS
RS默认以\n分割每行,可以指定某个字符作为分隔符来处理记录
[root@why ~]# echo 'www.whysdomain.com' | awk 'BEGIN{RS="."}{print $0}'
www
whysdomain
com
RS通过支持正则匹配分隔符
[root@why ~]# seq -f "str%02g" 10 |sed 'n;n;a\-----'
str01
str02
str03
-----
str04
str05
str06
-----
str07
str08
str09
-----
str10
[root@why ~]# seq -f "str%02g" 10 |sed 'n;n;a\-----' |awk 'BEGIN{RS="-+"}{print $1}'
str01
str04
str07
str10
将输出的换行符替换为+号
[root@why ~]# seq 10 |awk 'BEGIN{ORS="+"}{print $0}'
1+2+3+4+5+6+7+8+9+10+
通过awk的RS和ORS执行替换
[root@why ~]# tail -n2 /etc/services |awk 'BEGIN{RS="/";ORS="#"}{print $0}'
iqobject 48619#tcp # iqobject
iqobject 48619#udp # iqobject
#
注意会输出个#,所以还是要通过sed来替换好一些
NF
NF作为每行字段总数
[root@why ~]# echo "a b c d e f" |awk '{print NF}'
6
NF作为每行字段参数
[root@why ~]# echo "a b c d e f" |awk '{print $NF}'
f
[root@why ~]# echo "a b c d e f" |awk '{print $(NF-1)}'
e
删除后两个字段
[root@why ~]# echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print $0}'
a b c d
删除第一个字段
[root@why ~]# echo "a b c d e f" |awk '{$1="";print $0}'
b c d e f
NR和FNR
打印行数
[root@why ~]# tail -n5 /etc/services |awk '{print NR,$0}'
1 blp5 48129/udp # Bloomberg locator
2 com-bardac-dw 48556/tcp # com-bardac-dw
3 com-bardac-dw 48556/udp # com-bardac-dw
4 iqobject 48619/tcp # iqobject
5 iqobject 48619/udp # iqobject
打印总行数
[root@why ~]# tail -n5 /etc/services |awk 'END{print NR}'
5
打印第三行
[root@why ~]# tail -n5 /etc/services |awk 'NR==3'
com-bardac-dw 48556/udp # com-bardac-dw
打印前三行
[root@why ~]# tail -n5 /etc/services |awk 'NR<=3'
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
打印第三行第二个字段
[root@why ~]# tail -n5 /etc/services |awk 'NR==3{print $2}'
48556/udp
NR与FNR区别
[root@why ~]# cat a
a
b
c
[root@why ~]# cat b
c
b
a
[root@why ~]# awk '{print NR,FNR,$0}' a b
1 1 a
2 2 b
3 3 c
4 1 c
5 2 b
6 3 a
可以看出NR每处理一行就会+1,而FNR在处理第二个文件时,编号重新计数。同时也知道awk处理两个文件时,是合并到一起处理
判断处理的第一个还是第二个文件
[root@why ~]# awk 'FNR==NR{print $0"1"}FNR!=NR{print $0"2"}' a b
a1
b1
c1
c2
b2
a2
一般FNR在处理多个文件时会用到
ARGC和ARGV
ARGV 是将命令行参数存到数组,元素由ARGC指定,数组下标从0开始
[root@why ~]# awk 'BEGIN{print ARGC}' 1 2 3
4
[root@why ~]# awk 'BEGIN{print ARGV[0]}'
awk
[root@why ~]# awk 'BEGIN{print ARGV[1]}' 1 2
1
ARGIND
ARGIND是当前正在处理的文件索引值,第一个文件是 1,第二个文件是 2,以此类推
判断正在处理哪个文件
[root@why ~]# awk '{print ARGIND,$0}' a b
1 a
1 b
1 c
2 c
2 b
2 a
ENVIRON
ENVIRON调用系统变量,调用的是env命令中的系统变量,不过一般都用-v指定了。
调用HOSTNAME变量
[root@why ~]# awk 'BEGIN{print ENVIRON["HOSTNAME"]}'
why
如果是设置的环境变量,还需要用export导入到系统变量才可以调用
FILENAME
当前处理文件的文件名
[root@why ~]# awk 'NR==FNR{print FILENAME"->"$0}NR!=FNR{print FILENAME"->"$0}' a b
a->a
a->b
a->c
b->c
b->b
b->a
IGNORECASE=1
忽略大小写匹配
[root@why ~]# echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'
A
a
操作符
运算符 | 描述 | ||
---|---|---|---|
(....) | 分组 | ||
$ | 字段引用 | ||
++ -- | 递增和递减 | ||
+ - ! | 加号,减号,和逻辑否定 | ||
* / % | 乘,除和取余 | ||
+ - | 加法,减法 | ||
` | &` | 管道,getline,print 和 printf | |
< > <= >= != == | 关系运算符 | ||
~ !~ | 正则表达式匹配,否定正则表达式匹配 | ||
in | 数组成员 | ||
&& | 逻辑 and,逻辑 or | ||
?: | 简写条件表达式:expr1?expr2:expr3第一个表达式为真,执行 expr2,否则执行 expr3 | ||
= += -= *= /= %= ^= | 变量赋值运算符 |
截取整数(只匹配开头的整数)
[root@why ~]# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print +$0}'
123
0
123
[root@why ~]# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print -$0}'
-123
0
-123
不匹配某行(!)
[root@why ~]# tail /etc/services |awk '!/blp5/{print $0}'
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
加减乘除
[root@why ~]# seq 5 |awk '{print $0+2}'
3
4
5
6
7
[root@why ~]# seq 5 |awk '{print $0-2}'
-1
0
1
2
3
[root@why ~]# seq 5 |awk '{print $0*2}'
2
4
6
8
10
[root@why ~]# seq 5 |awk '{print $0%2}'
1
0
1
0
1
[root@why ~]# seq 5 |awk '{print $0/2}'
0.5
1
1.5
2
2.5
管道符使用(|)
[root@why ~]# seq 5 |shuf
3
2
1
5
4
[root@why ~]# seq 5 |shuf |awk '{print $0|"sort"}'
1
2
3
4
5
正则表达式匹配(~)
[root@why ~]# seq 5 |awk '$0~3{print $0}'
3
[root@why ~]# seq 5 |awk '$0!~3{print $0}'
1
2
4
5
[root@why ~]# seq 5 |awk '$0~/[34]/{print $0}'
3
4
[root@why ~]# seq 5 |awk '$0!~/[34]/{print $0}'
1
2
5
[root@why ~]# seq 5 |awk '$0~/[^34]/{print $0}'
1
2
5
判断数组成员
[root@why ~]# awk 'BEGIN{a["b"]=123}END{if("b" in a)print "yes"}' </dev/null
yes
三目运算符
三目运算作为一个表达式,里面不允许写print
[root@why ~]# awk 'BEGIN{print 1==1?"yes":"no"}'
yes
[root@why ~]# seq 3 |awk '{print $0==2?"yes":"no"}'
no
yes
no
替换换行符为逗号
[root@why ~]# seq 5 | awk '{print n=(n?n","$0:$0)}'
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
[root@why ~]# seq 5 | awk '{n=(n?n","$0:$0)}END{print n}'
1,2,3,4,5
读取第一行时,n没有变量,为假输出$0也就是1,并赋值变量n,读取第二行时,n是1为 真,输出1,2以此类推,后面会一直为真 在awk中有三种情况认为是错误的,为0,未定义的变量和空
[root@why ~]# awk 'BEGIN{n=0;if(n)print "true";else print "false"}'
false
[root@why ~]# awk 'BEGIN{s="";if(s)print "true";else print "false"}'
false
[root@why ~]# awk 'BEGIN{if(s)print "true";else print "false"}'
false
字段求和
[root@why ~]# seq 5 |awk '{sum++}END{print sum}'
5
[root@why ~]# seq 5 |awk '{sum+=1}END{print sum}'
5
[root@why ~]# seq 5 |awk '{sum+=$0}END{print sum}'
15
awk流程控制
if
格式:if (condition) statement [ else statement ]
单分支
[root@why ~]# seq 5 | awk '{if($0==4) print $0}'
4
双分支
[root@why ~]# seq 5 | awk '{if($0==4) print $0;else print "no"}'
no
no
no
4
no
多分支
[root@why ~]# awk '{if($1==4){print "1"} else if($2==5){print "2"} else if($3==6){print "3"} else {print "no"}}' c
no
1
no
[root@why ~]# cat c
1 2 3
4 5 6
7 8 9
where语句
格式:while (condition) statement
遍历打印所有字段
[root@why ~]# awk '{i=1;while(i<=NF){print $i;i++}}' c
1
2
3
4
5
6
7
8
9
for语句(C语言风格)
格式:for (expr1; expr2; expr3) statement
遍历打印所有字段
[root@why ~]# awk '{for(i=1;i<=NF;i++)print $i}' c
1
2
3
4
5
6
7
8
9
倒叙打印文本
[root@why ~]# awk '{for(i=NF;i>=1;i--)print $i}' c
3
2
1
6
5
4
9
8
7
不换行输出(printf)
[root@why ~]# awk '{for(i=NF;i>=1;i--){printf $i" "};print ""}' c
3 2 1
6 5 4
9 8 7
[root@why ~]# awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' c
3 2 1
6 5 4
9 8 7
不打印第一行
[root@why ~]# awk '{for(i=2;i<=NF;i++){printf $i" "};print ""}' c
2 3
5 6
8 9
IP加单引号
[root@why ~]# echo '10.10.10.1 10.10.10.2 10.10.10.3' |awk '{for(i=1;i<=NF;i++)printf "\047"$i"\047"}'
'10.10.10.1''10.10.10.2''10.10.10.3'
\047是ASCII码,可以通过showkey -a命令查看
for语句遍历数组
格式:for (var in array) statement
[root@why ~]# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2
3 str3
break和continue
break跳过所有循环,continue跳过当前循环
[root@why ~]# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}'
1
2
[root@why ~]# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}'
1
2
4
5
删除数组和元素
格式: delete array[index] 删除数组元素 delete array 删除数组
[root@why ~]# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a;for(v in a)print v,a[v]}'
[root@why ~]# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a[3];for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2
exit语句
格式:exit [ expression ] exit退出程序,与shell的exit一样。[expr]是0-255之间的数字
[root@why ~]# seq 5 |awk '{if($0~/3/)exit (123)}'
[root@why ~]# echo $?
123
数组
数组是用来存储一系列值的变量,通过下标(索引)来访问值。 awk 中数组称为关联数组,因为它的下标可以是数字也可以是字符串。 数组元素的键和值存储在 awk 程序内部的一个表中,该表采用散列算法,因此数组元素是随机排序。 数组格式:array[index]=value
自定义数组
[root@why ~]# awk 'BEGIN{a[0]="why";a[1]="whyy";print a[0]}'
why
通过NR设置记录下标,下标从1开始
[root@why ~]# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}'
apache
[root@why ~]# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[2]}'
mysql
[root@why ~]# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[3]}'
why
通过for循环遍历数组
[root@why ~]# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(k in a)print a[k],k}'
mysql 4
why 5
tcpdump 1
nscd 2
apache 3
[root@why ~]# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(k=1;k<=NR;k++)print a[k],k}'
tcpdump 1
nscd 2
apache 3
mysql 4
why 5
上面打印的 i 是数组的下标。 第一种 for 循环的结果是乱序的,刚说过,数组是无序存储。 第二种 for 循环通过下标获取的情况是排序正常。 所以当下标是数字序列时,还是用 for(expr1;expr2;expr3)循环表达式比较好,保持顺序不变。
通过++方式作为下标
[root@why ~]# tail -n5 /etc/passwd |awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print a[i],i}'
tcpdump 0
nscd 1
apache 2
mysql 3
why 4
x被awk初始化值是0,每循环一次+1
使用字段作为下标
[root@why ~]# tail -n5 /etc/passwd |awk -F: '{a[$1]=$7}END{for(v in a)print a[v],v}'
/sbin/nologin apache
/bin/bash mysql
/bin/bash why
/sbin/nologin tcpdump
/sbin/nologin nscd
统计相同字段出现次数
[root@why ~]# tail /etc/services |awk '{a[$1]++}END{for(v in a)print a[v],v}'
2 iqobject
2 com-bardac-dw
2 blp5
2 isnetserv
1 3gpp-cbsp
1 nimgtw
只打印出现次数大于等于 2 的
[root@why ~]# tail /etc/services |awk '{a[$1]++}END{for(v in a) if(a[v]>=2){print a[v],v}}'
2 iqobject
2 com-bardac-dw
2 blp5
2 isnetserv
统计 TCP 连接状态
[root@why ~]# netstat -antp |awk '/^tcp/{a[$6]++}END{for(v in a)print a[v],v}'
2 ESTABLISHED
5 LISTEN
打印重复行
[root@why ~]# tail /etc/services |awk 'a[$1]++'
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/udp # iqobject
只打印重复的行说明:先明白一个情况,当值是0是为假,1为真,知道这点就不难理解了。由于执行了++当处理第一条记录时,初始值是0为假,就不打印,如果再遇到相同的记录,值就会+1,不为0,打印。
[root@why ~]# tail /etc/services |awk '{if(a[$1]++)print $1}'
isnetserv
blp5
com-bardac-dw
iqobject
去重
[root@why ~]# tail /etc/services |awk '!a[$1]++'
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
去重说明:初始值是0为假,感叹号取反为真,打印,也就是说,每个记录的第一个值都是为0,所以都会打印,如果再遇到相同的记录+1,值就会为真,取反为假就不打印。
[root@why ~]# tail /etc/services |awk '{if(!a[$1]++)print $1}'
nimgtw
3gpp-cbsp
isnetserv
blp5
com-bardac-dw
iqobject
多维数组
awk的多维数组,实际上 awk 并不支持多维数组,而是逻辑上模拟二维数组的访问方式,比如a[a,b]=1,使用SUBSEP(默认\034)作为分隔下标字段,存储后是这样a\034b
[root@why ~]# awk 'BEGIN{a["x","y"]=123;for(v in a) print v,a[v]}'
xy 123
我们可以重新赋值SUBSEP变量,改变下标默认分隔符
[root@why ~]# awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a) print v,a[v]}'
x:y 123
根据指定的字段统计出现次数
[root@why ~]# awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(v in a)print a[v],v}' d
2 B-192.168.1.2
1 D-192.168.1.4
2 C-192.168.1.1
1 A-192.168.1.1
[root@why ~]# cat d
A 192.168.1.1 HTTP
B 192.168.1.2 HTTP
B 192.168.1.2 MYSQL
C 192.168.1.1 MYSQL
C 192.168.1.1 MQ
D 192.168.1.4 NGINX
内置函数
函数 | 描述 |
---|---|
int(expr) | 截断为整数 |
sqrt(expr) | 平方根 |
rand() | 返回一个随机数N,0和1范围,0 |
srand([expr]) | 使用expr生成随机数,如果不指定,默认使用当前时间为种子,如果前面有种子则使用生成随机数 |
asort(a, b) | 对数组a的值进行排序,把排序后的值存到新的数组b中,新排序的数组下标从1开始 |
asorti(a,b) | 对数组a的下标进行排序,同上 |
sub(r, s [, t]) | 对输入的记录用s替换r,t可选针对某字段替换,但只替换第一个字符串 |
gsub(r,s [, t]) | 对输入的记录用 s 替换 r,t 可选针对某字段替换,替换所有字符串 |
index(s, t) | 返回 s 中字符串 t 的索引位置,0 为不存在 |
length([s]) | 返回 s 的长度 |
match(s, r [, a]) | 测试字符串 s 是否包含匹配 r 的字符串 |
split(s, a [, r [,seps] ]) | 根据分隔符 seps 将 s 分成数组 a |
substr(s, i [, n]) | 截取字符串 s 从 i 开始到长度 n,如果 n 没指定则是剩余部分 |
tolower(str) | str 中的所有大写转换成小写 |
toupper(str) | str 中的所有小写转换成大写 |
systime() | 当前时间戳 |
strftime([format [,timestamp[, utc-flag]]]) | 格式化输出时间,将时间戳转为字符串 |
int()
[root@why ~]# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print int($0)}'
123
0
123
[root@why ~]# awk 'BEGIN{print int(10/3)}'
3
[root@why ~]# awk 'BEGIN{print 10/3}'
3.33333
sqrt()
获取9的平方根
[root@why ~]# awk 'BEGIN{print sqrt(9)}'
3
rand()和srand()
rand()并不是每次运行就是一个随机数,会一直保持一个不变
[root@why ~]# awk 'BEGIN{print rand()}'
0.237788
[root@why ~]# awk 'BEGIN{print rand()}'
0.237788
当执行 srand()函数后,rand()才会发生变化,所以一般在 awk 着两个函数结合生成随机数,但是也有很大几率生成一样
[root@why ~]# awk 'BEGIN{srand();print rand()}'
0.720131
生成1-10的随机数
[root@why ~]# awk 'BEGIN{srand();print int(rand()*10)}'
4
[root@why ~]# awk 'BEGIN{srand();print int(rand()*10)}'
1
asort()和 asorti()
[root@why ~]# seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asort(a,b);for(i=1;i<=s;i++)print b[i],i}'
str1 1
str2 2
str3 3
str4 4
str5 5
[root@why ~]# seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asorti(a,b);for(i=1;i<=s;i++)print b[i],i}'
0 1
1 2
2 3
3 4
4 5
asort 将 a 数组的值放到数组 b,a 下标丢弃,并将数组 b 的总行号赋值给 s,新数组 b 下标从 1 开 始,然后遍历
替换sub()和 gsub()
[root@why ~]# tail /etc/services |awk '/blp5/{sub(/tcp/,"icmp");print $0}'
blp5 48129/icmp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
[root@why ~]# tail /etc/services |awk '/blp5/{gsub(/c/,"9");print $0}'
blp5 48129/t9p # Bloomberg lo9ator
blp5 48129/udp # Bloomberg lo9ator
[root@why ~]# echo "1 2 2 3 4 5" |awk 'gsub(2,7,$2){print $0}'
1 7 2 3 4 5
[root@why ~]# echo "1 2 3 a b c" |awk 'gsub(/[0-9]/, '0'){print $0}'
0 0 0 a b c
在指定行前后加一行
[root@why ~]# seq 5 | awk 'NR==2{sub('/.*/',"txt\n&")}{print}'
1
txt
2
3
4
5
[root@why ~]# seq 5 | awk 'NR==2{sub('/.*/',"&\ntxt")}{print}'
1
2
txt
3
4
5
字符串索引index()
[root@why ~]# tail -n 5 /etc/services |awk '{print index($2,"tcp")}'
0
7
0
7
0
[root@why ~]# tail -n 5 /etc/services |awk '{print $2}'
48129/udp
48556/tcp
48556/udp
48619/tcp
48619/udp
返回长度length()
[root@why ~]# tail -n 5 /etc/services |awk '{print length($1)}'
4
13
13
8
8
[root@why ~]# tail -n 5 /etc/services |awk '{print $1}'
blp5
com-bardac-dw
com-bardac-dw
iqobject
iqobject
统计数组的长度
[root@why ~]# tail -n 5 /etc/services |awk '{a[$1]=$2}END{print length(a)}'
3
按分割符分割数组split()
[root@why ~]# echo -e "123#456#789\nabc#cde#fgh" |awk '{split($0,a);for(v in a)print a[v],v}'
123#456#789 1
abc#cde#fgh 1
[root@why ~]# echo -e "123#456#789\nabc#cde#fgh" |awk '{split($0,a,"#");for(v in a)print a[v],v}'
123 1
456 2
789 3
abc 1
cde 2
fgh 3
substr()
[root@why ~]# echo -e "123#456#789\nabc#cde#fgh" |awk '{print substr($0,4)}'
#456#789
#cde#fgh
大写转换成小写tolower()和 toupper()
[root@why ~]# echo -e "123#456#789\nABC#cde#fgh" |awk '{print tolower($0)}'
123#456#789
abc#cde#fgh
[root@why ~]# echo -e "123#456#789\nabc#cde#fgh" |awk '{print toupper($0)}'
123#456#789
ABC#CDE#FGH
时间处理
返回当前时间戳systime()和strftime()
[root@why ~]# awk 'BEGIN{print systime()}'
1483980688
将时间戳转为日期和时间
[root@why ~]# echo "1483297766" |awk '{print strftime("%Y-%m-%d %H:%M:%S",$0)}'
2017-01-02 03:09:26
I/O语句
语句 | 描述 | |
---|---|---|
getline | 设置$0 来自下一个输入记录 | |
getline var | 设置 var 来自下一个输入记录 | |
command | getline [var] 运行命令管道输出到$0 或 var | |
next | 停止当前处理的输入记录 | |
打印当前记录 | ||
printf fmt, expr-list | 格式化输出 | |
printf fmt, expr-list >file | 格式输出和写到文件 | |
system(cmd-line) | 执行命令和返回状态 | |
print ... >> file | 追加输出到文件 | |
`print ... | command` | 打印输出作为命令输入 |
getline
获取匹配的下一行
[root@why ~]# seq 5 |awk '/3/{getline;print}'
4
[root@why ~]# seq 5 |awk '/3/{print;getline;print}'
3
4
在匹配的下一行加个星号:
[root@why ~]# seq 5 |awk '/3/{getline;sub(".*","&*");print}'
4*
[root@why ~]# seq 5 |awk '/3/{print;getline;sub(".*","&*")}{print}'
1
2
3
4*
5
next
不打印匹配行
[root@why ~]# seq 5 |awk '{if($0==3){next}else{print}}'
1
2
4
5
删除指定行:
[root@why ~]# seq 5 |awk 'NR==1{next}{print $0}'
2
3
4
5
如果前面动作成功,就遇到 next,后面的动作不再执行,跳过。
system()
执行 shell 命令判断返回值:
[root@why ~]# awk 'BEGIN{if(system("grep root /etc/passwd &>/dev/null")==0)print "yes";else print "no"}'
yes
打印结果写到文件
[root@why ~]# tail -n5 /etc/services |awk '{print $2 > "a.txt"}'
[root@why ~]# cat a.txt
48049/tcp
48128/tcp
48128/udp
48129/tcp
48129/udp
管道连接 shell 命令
将结果通过 grep 命令过滤:
[root@why ~]# tail -n5 /etc/services |awk '{print $2|"grep tcp"}'
48556/tcp
48619/tcp
49000/tcp
printf
格式化输出,默认打印字符串不换行。 格式:printf [format] arguments Format | 描述 ---|--- %.ns | 输出字符串,n 是输出几个字符 %ni | 输出整数,n 是输出几个数字 %m.nf | 输出浮点数,m 是输出整数位数,n 是输出的小数位数 %x | 不带正负号的十六进制,使用 a 至 f 表示 10 到 15 %X | 不带正负号的十六进制,使用 A 至 F 表示 10 至 15 %% | 输出单个% %-5s | 左对齐,对参数每个字段左对齐,宽度为 5 %-4.2f | 左对齐,宽度为 4,保留两位小数 %5s | 右对齐,不加横线表示右对齐
输出一个字符
[root@why ~]# awk 'BEGIN{printf "%.1s\n","abc"}'
a
保留两个小数点:
[root@why ~]# awk 'BEGIN{printf "%.2f\n",10/3}'
3.33
格式化输出
[root@why ~]# awk 'BEGIN{printf "user:%s\tpass:%d\n","abc",123}'
user:abc pass:123
左对齐宽度10
[root@why ~]# awk 'BEGIN{printf "%-10s %-10s %-10s\n","ID","Name","Passwd"}'
ID Name Passwd
右对齐宽度10
[root@why ~]# awk 'BEGIN{printf "%10s %10s %10s\n","ID","Name","Passwd"}'
ID Name Passwd
格式化输出
[root@why ~]# awk -F: 'BEGIN{printf "UserName\t\tShell\n-----------------------------\n"}{printf"%-20s %-20s\n",$1,$7}END{print "END...\n"}' /etc/passwd
UserName Shell
-----------------------------
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
sync /bin/sync
shutdown /sbin/shutdown
halt /sbin/halt
打印十六进制
[root@why ~]# awk 'BEGIN{printf "%x %X",123,123}'
7b 7B
函数
function name(parameter list) { statements }
示例
[root@why ~]# awk 'function myfunc(a,b){return a+b}BEGIN{print myfunc(1,2)}'
3