Linux下Shell文本处理三剑客--awk

文章目录

      • awk
        • 1、选项
        • 2、模式
        • 3、内置变量
        • 4、操作符
        • 5、流程控制
        • 6、数组
        • 7、内置函数
        • 8、I/O语句
        • 9、printf语句
        • 10、自定义函数
        • 11、需求案例

awk

awk是一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等等。

在Linux系统下默认awk是gawk,它是awk的GNU版本。可以通过命令查看应用的版本:ls -l /bin/awk

基本的命令语法:awk option ‘pattern {action}’ file

其中pattern表示AWK在数据中查找的内容,而action是在找到匹配内容时所执行的一系列命令。花括号用于根据特定的模式对一系列指令进行分组。

awk处理的工作方式与数据库类似,支持对记录和字段处理,这也是grep和sed不能实现的。

在awk中,缺省的情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,而将一行中的某一部分作为记录中的一个字段。用1,2,3…数字的方式顺序的表示行(记录)中的不同字段。用$后跟数字,引用对应的字段,以逗号分隔,0表示整个行。

1、选项

选项 描述
-f program-file 从文件中读取awk程序源文件
-F fs 指定fs为输入字段分隔符
-v var=value 变量赋值
–posix 兼容POSIX正则表达式
–dump-variables=[file] 把awk命令时的全局变量写入文件,默认文件是awkvars.out
–profile=[file] 格式化awk语句到文件,默认是awkprof.out

2、模式

常用模式有:

Pattern Description
BEGIN{ } 给程序赋予初始状态,先执行的工作
END{ } 程序结束之后执行的一些扫尾工作
/regular expression/ 为每个输入记录匹配正则表达式
pattern && pattern 逻辑and,满足两个模式
pattern || pattern 逻辑or,满足其中一个模式
! pattern 逻辑not,不满足模式
pattern1, pattern2 范围模式,匹配所有模式1的记录,直到匹配到模式2

而动作呢,就是下面所讲的print、流程控制、I/O语句等。

示例:

1)从文件读取awk程序处理文件

[root@ chenc01 ~]# vi test.awk
{print $2}

[root@ chenc01 ~]# tail -n3 /etc/services |awk -f test.awk
48049/tcp
48128/tcp
49000/tcp

2)指定分隔符,打印指定字段 

打印第二字段,默认以空格分隔:

[root@ chenc01 ~]# tail -n3 /etc/services |awk '{print $2}'
48049/tcp
48128/tcp
48128/udp

指定冒号为分隔符打印第一字段:

[root@ chenc01 ~]# awk -F ':' '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
......

还可以指定多个分隔符,作为同一个分隔符处理:

[root@ chenc01 ~]# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'  
 iqobject
 iqobject
 Matahari Broker

[root@ chenc01 ~]# tail -n3 /etc/services |awk -F'[/#]' '{print $1}'
iqobject    48619
iqobject    48619
matahari    49000

[root@ chenc01 ~]# tail -n3 /etc/services |awk -F'[/#]' '{print $2}'
tcp        
udp        
tcp        

[root@ chenc01 ~]# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
 iqobject
 iqobject
 Matahari Broker

[root@ chenc01 ~]# tail -n3 /etc/services |awk -F'[ /]+' '{print $2}'
48619
48619
49000

[]元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了。

3)变量赋值

[root@ chenc01 ~]# awk -v a=123 'BEGIN{print a}'  
123

系统变量作为awk变量的值:

[root@ chenc01 ~]# a=123
[root@ chenc01 ~]# awk -v a=$a 'BEGIN{print a}'  
123
或使用单引号
[root@ chenc01 ~]# awk 'BEGIN{print '$a'}'  
123

4)输出awk全局变量到文件
[root@ chenc01 ~]# seq 5 |awk --dump-variables '{print $0}'
1
2
3
4
5

[root@ chenc01 ~]# cat awkvars.out             
ARGC: number (1)
ARGIND: number (0)
ARGV: array, 1 elements
BINMODE: number (0)
CONVFMT: string ("%.6g")
ERRNO: number (0)
FIELDWIDTHS: string ("")
FILENAME: string ("-")
FNR: number (5)
FS: string (" ")
IGNORECASE: number (0)
LINT: number (0)
NF: number (1)
NR: number (5)
OFMT: string ("%.6g")
OFS: string (" ")
ORS: string ("\n")
RLENGTH: number (0)
RS: string ("\n")
RSTART: number (0)
RT: string ("\n")
SUBSEP: string ("\034")
TEXTDOMAIN: string ("messages")

5)BEGIN和END

BEGIN模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标题。

例如:打印页眉

[root@ chenc01 ~]# tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print $0}'
Service     Port          Description
===
3gpp-cbsp    48049/tcp           # 3GPP Cell Broadcast Service 
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
matahari    49000/tcp            # Matahari Broker

END模式是在程序处理完才会执行。

例如:打印页尾

[root@ chenc01 ~]# tail /etc/services |awk '{print $0}END{print "===\nEND......"}'
3gpp-cbsp    48049/tcp           # 3GPP Cell Broadcast Service 
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
matahari    49000/tcp            # Matahari Broker
===
END......

6)格式化输出awk命令到文件

[root@ chenc01 ~]# tail /etc/services |awk --profile 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print $0}END{print "===\nEND......"}'
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
===
END......

[root@ chenc01 ~]# cat awkprof.out 
[root@ chenc01 ~]# gawk profile, created Sat Jan  7 19:45:22 2017
     # BEGIN block(s)
     BEGIN {
         print "Service\t\tPort\t\t\tDescription\n==="
     }
     # Rule(s)
     {
         print $0
     }

     # END block(s)
     END {
         print "===\nEND......"
     }

7)/re/正则匹配

匹配包含tcp的行:

[root@ chenc01 ~]# tail /etc/services |awk '/tcp/{print $0}'  
3gpp-cbsp    48049/tcp           # 3GPP Cell Broadcast Service 
isnetserv    48128/tcp           # Image Systems Network Services
blp5      48129/tcp              # Bloomberg locator
com-bardac-dw   48556/tcp        # com-bardac-dw
iqobject    48619/tcp            # iqobject
matahari    49000/tcp            # Matahari Broker
匹配开头是blp5的行:

[root@ chenc01 ~]# tail /etc/services |awk '/^blp5/{print $0}'  
blp5      48129/tcp             # Bloomberg locator
blp5      48129/udp             # Bloomberg locator
匹配第一个字段是8个字符的行:

[root@ chenc01 ~]# tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'
iqobject    48619/tcp           # iqobject
iqobject    48619/udp           # iqobject
matahari    49000/tcp           # Matahari Broker

如果没有匹配到,请查看你的awk版本(awk --version)是不是3,因为4才支持{}

8)逻辑and、or和not

匹配记录中包含blp5和tcp的行:

[root@ chenc01 ~]# tail /etc/services |awk '/blp5/ && /tcp/{print $0}'    
blp5      48129/tcp             # Bloomberg locator
匹配记录中包含blp5或tcp的行:

[root@ chenc01 ~]# tail /etc/services |awk '/blp5/ || /tcp/{print $0}'    
3gpp-cbsp    48049/tcp          # 3GPP Cell Broadcast Service 
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
matahari    49000/tcp           # Matahari Broker
不匹配开头是#和空行:

[root@ chenc01 ~]# awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf[root@ chenc01 ~]# awk '! /^#|^$/' /etc/httpd/conf/httpd.conf  [root@ chenc01 ~]# awk '/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf

9)匹配范围
[root@ chenc01 ~]# tail /etc/services |awk '/^blp5/,/^com/'
blp5      48129/tcp               # Bloomberg locator
blp5      48129/udp               # Bloomberg locator
com-bardac-dw    48556/tcp        # com-bardac-dw
对匹配范围后记录再次处理,例如匹配关键字下一行到最后一行:

[root@ chenc01 ~]# seq 5 |awk '/3/,/^$/{printf /3/?"":$0"\n"}' 
4
5

另一种判断真假的方式实现:

[root@ chenc01 ~]# seq 5 |awk '/3/{t=1;next}t'
4
5

1和2都不匹配3,不执行后面{},执行t,t变量还没赋值,为空,空在awk中就为假,就不打印当前行。匹配到3,执行t=1,next跳出,不执行t。4也不匹配3,执行t,t的值上次赋值的1,为真,打印当前行,以此类推。(非0的数字都为真,所以t可以写任意非0数字)

如果想打印匹配行都最后一行,就可以这样了:

[root@ chenc01 ~]# seq 5 |awk '/3/{t=1}t'   
3
4
5

3、内置变量

变量名 描述
FS 输入字段分隔符,默认是空格或制表符
OFS 输出字段分隔符,默认是空格
RS 输入记录分隔符,默认是换行符\n
ORS 输出记录分隔符,默认是换行符\n
NF 统计当前记录中字段个数
NR 统计记录编号,每处理一行记录,编号就会+1
FNR 统计记录编号,每处理一行记录,编号也会+1,与NR不同的是,处理第二个文件时,编号会重新计数。
ARGC 命令行参数数量
ARGV 命令行参数数组序列数组,下标从0开始,ARGV[0]是awk
ARGIND 当前正在处理的文件索引值。第一个文件是1,第二个文件是2,以此类推
ENVIRON 当前系统的环境变量
FILENAME 输出当前处理的文件名
IGNORECASE 忽略大小写
SUBSEP 数组中下标的分隔符,默认为"\034"
示例:

1)FS和OFS

在程序开始前重新赋值FS变量,改变默认分隔符为冒号,与-F一样。

[root@ chenc01 ~]# awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5      
root x
bin x
daemon x
adm x
lp x

也可以使用-v来重新赋值这个变量:

[root@ chenc01 ~]# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5    # 中间逗号被换成了OFS的默认值     
root x
bin x
daemon x
adm x
lp x

由于OFS默认以空格分隔,反向引用多个字段分隔的也是空格,如果想指定输出分隔符这样:

[root@ chenc01 ~]# awk 'BEGIN{FS=":";OFS=":"}{print $1,$2}' /etc/passwd |head -n5
root:x
bin:x
daemon:x
adm:x
lp:x

也可以通过字符串拼接实现分隔:

[root@ chenc01 ~]# awk 'BEGIN{FS=":"}{print $1"#"$2}' /etc/passwd |head -n5
root#x
bin#x
daemon#x
adm#x
lp#x

2)RS和ORS

RS默认是\n分隔每行,如果想指定以某个字符作为分隔符来处理记录:

[root@ chenc01 ~]# echo "www.baidu.com/user/test.html" |awk 'BEGIN{RS="/"}{print $0}'
www.baidu.com
user
test.html

RS也支持正则,简单演示下:

[root@ chenc01 ~]# seq -f "str%02g" 10 |sed 'n;n;a\-----' |awk 'BEGIN{RS="-+"}{print $1}'
str01
str04
str07
str10

将输出的换行符替换为+号:
[root@ chenc01 ~]# seq 10 |awk 'BEGIN{ORS="+"}{print $0}'
1+2+3+4+5+6+7+8+9+10+

替换某个字符:

[root@ chenc01 ~]# tail -n2 /etc/services |awk 'BEGIN{RS="/";ORS="#"}{print $0}'
iqobject    48619#udp        # iqobject
matahari    49000#tcp        # Matahari Broker

3)NF

NF是字段个数。

[root@ chenc01 ~]# echo "a b c d e f" |awk '{print NF}'
6

打印最后一个字段:
[root@ chenc01 ~]# echo "a b c d e f" |awk '{print $NF}'
f

打印倒数第二个字段:
[root@ chenc01 ~]# echo "a b c d e f" |awk '{print $(NF-1)}'
e

排除最后两个字段:
[root@ chenc01 ~]# echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print $0}'
a b c d

排除第一个字段:
[root@ chenc01 ~]# echo "a b c d e f" |awk '{$1="";print $0}'
 b c d e f 

4)NR和FNR

NR统计记录编号,每处理一行记录,编号就会+1,FNR不同的是在统计第二个文件时会重新计数。

打印行数:

[root@ chenc01 ~]# tail -n5 /etc/services |awk '{print NR,$0}'
1 com-bardac-dw   48556/tcp        # com-bardac-dw
2 com-bardac-dw   48556/udp        # com-bardac-dw
3 iqobject     48619/tcp           # iqobject
4 iqobject     48619/udp           # iqobject
5 matahari     49000/tcp           # Matahari Broker
打印总行数:

[root@ chenc01 ~]# tail -n5 /etc/services |awk 'END{print NR}'
5

打印第三行:
[root@ chenc01 ~]# tail -n5 /etc/services |awk 'NR==3'    
iqobject     48619/tcp        # iqobject

打印第三行第二个字段:
[root@ chenc01 ~]# tail -n5 /etc/services |awk 'NR==3{print $2}'
48619/tcp

打印前三行:
[root@ chenc01 ~]# tail -n5 /etc/services |awk 'NR<=3{print NR,$0}'
1 com-bardac-dw  48556/tcp        # com-bardac-dw
2 com-bardac-dw  48556/udp        # com-bardac-dw
3 iqobject    48619/tcp           # iqobject

看下NR和FNR的区别:

[root@ chenc01 ~]# cat a
a
b
c
[root@ chenc01 ~]# cat b
c
d
e

[root@ chenc01 ~]# awk '{print NR,FNR,$0}' a b
1 1 a
2 2 b
3 3 c
4 1 c
5 2 d
6 3 e

可以看出NR每处理一行就会+1,而FNR在处理第二个文件时,编号重新计数。同时也知道awk处理两个文件时,是合并到一起处理。

[root@ chenc01 ~]# awk 'FNR==NR{print $0"1"}FNR!=NR{print $0"2"}' a b 
a1
b1
c1
c2
d2
e2

当FNR==NR时,说明在处理第一个文件内容,不等于时说明在处理第二个文件内容。

一般FNR在处理多个文件时会用到,下面会讲解。

5)ARGC和ARGV
ARGC是命令行参数数量
ARGV是将命令行参数存到数组,元素由ARGC指定,数组下标从0开始

[root@ chenc01 ~]# awk 'BEGIN{print ARGC}' 1 2 3
4

[root@ chenc01 ~]# awk 'BEGIN{print ARGV[0]}'
awk

[root@ chenc01 ~]# awk 'BEGIN{print ARGV[1]}' 1 2
1

[root@ chenc01 ~]# awk 'BEGIN{print ARGV[2]}' 1 2 
2

6)ARGIND

ARGIND是当前正在处理的文件索引值,第一个文件是1,第二个文件是2,以此类推,从而可以通过这种方式判断正在处理哪个文件。

[root@ chenc01 ~]# awk '{print ARGIND,$0}' a b
1 a
1 b
1 c
2 c
2 d
2 e

[root@ chenc01 ~]# awk 'ARGIND==1{print "a->"$0}ARGIND==2{print "b->"$0}' a b    
a->a
a->b
a->c
b->c
b->d
b->e

7)ENVIRON

ENVIRON调用系统变量。

[root@ chenc01 ~]# awk 'BEGIN{print ENVIRON["HOME"]}'
/root

如果是设置的环境变量,还需要用export导入到系统变量才可以调用:

[root@ chenc01 ~]# awk 'BEGIN{print ENVIRON["a"]}'

[root@ chenc01 ~]# export a

[root@ chenc01 ~]# awk 'BEGIN{print ENVIRON["a"]}'
123

8)FILENAME

FILENAME是当前处理文件的文件名。

[root@ chenc01 ~]# awk 'FNR==NR{print FILENAME"->"$0}FNR!=NR{print FILENAME"->"$0}' a b   
a->a
a->b
a->c
b->c
b->d
b->e

9)忽略大小写
[root@ chenc01 ~]# echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'
A
a

等于1代表忽略大小写。

4、操作符

运算符 描述
(…) 分组
$ 字段引用
++ – 递增和递减
+ - ! 加号,减号,和逻辑否定
* / % 乘,除和取余
+ - 加法,减法
| |& 管道,用于getline,print和printf
< > <= >= != == 关系运算符
~ !~ 正则表达式匹配,否定正则表达式匹配
in 数组成员
&& || 逻辑and,逻辑or
?: 简写条件表达式:expr1 ? expr2 : expr3第一个表达式为真,执行expr2,否则执行expr3
= += -= *= /= %= ^= 变量赋值运算符

须知:*

在awk中,有3种情况表达式为假:数字是0,空字符串和未定义的值。

数值运算,未定义变量初始值为0。字符运算,未定义变量初始值为空。

举例测试:

[root@ chenc01 ~]# awk 'BEGIN{n=0;if(n)print "true";else print "false"}'
false

[root@ chenc01 ~]# awk 'BEGIN{s="";if(s)print "true";else print "false"}'
false

[root@ chenc01 ~]# awk 'BEGIN{if(s)print "true";else print "false"}'
false

示例:

1)截取整数

[root@ chenc01 ~]# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print +$0}'
123
0
123

[root@ chenc01 ~]# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print -$0}'
-123
0
-123

2)感叹号

打印奇数行:

[root@ chenc01 ~]# seq 6 |awk 'i=!i'
1
3
5

打印偶数行:

[root@ chenc01 ~]# seq 6 |awk '!(i=!i)'  
2
4
6

读取第一行:i是未定义变量,也就是i=!0,!取反意思。感叹号右边是个布尔值,0或空字符串为假,非0或非空字符串为真,!0就是真,因此i=1,条件为真打印当前记录。
没有print为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。
读取第二行:因为上次i的值由0变成了1,此时就是i=!1,条件为假不打印。
读取第三行:上次条件又为假,i恢复初始值0,取反,继续打印。以此类推...
可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。

2)不匹配某行

[root@ chenc01 ~]# tail /etc/services |awk '!/blp5/{print $0}'
3gpp-cbsp    48049/tcp           # 3GPP Cell Broadcast Service 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
matahari    49000/tcp            # Matahari Broker

3)乘法和除法

[root@ chenc01 ~]# seq 5 |awk '{print $0*2}'
2
4
6
8
10

[root@ chenc01 ~]# seq 5 |awk '{print $0%2}'
1
0
1
0
1

打印偶数行:
[root@ chenc01 ~]# seq 5 |awk '$0%2==0{print $0}'
2
4

打印奇数行:
[root@ chenc01 ~]# seq 5 |awk '$0%2!=0{print $0}'
1
3
5

4)管道符使用

[root@ chenc01 ~]# seq 5 |shuf |awk '{print $0|"sort"}'
1
2
3
4
5

5)正则表达式匹配

[root@ chenc01 ~]# seq 5 |awk '$0~3{print $0}'
3

[root@ chenc01 ~]# seq 5 |awk '$0!~3{print $0}'
1
2
4
5

[root@ chenc01 ~]# seq 5 |awk '$0~/[34]/{print $0}'
3
4

[root@ chenc01 ~]# seq 5 |awk '$0!~/[34]/{print $0}'
1
2
5

[root@ chenc01 ~]# seq 5 |awk '$0~/[^34]/{print $0}'
1
2
5

6)判断数组成员

[root@ chenc01 ~]# awk 'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}' 
yes

7)三目运算符

[root@ chenc01 ~]# awk 'BEGIN{print 1==1?"yes":"no"}'  # 三目运算作为一个表达式,里面不允许写print
yes

[root@ chenc01 ~]# seq 3 |awk '{print $0==2?"yes":"no"}'
no
yes
no

替换换行符为逗号:

[root@ chenc01 ~]# 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@ chenc01 ~]# seq 5 |awk '{n=(n?n","$0:$0)}END{print n}'
1,2,3,4,5

说明:读取第一行时,n没有变量,为假输出$0也就是1,并赋值变量n,读取第二行时,n是1为真,输出1,2 以此类推,后面会一直为真。
每三行后面添加新一行:

[root@ chenc01 ~]# seq 10 |awk '{print NR%3?$0:$0 "\ntxt"}'
1
2
3
txt
4
5
6
txt
7
8
9
txt
10
在

两行合并一行:

[root@ chenc01 ~]# seq 6 |awk '{printf NR%2!=0?$0" ":$0" \n"}'  
1 2 
3 4 
5 6 

[root@ chenc01 ~]# seq 6 |awk 'ORS=NR%2?" ":"\n"' 
1 2
3 4
5 6

[root@ chenc01 ~]# seq 6 |awk '{if(NR%2)ORS=" ";else ORS="\n";print}'

8)变量赋值

字段求和:
[root@ chenc01 ~]# seq 5 |awk '{sum+=1}END{print sum}'
5

[root@ chenc01 ~]# seq 5 |awk '{sum+=$0}END{print sum}'
15

5、流程控制

1)if语句

格式:if (condition) statement [ else statement ]
单分支:

[root@ chenc01 ~]# seq 5 |awk '{if($0==3)print $0}'  
3

也支持正则匹配判断,一般在写复杂语句时使用:

[root@ chenc01 ~]# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2~/[0-9]/)print $2}'  
456cde
[root@ chenc01 ~]# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2!~/[0-9]/)print $2}'
aaabbb
或
[root@ chenc01 ~]# echo "123abc#456cde 789aaa#aaabbb" |xargs -n1 |awk -F# '$2!~/[0-9]/{print $2}'   
aaabbb

双分支:

[root@ chenc01 ~]# seq 5 |awk '{if($0==3)print $0;else print "no"}'
no
no
3
no
no

多分支:
[root@ chenc01 ~]# cat file
1 2 3
4 5 6
7 8 9
[root@ chenc01 ~]# awk '{if($1==4){print "1"} else if($2==5){print "2"} else if($3==6){print "3"} else {print "no"}}' file      
no
1
no

2)while语句

格式:while (condition) statement
遍历打印所有字段:

[root@ chenc01 ~]# awk '{i=1;while(i<=NF){print $i;i++}}' file
1
2
3
4
5
6
7
8
9

awk是按行处理的,每次读取一行,并遍历打印每个字段。

3)for语句C语言风格

格式:for (expr1; expr2; expr3) statement
遍历打印所有字段:

[root@ chenc01 ~]# cat file
1 2 3
4 5 6
7 8 9

[root@ chenc01 ~]# awk '{for(i=1;i<=NF;i++)print $i}' file
1
2
3
4
5
6
7
8
9

倒叙打印文本:

[root@ chenc01 ~]# awk '{for(i=NF;i>=1;i--)print $i}' file    
3
2
1
6
5
4
9
8
7

都换行了,这并不是我们要的结果。怎么改进呢?

[root@ chenc01 ~]# awk '{for(i=NF;i>=1;i--){printf $i" "};print ""}' file  # print本身就会新打印一行
3 2 1
6 5 4
9 8 7
或
[root@ chenc01 ~]# awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' file
3 2 1
6 5 4
9 8 7

在这种情况下,是不是就排除第一行和倒数第一行呢?我们正序打印看下
排除第一行:

[root@ chenc01 ~]# awk '{for(i=2;i<=NF;i++){printf $i" "};print ""}' file
2 3
5 6
8 9

排除第二行:

[root@ chenc01 ~]# awk '{for(i=1;i<=NF-1;i++){printf $i" "};print ""}' file
1 2
4 5
7 8

IP加单引号:

[root@ chenc01 ~]# 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命令查看。

4)for语句遍历数组

格式:for (var in array) statement

[root@ chenc01 ~]# 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

5)break和continue语句

break跳过所有循环,continue跳过当前循环。

[root@ chenc01 ~]# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}'
1
2

[root@ chenc01 ~]# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}'
1
2
4
5

6)删除数组和元素

格式:
delete array[index]  删除数组元素
delete array  删除数组

[root@ chenc01 ~]# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a;for(v in a)print v,a[v]}'
空的…  

[root@ chenc01 ~]# 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

7)exit语句

格式:exit [ expression ]
exit退出程序,与shell的exit一样。[ expr ]是0-255之间的数字。

[root@ chenc01 ~]# seq 5 |awk '{if($0~/3/)exit (123)}'     
[root@ chenc01 ~]# echo $?
123

6、数组

数组:存储一系列相同类型的元素,键/值方式存储,通过下标(键)来访问值。
awk中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标。
数组元素的键和值存储在awk程序内部的一个表中,该表采用散列算法,因此数组元素是随机排序。
数组格式:array[index]=value

1)自定义数组

[root@ chenc01 ~]# awk 'BEGIN{a[0]="test";print a[0]}'
test

2)通过NR设置记录下标,下标从1开始
[root@ chenc01 ~]# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}'
systemd-network

[root@ chenc01 ~]# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[2]}'
zabbix

[root@ chenc01 ~]# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[3]}'
user

3)通过for循环遍历数组

[root@ chenc01 ~]# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(v in a)print a[v],v}'
zabbix 4
user 5
admin 1
systemd-bus-proxy 2
systemd-network 3

[root@ chenc01 ~]# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(i=1;i<=NR;i++)print a[i],i}'
admin 1
systemd-bus-proxy 2
systemd-network 3
zabbix 4
user 5

上面打印的i是数组的下标。
第一种for循环的结果是乱序的,刚说过,数组是无序存储。
第二种for循环通过下标获取的情况是排序正常。
所以当下标是数字序列时,还是用for(expr1;expr2;expr3)循环表达式比较好,保持顺序不变。

4)通过++方式作为下标

[root@ chenc01 ~]# tail -n5 /etc/passwd |awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print a[i],i}'
admin 0
systemd-bus-proxy 1
systemd-network 2
zabbix 3
user 4

x被awk初始化值是0,没循环一次+1

5)使用字段作为下标

[root@ chenc01 ~]# tail -n5 /etc/passwd |awk -F: '{a[$1]=$7}END{for(v in a)print a[v],v}'
/sbin/nologin admin
/bin/bash user
/sbin/nologin systemd-network
/sbin/nologin systemd-bus-proxy
/sbin/nologin zabbix

6)统计相同字段出现次数

[root@ chenc01 ~]# tail /etc/services |awk '{a[$1]++}END{for(v in a)print a[v],v}'
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5

[root@ chenc01 ~]# tail /etc/services |awk '{a[$1]+=1}END{for(v in a)print a[v],v}' 
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5

[root@ chenc01 ~]# tail /etc/services |awk '/blp5/{a[$1]++}END{for(v in a)print a[v],v}'
2 blp5

第一个字段作为下标,值被++初始化是0,每次遇到下标(第一个字段)一样时,对应的值就会被+1,因此实现了统计出现次数。
想要实现去重的的话就简单了,只要打印下标即可。

7)统计TCP连接状态

[root@ chenc01 ~]# netstat -antp |awk '/^tcp/{a[$6]++}END{for(v in a)print a[v],v}'
9 LISTEN
6 ESTABLISHED
6 TIME_WAIT

8)只打印出现次数大于等于2的

[root@ chenc01 ~]# tail /etc/services |awk '{a[$1]++}END{for(v in a) if(a[v]>=2){print a[v],v}}'
2 com-bardac-dw
2 iqobject
2 isnetserv
2 blp5

9)去重

只打印重复的行:
[root@ chenc01 ~]# 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

不打印重复的行:
[root@ chenc01 ~]# tail /etc/services |awk '!a[$1]++'
3gpp-cbsp    48049/tcp           # 3GPP Cell Broadcast Service 
isnetserv    48128/tcp           # Image Systems Network Services
blp5      48129/tcp              # Bloomberg locator
com-bardac-dw   48556/tcp        # com-bardac-dw
iqobject    48619/tcp            # iqobject
matahari    49000/tcp            # Matahari Broker

先明白一个情况,当值是0是为假,非0整数为真,知道这点就不难理解了。
只打印重复的行说明:当处理第一条记录时,执行了++,初始值是0为假,就不打印,如果再遇到相同的记录,值就会+1,不为0,则打印。
不打印重复的行说明:当处理第一条记录时,执行了++,初始值是0为假,感叹号取反为真,打印,如果再遇到相同的记录,值就会+1,不为0为真,取反为假就不打印。

[root@ chenc01 ~]# tail /etc/services |awk '{if(a[$1]++)print $1}'    
isnetserv
blp5
com-bardac-dw
iqobject
使用三目运算:

[root@ chenc01 ~]# tail /etc/services |awk '{print a[$1]++?$1:"no"}'  
no
no
isnetserv
no
blp5
no
com-bardac-dw
no
iqobject
no

[root@ chenc01 ~]# tail /etc/services |awk '{if(!a[$1]++)print $1}'
3gpp-cbsp
isnetserv
blp5
com-bardac-dw
iqobject
matahari

10)统计每个相同字段的某字段总数:

[root@ chenc01 ~]# tail /etc/services |awk -F'[ /]+' '{a[$1]+=$2}END{for(v in a)print v, a[v]}'
com-bardac-dw 97112
3gpp-cbsp 48049
iqobject 97238
matahari 49000
isnetserv 96256
blp5 96258

11)多维数组

awk的多维数组,实际上awk并不支持多维数组,而是逻辑上模拟二维数组的访问方式,比如a[a,b]=1,使用SUBSEP(默认\034)作为分隔下标字段,存储后是这样a\034b。

示例:

[root@ chenc01 ~]# awk 'BEGIN{a["x","y"]=123;for(v in a) print v,a[v]}'
xy 123

我们可以重新复制SUBSEP变量,改变下标默认分隔符:

[root@ chenc01 ~]# awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a) print v,a[v]}'
x:y 123

根据指定的字段统计出现次数:

[root@ chenc01 ~]# cat file
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

[root@ chenc01 ~]# awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(v in a)print a[v],v}' file
1 D-192.168.1.4
1 A-192.168.1.1
2 C-192.168.1.1
2 B-192.168.1.2

7、内置函数

函数 描述
int(expr) 截断为整数
sqrt(expr) 平方根
rand() 返回一个随机数N,0和1范围,0 < N < 1
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可选针对某字段替换,否则替换所有字符串
gensub(r, s, h [, t]) 对输入的记录用s替换r正则匹配,h替换指定索引位置
index(s, t) 返回s中字符串t的索引位置,0为不存在
length([s]) 返回s的长度
match(s, r [, a]) 测试字符串s是否包含匹配r的字符串,如果不包含返回0
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]]]) 格式化输出时间,将时间戳转为字符串
示例:

1)int()

截断为整数:
[root@ chenc01 ~]# echo -e "123abc\nabc123\n123abc123" | awk '{print int($0)}'
123
0
123

[root@ chenc01 ~]# awk 'BEGIN{print int(10/3)}'
3

2)sqrt()

获取9的平方根:
[root@ chenc01 ~]# awk 'BEGIN{print sqrt(9)}'
3

3)rand()和srand()

rand()并不是每次运行就是一个随机数,会一直保持一个不变:
[root@ chenc01 ~]# awk 'BEGIN{print rand()}'
0.237788

当执行srand()函数后,rand()才会发生变化,所以一般在awk着两个函数结合生成随机数,但是也有很大几率生成一样:

[root@ chenc01 ~]# awk 'BEGIN{srand();print rand()}'
0.31687

如果想生成1-10的随机数可以这样:

[root@ chenc01 ~]# awk 'BEGIN{srand();print int(rand()*10)}'
4

如果想更完美生成随机数,还得做相应的处理!

4)asort()和asorti()

排序数组:
[root@ chenc01 ~]# 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@ chenc01 ~]# 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开始,然后遍历。

5)sub()和gsub()

替换正则匹配的字符串:

[root@ chenc01 ~]# tail /etc/services |awk '/blp5/{sub(/tcp/,"icmp");print $0}'
blp5      48129/icmp        # Bloomberg locator
blp5      48129/udp         # Bloomberg locator

[root@ chenc01 ~]# tail /etc/services |awk '/blp5/{gsub(/c/,"9");print $0}'
blp5      48129/t9p         # Bloomberg lo9ator
blp5      48129/udp         # Bloomberg lo9ator

[root@ chenc01 ~]# echo "1 2 2 3 4 5" |awk 'gsub(2,7,$2){print $0}'
1 7 2 3 4 5

[root@ chenc01 ~]# echo "1 2 3 a b c" |awk 'gsub(/[0-9]/, '0'){print $0}'  
0 0 0 a b c

在指定行前后加一行:

[root@ chenc01 ~]# seq 5 | awk 'NR==2{sub('/.*/',"txt\n&")}{print}'
1
txt
2
3
4
5

[root@ chenc01 ~]# seq 5 | awk 'NR==2{sub('/.*/',"&\ntxt")}{print}'
1
2
txt
3
4
5

6)index()

获取字段索引起始位置:
[root@ chenc01 ~]# tail -n 5 /etc/services |awk '{print index($2,"tcp")}'
7
0
7
0
7

7)length()

统计字段长度:
[root@ chenc01 ~]# tail -n 5 /etc/services |awk '{print length($2)}'
9
9
9
9
9

统计数组的长度:
[root@ chenc01 ~]# tail -n 5 /etc/services |awk '{a[$1]=$2}END{print length(a)}'
3

8)match

[root@ chenc01 ~]# echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs  -n1 |awk '{print match($0,234)}'     
0
8
0

如果记录匹配字符串234,则返回索引位置,否则返回0。
那么,我们只想打印包含这个字符串的记录就可以这样:

[root@ chenc01 ~]# echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs  -n1 |awk '{if(match($0,234)!=0)print $0}' 
789aaa#234bbb    

9)split()

切分记录为数组a:
[root@ chenc01 ~]# 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#号切分记录为数据a:

[root@ chenc01 ~]# 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

10)substr()

截取字符串索引4到最后:
[root@ chenc01 ~]# echo -e "123#456#789\nabc#cde#fgh" |awk '{print substr($0,4)}'           
#456#789
#cde#fgh

截取字符串索引4到长度5:
[root@ chenc01 ~]# echo -e "123#456#789\nabc#cde#fgh" |awk '{print substr($0,4,5)}'
#456#
#cde#

11)tolower()和toupper()

转换小写:
[root@ chenc01 ~]# echo -e "123#456#789\nABC#cde#fgh" |awk '{print tolower($0)}'
123#456#789
abc#cde#fgh

转换大写:
[root@ chenc01 ~]# echo -e "123#456#789\nabc#cde#fgh" |awk '{print toupper($0)}'
123#456#789
ABC#CDE#FGH

12)时间处理

返回当前时间戳:
[root@ chenc01 ~]# awk 'BEGIN{print systime()}'
1483297766

将时间戳转为日期和时间
[root@ chenc01 ~]# echo "1483297766" |awk '{print strftime("%Y-%m-%d %H:%M:%S",$0)}'      
2017-01-01 14:09:26

8、I/O语句

语句 描述
getline 读取下一个输入记录设置给$0
getline var 读取下一个输入记录并赋值给变量var
command | getline [var] 运行Shell命令管道输出到$0或var
next 停止当前处理的输入记录后面动作
print 打印当前记录
printf fmt, expr-list 格式化输出
printf fmt, expr-list >file 格式输出和写到文件
system(cmd-line) 执行命令和返回状态
print … >> file 追加输出到文件
print … | command 打印输出作为命令输入
示例:

1)getline

获取匹配的下一行:
[root@ chenc01 ~]# seq 5 |awk '/3/{getline;print}'
4

[root@ chenc01 ~]# seq 5 |awk '/3/{print;getline;print}'
3
4

在匹配的下一行加个星号:
[root@ chenc01 ~]# seq 5 |awk '/3/{getline;sub(".*","&*");print}'
4*

[root@ chenc01 ~]# seq 5 |awk '/3/{print;getline;sub(".*","&*")}{print}'
1
2
3
4*
5

2)getline var

把a文件的行追加到b文件的行尾:
[root@ chenc01 ~]# cat a
a
b
c

[root@ chenc01 ~]# cat b
1 one
2 two
3 three

[root@ chenc01 ~]# awk '{getline line<"a";print $0,line}' b   
1 one a
2 two b
3 three c

把a文件的行替换b文件的指定字段:

[root@ chenc01 ~]# awk '{getline line<"a";gsub($2,line,$2);print}' b  
1 a
2 b
3 c

把a文件的行替换b文件的对应字段:

[root@ chenc01 ~]# awk '{getline line<"a";gsub("two",line,$2);print}' b   
1 one
2 b
3 three

3)command | getline [var]

获取执行shell命令后结果的第一行:
[root@ chenc01 ~]# awk 'BEGIN{"seq 5"|getline var;print var}'
1

循环输出执行shell命令后的结果:
[root@ chenc01 ~]# awk 'BEGIN{while("seq 5"|getline)print}'
1
2
3
4
5

4)next

不打印匹配行:
[root@ chenc01 ~]# seq 5 |awk '{if($0==3){next}else{print}}'
1
2
4
5

删除指定行:
[root@ chenc01 ~]# seq 5 |awk 'NR==1{next}{print $0}'
2
3
4
5

如果前面动作成功,就遇到next,后面的动作不再执行,跳过。
或者:
[root@ chenc01 ~]# seq 5 |awk 'NR!=1{print}' 
2
3
4
5

把第一行内容放到每行的前面:
[root@ chenc01 ~]# cat a
hello 
1 a
2 b
3 c

[root@ chenc01 ~]# awk 'NR==1{s=$0;next}{print s,$0}' a  
hello  1 a
hello  2 b
hello  3 c

[root@ chenc01 ~]# awk 'NR==1{s=$0}NF!=1{print s,$0}' a   
hello  1 a
hello  2 b
hello  3 c

5)system()

执行shell命令判断返回值:
[root@ chenc01 ~]# awk 'BEGIN{if(system("grep root /etc/passwd &>/dev/null")==0)print "yes";else print "no"}'
yes

6)打印结果写到文件

[root@ chenc01 ~]# tail -n5 /etc/services |awk '{print $2 > "a.txt"}'
[root@ chenc01 ~]# cat a.txt
48049/tcp
48128/tcp
48128/udp
48129/tcp
48129/udp

7)管道连接shell命令

将结果通过grep命令过滤:
[root@ chenc01 ~]# tail -n5 /etc/services |awk '{print $2|"grep tcp"}'
48556/tcp
48619/tcp
49000/tcp

9、printf语句

格式化输出,默认打印字符串不换行。

格式:printf [format] arguments

Format 描述
%s 一个字符串
%d,%i 一个小数
%f 一个浮点数
%.ns 输出字符串,n是输出几个字符
%m.nf 输出浮点数,m是输出整数位数,n是输出的小数位数
%x 不带正负号的十六进制,使用a至f表示10到15
%X 不带正负号的十六进制,使用A至F表示10至15
%% 输出单个%
%-5s 左对齐,对参数每个字段左对齐,宽度为5
%-4.2f 左对齐,宽度为4,保留两位小数
%5s 右对齐,不加横线表示右对齐
示例:

将换行符换成逗号:
[root@ chenc01 ~]# seq 5 |awk '{if($0!=5)printf "%s,",$0;else print $0}' 
1,2,3,4,5

小括号中的5是最后一个数字。
输出一个字符:
[root@ chenc01 ~]# awk 'BEGIN{printf "%.1s\n","abc"}'    
a

保留一个小数点:
[root@ chenc01 ~]# awk 'BEGIN{printf "%.2f\n",10/3}'
3.33

格式化输出:
[root@ chenc01 ~]# awk 'BEGIN{printf "user:%s\tpass:%d\n","abc",123}'
user:abc    pass:123

左对齐宽度10:
[root@ chenc01 ~]# awk 'BEGIN{printf "%-10s %-10s %-10s\n","ID","Name","Passwd"}'
ID     Name    Passwd

右对齐宽度10:
[root@ chenc01 ~]# awk 'BEGIN{printf "%10s %10s %10s\n","ID","Name","Passwd"}'  
     ID    Name   Passwd

打印表格:
[root@ chenc01 ~]# vi test.awk
BEGIN{
print "+--------------------+--------------------+";
printf "|%-20s|%-20s|\n","Name","Number";
print "+--------------------+--------------------+";
}

[root@ chenc01 ~]# awk -f test.awk
+--------------------+--------------------+
|Name         |Number        |
+--------------------+--------------------+
格式化输出:
[root@ chenc01 ~]# awk -F: 'BEGIN{printf "UserName\t\tShell\n-----------------------------\n"}{printf "%-20s %-20s\n",$1,$7}END{print "END...\n"}' /etc/passwd

打印十六进制:
[root@ chenc01 ~]# awk 'BEGIN{printf "%x %X",123,123}'
7b 7B

10、自定义函数

格式:function name(parameter list) { statements }

示例:
[root@ chenc01 ~]# awk 'function myfunc(a,b){return a+b}BEGIN{print myfunc(1,2)}'   
3

11、需求案例

1)分析Nginx日志

日志格式:'$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"'

统计访问IP次数:
[root@ chenc01 ~]# awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log

统计访问访问大于100次的IP:
[root@ chenc01 ~]# awk '{a[$1]++}END{for(v in a){if(a[v]>100)print v,a[v]}}' access.log 

统计访问IP次数并排序取前10:
[root@ chenc01 ~]# awk '{a[$1]++}END{for(v in a)print v,a[v] |"sort -k2 -nr |head -10"}' access.log

统计时间段访问最多的IP:
[root@ chenc01 ~]# awk '$4>="[02/Jan/2017:00:02:00" && $4<="[02/Jan/2017:00:03:00"{a[$1]++}END{for(v in a)print v,a[v]}' access.log

统计上一分钟访问量:
[root@ chenc01 ~]# date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M)
[root@ chenc01 ~]# awk -vdate=$date '$4~date{c++}END{print c}' access.log

统计访问最多的10个页面:
[root@ chenc01 ~]# awk '{a[$7]++}END{for(v in a)print v,a[v] |"sort -k1 -nr|head -n10"}' access.log

统计每个URL数量和返回内容总大小:
[root@ chenc01 ~]# awk '{a[$7]++;size[$7]+=$10}END{for(v in a)print a[v],v,size[v]}' access.log

统计每个IP访问状态码数量:
[root@ chenc01 ~]# awk '{a[$1" "$9]++}END{for(v in a)print v,a[v]}' access.log

统计访问IP是404状态次数:
[root@ chenc01 ~]# awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log

2)两个文件对比

找出b文件在a文件相同记录:
[root@ chenc01 ~]# seq 1 5 > a
[root@ chenc01 ~]# seq 3 7 > b
方法1:
[root@ chenc01 ~]# awk 'FNR==NR{a[$0];next}{if($0 in a)print $0}' a b 
3
4
5

[root@ chenc01 ~]# awk 'FNR==NR{a[$0];next}{if($0 in a)print FILENAME,$0}' a b
b 3
b 4
b 5

[root@ chenc01 ~]# awk 'FNR==NR{a[$0]}NR>FNR{if($0 in a)print $0}' a b  
3
4
5

[root@ chenc01 ~]# awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' a b  # a[$0]是通过b文件每行获取值,如果是1说明有
[root@ chenc01 ~]# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' a b
3
4
5

方法2:
[root@ chenc01 ~]# awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' a b
3
4
5

方法3:
[root@ chenc01 ~]# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' a b   
3
4
5

找出b文件在a文件不同记录:

方法1:
[root@ chenc01 ~]# awk 'FNR==NR{a[$0];next}!($0 in a)' a b       
6
7

[root@ chenc01 ~]# awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' a b
[root@ chenc01 ~]# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' a b
6
7

方法2:
[root@ chenc01 ~]# awk 'FILENAME=="a"{a[$0]=1}FILENAME=="b" && a[$0]!=1' a b

方法3:
[root@ chenc01 ~]# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' a b 

3)合并两个文件

将a文件合并到b文件:
[root@ chenc01 ~]# cat a
zhangsan 20
lisi 23
wangwu 29

[root@ chenc01 ~]# cat b
zhangsan man
lisi woman
wangwu man

[root@ chenc01 ~]# awk 'FNR==NR{a[$1]=$0;next}{print a[$1],$2}' a b
zhangsan 20 man
lisi 23 woman
wangwu 29 man

[root@ chenc01 ~]# awk 'FNR==NR{a[$1]=$0}NR>FNR{print a[$1],$2}' a b     
zhangsan 20 man
lisi 23 woman
wangwu 29 man

将a文件相同IP的服务名合并:

[root@ chenc01 ~]# cat a
192.168.1.1: httpd
192.168.1.1: tomcat
192.168.1.2: httpd
192.168.1.2: postfix
192.168.1.3: mysqld
192.168.1.4: httpd

[root@ chenc01 ~]# awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a  
192.168.1.4: httpd
192.168.1.1: httpd tomcat
192.168.1.2: httpd postfix
192.168.1.3: mysqld

说明:数组a存储是$1=a[$1] $2,第一个a[$1]是以第一个字段为下标,值是a[$1] $2,也就是$1=a[$1] $2,值的a[$1]是用第一个字段为下标获取对应的值,但第一次数组a还没有元素,那么a[$1]是空值,此时数组存储是192.168.1.1=httpd,再遇到192.168.1.1时,a[$1]通过第一字段下标获得上次数组的httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标192.168.1.1的新值。此时数组存储是192.168.1.1=httpd tomcat。每次遇到相同的下标(第一个字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。

4)将第一列合并到一行
[root@ chenc01 ~]# cat file
1 2 3
4 5 6
7 8 9

[root@ chenc01 ~]# awk '{for(i=1;i<=NF;i++)a[i]=a[i]$i" "}END{for(v in a)print a[v]}' file   
1 4 7
2 5 8
3 6 9

说明:
for循环是遍历每行的字段,NF等于3,循环3次。

读取第一行时:
第一个字段:a[1]=a[1]1" "  值a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此a[1]=1 。
第二个字段:a[2]=a[2]2" "  值a[2]数组a已经定义,但没有2这个下标,也获取不到对应的值,为空,因此a[2]=2 。
第三个字段:a[3]=a[3]3" "  值a[2]与上面一样,为空,a[3]=3 。

读取第二行时:
第一个字段:a[1]=a[1]4" "  值a[2]获取数组a的2为下标对应的值,上面已经有这个下标了,对应的值是1,因此a[1]=1 4
第二个字段:a[2]=a[2]5" "  同上,a[2]=2 5
第三个字段:a[3]=a[3]6" "  同上,a[2]=3 6

读取第三行时:
处理方式同上,数组最后还是三个下标,分别是1=1 4 7,2=2 5 8,3=3 6 9。最后for循环输出所有下标值。

5)字符串拆分,统计出现的次数

字符串拆分:
方法1:
[root@ chenc01 ~]# echo "hello world" |awk -F '' '{print $1}'
h

[root@ chenc01 ~]# echo "hello" |awk -F '' '{for(i=1;i<=NF;i++)print $i}'   
h
e
l
l
o

方法2:
[root@ chenc01 ~]# echo "hello" |awk '{split($0,a,"''");for(v in a)print a[v]}'
l
o
h
e
l

统计字符串中每个字母出现的次数:

[root@ chenc01 ~]# echo "a.b.c,c.d.e" |awk -F '[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}'
a 1
b 1
c 2
d 1
e 1

6)统计平均成绩

[root@ chenc01 ~]# cat file
job 80
dave 84
tom 75
dave 73
job 72
tom 83
dave 88

[root@ chenc01 ~]# awk '{a[$1]+=$2;b[$1]++}END{for(i in a)print i,a[i]/b[i]}' file
job 76
dave 81.6667
tom 79

7)费用统计

[root@ chenc01 ~]# cat file
zhangsan 8000 1
zhangsan 5000 1
lisi 1000 1
lisi 2000 1
wangwu 1500 1
zhaoliu 6000 1
zhaoliu 2000 1
zhaoliu 3000 1

[root@ chenc01 ~]# awk '{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(v in name)print v,cost[v],number[v]}' file
zhangsan 5000 1
lisi 3000 2
wangwu 1500 1
zhaoliu 11000 3

8)获取数字字段最大值
[root@ chenc01 ~]# cat file
a b 1
c d 2
e f 3
g h 3
i j 2

获取第三字段最大值:
[root@ chenc01 ~]# awk 'BEGIN{max=0}{if($3>max)max=$3}END{print max}' file
3

打印第三字段最大行:
[root@ chenc01 ~]# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)print v,a[v],max}' a
g h 3 3 3
e f 3 3 3
c d 2 2 3
a b 1 1 3
i j 2 2 3

[root@ chenc01 ~]# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)if(a[v]==max)print v}' a
gh 3
e f 3

9)去除第一行和最后一行
[root@ chenc01 ~]# seq 5 |awk 'NR>2{print s}{s=$0}'
2
3
4

读取第一行,NR=1,不执行print s,s=1
读取第二行,NR=2,不执行print s,s=2 (大于为真) 
读取第三行,NR=3,执行print s,此时s是上一次p赋值内容2,s=3
最后一行,执行print s,打印倒数第二行,s=最后一行
获取Nginx负载均衡配置端IP和端口:

[root@ chenc01 ~]# cat nginx.conf
upstream example-servers1 {
  server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;
}

upstream example-servers2 {
  server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;
  server 127.0.0.1:82 backup;
}

[root@ chenc01 ~]# awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' nginx.conf  
127.0.0.1:80
[root@ chenc01 ~]# awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' nginx.conf  
[root@ chenc01 ~]# awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' nginx.conf
127.0.0.1:80

读取第一行,i初始值为0,0>1为假,不执行print s,x=example-servers1,i=1
读取第二行,i=1,1>1为假,不执行print s,s=127.0.0.1:80,i=2
读取第三行,i=2,2>1为真,执行print s,此时s是上一次s赋值内容127.0.0.1:80,i=3
最后一行,执行print s,打印倒数第二行,s=最后一行。
这种方式与上面一样,只是用i++作为计数器。

10)知道上述方式,就可以实现这种需求了,打印匹配行的上一行

[root@ chenc01 ~]# seq 5 |awk '/3/{print s}{s=$0}'
2

你可能感兴趣的:(Shell)