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

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

命令格式

awk 选项 'BEGIN{}{动作}END{}' file

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

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

在 awk 中,默认情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,每一行指定分隔符分为多段内容,每段用 $1,$2,$3…数字的方式顺序来表示。操作多个字段是,以逗号分隔,$0 表示整个行,与 Shell 的位置参数有异曲同工之妙。

选项

建议结合下方示例来理解选项的使用

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

print 用于输出想要的内容,类似 echo

读取文件中的动作

# $2 表示以空格为分隔符将一行分为多段内容,$2为第二段内容
$ echo '{print $2}' > awk.txt
$ tail -n3 /etc/services | awk -f awk.txt
45514/udp
45514/tcp
46998/tcp

设置分隔符

# 使用冒号将一行内容分隔为多段
awk -F':' '{print $1}' /etc/passwd

# 设置多个分隔符,使用#和:将一行内容分为多段
$ echo "1#2:3#4:5:6" > awk.txt
$ awk -F'[#:]' '{print $1,$3}' awk.txt 
1 3
# 1#2:3,使用#和:将这段字符串分为1 2 3,$1=1,$2=2,$3=3
# 多个分隔符的意思就是每遇到一个指定分隔符中的任何一个都将作为一个字段

变量赋值

$ awk -v blog="FeiYi Blog" 'BEGIN{print blog}'
FeiYi Blog

# 系统变量作为awk变量的值
$ a="FeiYi Blog"
$ awk -v blog="$a" 'BEGIN{print blog}'
FeiYi Blog

输出awk全局变量

awk --dump-variables -v blog="$a" 'BEGIN{print blog}'
# 默认当前路径会生成awkvars.out文件,用来保存全局变量

格式化输出awk命令

将awk的操作以json格式保存到文件

awk --profile -v blog="$a" 'BEGIN{print blog}'
# 默认当前路径会生成awkprof.out文件,用来保存格式化后的awk命令

模式(pattern)

对查找内容的过滤,建议结合下方示例来理解模式的应用

Pattern 描述
BEGIN{ } awk处理文本之前执行的操作
END{ } awk处理文本之后执行的操作
/regular expression/ 为每个输入记录匹配正则表达式
pattern && pattern 逻辑与,同时满足两个模式
pattern || pattern 逻辑或,满足其中之一即可
pattern1, pattern2 范围模式,匹配所有模式1的记录,直到匹配到模式2

BEGIN 和 END

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

$ tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print $0}'
Service         Port                    Description
===
aigairserver    21221/tcp               # Services for Air Server
ka-kdp          31016/udp               # Kollective Agent Kollective Delivery
ka-sddp         31016/tcp               # Kollective Agent Secure Distributed Delivery
edi_service     34567/udp               # dhanalakshmi.org EDI Service
axio-disc       35100/tcp               # Axiomatic discovery protocol
axio-disc       35100/udp               # Axiomatic discovery protocol
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive
cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp               # Capture handwritten signatures

END模式是在awk处理文件之后执行的操作

$ tail /etc/services |awk --profile 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print $0}END{print "===\nEND......"}'
Service         Port                    Description
===
aigairserver    21221/tcp               # Services for Air Server
ka-kdp          31016/udp               # Kollective Agent Kollective Delivery
ka-sddp         31016/tcp               # Kollective Agent Secure Distributed Delivery
edi_service     34567/udp               # dhanalakshmi.org EDI Service
axio-disc       35100/tcp               # Axiomatic discovery protocol
axio-disc       35100/udp               # Axiomatic discovery protocol
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive
cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp               # Capture handwritten signatures
===
END......

正则匹配

和 sed 类似

# 输出匹配包含tcp的行
tail /etc/services | awk '/tcp/{print $0}'

# 匹配以a开头的行
tail /etc/services | awk '/^a/{print $0}'

# 匹配开头是6个字符的行
tail /etc/services | awk '/^[a-z0-9-]{6} /{print $0}'

# 匹配以Kollective Delivery结尾的行
tail /etc/services | awk '/(Kollective Delivery)$/{print $0}'

逻辑与、逻辑或、逻辑非

# 匹配必须包括 udp 和 ka-kdp 的行
tail /etc/services | awk '/udp/ && /ka-kdp/ {print $0}'

# 匹配必须包括 tcp 或 ka-kdp 的行
tail /etc/services | awk '/udp/ || /ka-kdp/ {print $0}'

# 不匹配注释行和空行
cat /etc/nginx/nginx.conf | awk '!/^$/ && !/^#/ {print $0}'
cat /etc/nginx/nginx.conf | awk '! /^$|^#/ {print $0}'
cat /etc/nginx/nginx.conf | awk '/^[^#]|"^$"/ {print $0}'

匹配范围

对以下内容进行匹配

$ cat awk.txt 
Remember Kruschev:  he tried to do too many things too fast, and he was
removed in disgrace.  If Gorbachev tries to destroy the system or make too
many fundamental changes to it, I believe the system will get rid of him.
I am not a political scientist, but I understand the system very well.
I believe he will have a "heart attack" or retire or be removed.  He is
up against a brick wall.  If you think they will change everything and
become a free, open society, forget it!
                -- Victor Belenko, MiG-25 fighter pilot who defected in 1976
                        "Defense Electronics", Vol 20, No. 6, pg. 110

匹配过程与sed类似,以行为单位进行匹配

# 匹配文本中有Rem的行作为起始行,匹配文本中有I的行作为结束行
$ awk '/Rem/,/I/' awk.txt
Remember Kruschev:  he tried to do too many things too fast, and he was
removed in disgrace.  If Gorbachev tries to destroy the system or make too

awk内置变量

建议结合下方示例来理解变量的应用

变量名 描述
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”

FS 和 OFS 字段分隔符

字段分隔符,将一行中的内容,以 FS 定义的值进行分隔,每遇到一个 FS 分隔符就被分隔为一个字段

FS: FS 与 -F 选项功能一样,在awk处理文本之前设置 FS 变量

$ tail -n3 /etc/passwd | awk 'BEGIN{FS=":"}{print $1,$2}'
ssjinyao x
redis x
lei x

$ tail -n3 /etc/passwd | awk -F':' '{print $1,$2}'
ssjinyao x
redis x
lei x

$ tail -n3 /etc/passwd | awk -v FS=':' '{print $1,$2}'
ssjinyao x
redis x
lei x

OFS: 上面的例子中默认输出的是 lei x 中间以空格分隔,这个空格就是OFS的值

$ tail -n3 /etc/passwd | awk 'BEGIN{FS=":";OFS="-"}{print $1,$2}'
ssjinyao-x
redis-x
lei-x

$ tail -n3 /etc/passwd | awk -F':' '{print $1"-"$2}'
ssjinyao-x
redis-x
lei-x

RS 和 ORS 行分隔符

作为行分隔符,也叫记录分隔符,一行内容也叫一行记录,将 RS 定义的符号作为换行的分隔符,每遇到一个 RS 分隔符就进行换行。

RS: RS默认以 \n 分隔每段值

$ echo "www.feiyiblog.com/user/test.html" | awk 'BEGIN{FS="/"}{print $0}'
www.feiyiblog.com/user/test.html
$ echo "www.feiyiblog.com/user/test.html" | awk 'BEGIN{RS="/"}{print $0}'
www.feiyiblog.com
user
test.html

# 使用正则
$ seq -f "str%02g" 10 | sed 'n;n;a\-----'
str01
str02
str03
-----
str04
str05
str06
-----
str07
str08
str09
-----
str10

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

ORS: 默认以 \n 作为分隔输出每行内容

$ seq 10 |awk 'BEGIN{ORS="-"}{print $0}'
1-2-3-4-5-6-7-8-9-10-

$ tail -n3 /etc/passwd | awk 'BEGIN{FS=":";OFS="-";ORS=":"}{print $1,$2}'
ssjinyao-x:redis-x:lei-x:

# 默认ORS的原本输出内容为
ssjinyao-x
redis-x
lei-x

NF 行字段数

统计每行中的字段数量

$ echo "abcdef" | awk 'BEGIN{FS=""}{print NF}'
6

# 示例中NF的值为6,也可以直接引用该值来输出最后一个字段 {print $NF}={print $6}
$ echo "abcdef" | awk 'BEGIN{FS=""}{print $NF}'
f

# 也可以使用NF进行运算来输出指定字段
$ echo "abcdef" | awk 'BEGIN{FS=""}{print $(NF-1)}'
e

# 也可以直接给字段赋值,不输出最后2个字段
$ echo "abcdef" | awk 'BEGIN{FS=""}{$NF="";$(NF-1)="";print $0}'
a b c d 

NR 和 FNR 文件行编号

NR: 说白了就是给每一行编写行号

$ tail -n4 /etc/services | awk '{print NR,$0}'
1 pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
2 cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive
3 cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
4 spremotetablet  46998/tcp               # Capture handwritten signatures

# 输出第3行,第1个字段
$ tail -n4 /etc/services | awk 'NR==3{print $1}'
cloudcheck

# 输出总行数
$ tail -n4 /etc/services | awk 'END{print NR}'
4

# 输出前2行
$  tail -n4 /etc/services | awk 'NR<=2{print $0}'
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive

FNR: awk处理2个文件时,NR会将多个文件的行进行统一编号,而FNR在处理第二个文件时,编号重新计数。

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

$ cat a 
a
b
c
$ cat b 
c
d
e
$ 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

处理2个文件时,进行判断

$ awk 'FNR==NR{print $0" 1"}FNR!=NR{print $0" 2"}' a b
a 1
b 1
c 1
c 2
d 2
e 2

ARGC 和 ARGV 命令行参数

ARGC: 命令行参数数量,除了选项中间单引号内的全都属于参数,也就是awk命令和处理的文件名属于参数

$ awk 'BEGIN{print ARGC}' a b c
4

ARGV: 将命令行参数存储到数组中,数组下标从0开始

$ awk 'BEGIN{print ARGV[0]}' a b c
awk
$ awk 'BEGIN{print ARGV[1]}' a b c
a
$ awk 'BEGIN{print ARGV[2]}' a b c
b
$ awk 'BEGIN{print ARGV[3]}' a b c
c

ARGIND 文件识别

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

$ cat a 
a
b
c
$ cat b 
c
d
e
$ awk '{print ARGIND,$0}' a b
1 a
1 b
1 c
2 c
2 d
2 e

# ARGIND=1时是在处理a文件,ARGIND=2时是在处理b文件
$ awk 'ARGIND==1{print "a文件: "$0}{print "---"}ARGIND==2{print "---\nb文件: "$0}' a b
a文件: a
a文件: b
a文件: c
b文件: c
b文件: d
b文件: e

ENVIRON 调用系统变量

通过 export 定义的变量也可使用同样的方式调用

$ awk 'BEGIN{print ENVIRON["SHELL"]}'
/bin/bash
$ awk 'BEGIN{print ENVIRON["HOME"]}'
/root
$ awk 'BEGIN{print ENVIRON["USER"]}'
root

FILENAME 显示当前文件名

$ 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

IGNORECASE 忽略大小写

IGNORECASE=0 表示不忽略大小写,默认为0

其余值都是忽略大小写

$ echo -e "A \na \nB \nb \nC \nc" | awk 'BEGIN{IGNORECASE=1}/b/'
B 
b 

运算符

建议结合下方示例来理解运算符的应用

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

须知:

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

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

# 数字是0$ awk 'BEGIN{n=0;if(n)print "true";else print "false"}'false# 空字符串$ awk 'BEGIN{n="";if(n)print "true";else print "false"}'false# 未定义的值$ awk 'BEGIN{if(n)print "true";else print "false"}'false

截取整数

$ echo "123abc 456def ghi789 1011jkl1213" | awk 'BEGIN{RS=" "}{print +$0}'
123
456
0
1011

由上得出,当输出的字符串中,字段开头有数字的情况下,可以将数字提取出来,如果开头不是数字,视为 0,如果数字中间存在非数字,则截取完开头数字即停止截取

同样减号也有相同的作用,只不过结果将会是负数

$ echo "123abc 456def ghi789 1011jkl1213" | awk 'BEGIN{RS=" "}{print -$0}'
-123
-456
0
-1011

截取整数的功能,其实是与0+$0的运算,当然也可以在加减号前加入其他数字,就会得到相应的运算结果

$ echo "123abc 456def ghi789 1011jkl1213" | awk 'BEGIN{RS=" "}{print 1+$0}'
124
457
1
1012

逻辑否(取反)

也就是平常用到的取反

# 输出奇数行
$ seq 8 | awk 'i = !i'
1
3
5
7

i 是一个未定义的值,awk将认为 i=0,所以 i = !i 等于 0 = !0,这个等式并不成立,结果为 false,

awk读取第一行时,i 是一个未定义的值,i = !i 认为是在给 i 赋值,但是等式右边的 !i 中的 i 不存在,被认为是0,所以 i = !i 等于 i = !0 ,非0或非空字符串为真,!0 位真,因此 i = 1,条件为真则会打印当前行的内容。

awk读取第二行时,在读取第一行时 i = 1,所以 i = !i 等于 i = !1 ,条件为假则不会打印当前行。

第三行同理第一行,第二行结束时 i = 0,以此类推

# 输出偶数行
# 原理和奇数一样,只要将上面输出奇数行的内容整体取反即可
$ seq 8 | awk '!(i = !i)'
2
4
6
8

在奇数中,第一行 i 的结果是 1,在偶数这里将 1 取反,结果为 假,就第一行不输出了,以此类推。

也可以使用取反达到类似 grep -v 的操作

# 样例文本
$ tail /etc/services 
aigairserver    21221/tcp               # Services for Air Server
ka-kdp          31016/udp               # Kollective Agent Kollective Delivery
ka-sddp         31016/tcp               # Kollective Agent Secure Distributed Delivery
edi_service     34567/udp               # dhanalakshmi.org EDI Service
axio-disc       35100/tcp               # Axiomatic discovery protocol
axio-disc       35100/udp               # Axiomatic discovery protocol
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive
cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp               # Capture handwritten signatures

# grep
$ tail /etc/services  |  grep -v axio
aigairserver    21221/tcp               # Services for Air Server
ka-kdp          31016/udp               # Kollective Agent Kollective Delivery
ka-sddp         31016/tcp               # Kollective Agent Secure Distributed Delivery
edi_service     34567/udp               # dhanalakshmi.org EDI Service
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive
cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp               # Capture handwritten signatures

# awk
$ tail /etc/services  |  awk '!/axio/{print $0}'
aigairserver    21221/tcp               # Services for Air Server
ka-kdp          31016/udp               # Kollective Agent Kollective Delivery
ka-sddp         31016/tcp               # Kollective Agent Secure Distributed Delivery
edi_service     34567/udp               # dhanalakshmi.org EDI Service
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive
cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp               # Capture handwritten signatures

乘 除 取余

$ seq 5 | awk '{print $0*2}'
2
4
6
8
10

$ seq 5 | awk '{print $0/2}'
0.5
1
1.5
2
2.5

$ seq 5 | awk '{print $0%3}'
1
2
0
1
2

管道符

# 无规则打乱顺序
$ seq 5 | shuf
1
5
4
3
2

# sort 按照从小到大排序
$ seq 5 | shuf | awk '{print $0 | "sort"}'
1
2
3
4
5

正则匹配

$ seq 5 | awk '$0~3{print $0}'
3

$ seq 5 | awk '$0!~3{print $0}'
1
2
4
5

$ seq 5 | awk 'NR!~/[3-5]/{print $0}'
1
2

$ seq 5 | awk 'NR~/[25]/{print $0}'
2
5

数组索引判断

判断索引在数组中是否存在

$ awk 'BEGIN{a[0]=123;a[1]=456}END{if(0 in a)print "yes"}' < /dev/null
yes

三目运算符

就是一个简单的if语句,expr1 ? expr2 : expr3三目运算中不允许有print

expr1 作为一个表达式,判断expr1表达式的结果,成立执行 expr2,反之执行expr3

$ awk 'BEGIN{print 1==2 ? "yes" : "no"}' 
no
$ awk 'BEGIN{print 1==1 ? "yes" : "no"}' 
yes

替换换行符为逗号

$ seq 5 | awk '{print n=(n ? n","$0 : $0)}'
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5

n ? n","$0 : $0,改操作表示 n 成立,输出 n","$0,否则输出 $0

起始 n 的值为 0,结果为false,代入运算

处理第一行时 {print n=$0},第一行的 $0 是 1,此时 n = 1,输出1

处理第二行时,此时 n 的值为 1,表达式成立{print n=n","$0},等于{print n=1","$0},第二行的 $0 是 2,此时 n= 1,2,输出 1,2

处理第三行时,此时 n 的值为 1,2 ,非0值,表达式依然成立{print n=n","$0},等于{print n=1,2","$0},第三行的 $0 是 3,此时 n= 1,2,3,输出 1,2,3

以此类推…

每 3 行后新加一行

$ seq 10 | awk '{print NR%3 ? $0 : $0 "\ntxt"}'
1
2
3
txt
4
5
6
txt
7
8
9
txt
10

NR%3为取余,只有3的倍数的时候是没有余数的,结果为0,如下,当结果为0时,输出内容是$0 \ntxt。

$ seq 10 | awk '{print NR%3}'
1
2
0
1
2
0
1
2
0
1

变量赋值

# +=是累积叠加的计算
# sum+1=1 1+1=2 2+1=3 ,每次处理一行sum的值都会改变,也会累计+1
$ seq 5 | awk '{sum+=1}END{print sum}'
5

# 同理,将每一行的$1的值相加
$ seq 5 | awk '{sum+=$1}END{print sum}'
15

流程控制

可使用 awk 的判断或者循环语句,进行灵活处理

if 语句

格式: 
  单分支:
          if (条件) 动作 [else 动作]
  双分支:
          if (条件) 动作; else 动作
  多分支:
          if (条件) 动作; else if (条件) 动作; else 动作

示例

单分支

$ seq 5 | awk '{if ($0==3) print $0}'
3

双分支

$ seq 5 | awk '{if ($0==3) print $0; else print "no"}'
no
no
3
no
no

多分支

$ seq 5 | awk '{if ($0==1) print $0; else if ($0==3) print $0; else if ($0==5) print $0;else print "no"}'
1
no
3
no
5

判断也可以使用正则

$ seq 5 | awk '{if ($0~/[135]/) print $0; else print "no"}'
1
no
3
no
5

$ seq 5 | awk '{if ($0!~/[135]/) print $0; else print "no"}'
no
2
no
4
no

while 语句

格式:
while (条件) 动作

示例

$ seq 5 | awk '{i=0; while (i<NF) {print $i; i++}}'
1
2
3
4
5

for 语句(C风格)

格式:
for (C风格条件) 动作

示例

C 风格的 for ,普遍条件有 3 个,下面对示例进行解析,格式为 for (初始值;运算判断;增量/减量)

在处理每一行 for 循环中这3个条件的执行顺序如下:

初始值只有在第一次循环时执行–>判断–>循环体–>增量/减量

之后继续判断–>循环体–>增量/减量,直到条件不成立,开始下一行

初始值–>判断–>循环体–>增量/减量–>判断–>循环体–>增量/减量–>判断(条件不成立)–>结束循环

$ cat file 
1 2 3
4 5 6
7 8 9

$ awk '{for (i=1;i<=NF;i++) print $i}' file
1
2
3
4
5
6
7
8
9

# for (i=1;i<=NF;i++) print $i
# NF 的值为每一行的字段个数,且不受分隔符限制,也就是在file文件中,NF 每一行的值都为 3
# for (i=1;i<=3;i++) print $i
# 第一行第一次循环 i=1;i<=3成立,print $i,此时i值为 1,$1=1然后执行 i++,此时i值为2
# 第一行第二次循环 2<=3成立,print $i,此时i值为 2,$2=2然后执行 i++,此时i值为3
# 第一行第三次循环 3<=3成立,print $i,此时i值为 3,$3=3然后执行 i++,此时i值为4
# 第一行第四次循环 4<=3不成立,开始处理下一行

# 第二行第一次循环 i=1;i<=3成立,print $i,此时i值为 1,$1=4然后执行 i++,此时i值为2
# 第二行第二次循环 2<=3成立,print $i,此时i值为 2,$2=5然后执行 i++,此时i值为3
# 第二行第三次循环 3<=3成立,print $i,此时i值为 3,$3=6然后执行 i++,此时i值为4
# 第二行第四次循环 4<=3不成立,开始处理下一行
# 直至处理完三行,循环结束

$ awk '{for (i=NF;i>=1;i--) printf $i" "; print ""}' file 
3 2 1 
6 5 4 
9 8 7 

$ 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'

for 遍历数组

格式:
    for (变量 in 数组名) 动作

示例

$ seq -f "str%.g" 5 | awk '{a[NR]=$0}END{for (v in a) print v,a[v]}'
1 str1
2 str2
3 str3
4 str4
5 str5

分析一下是怎么做到的,以下是要处理的文本

$ seq -f "str%.g" 5
str1
str2
str3
str4
str5

NR 为每一行编号,也就是行好

$ seq -f "str%.g" 5 | awk '{a[NR]=$0}
# 第一行为 str1,相关联的行号NR的值也为1,所以第一次处理的结果是 a[1]=str1
# 第二行为 str2,相关联的行号NR的值也为2,所以第二次处理的结果是 a[2]=str2
# 以此类推
# 相当于最后结果为 a=(str1,str2,str3,str4,str5),索引依次为1、2、3、4、5

查看循环的逻辑

$ seq -f "str%.g" 5 | awk '{a[NR]=$0}END{for (v in a) print v,a[v]}'
# {for (v in a) print v,a[v]}
# 只看循环部分,由上得知,a的结果为a=(str1,str2,str3,str4,str5)
# v的值从a数组的索引中循环,v in a,在运算符中,in是用来判断数组的索引的
# a的索引为 1、2、3、4、5,所以 v 的值在循环时也是这5个值
# print v,a[v]  最后输出时为:
# print 数组索引号,a[索引],所以结果为
数组索引    a[索引]
1            str1
2            str2
3            str3
4            str4
5            str5

break 和 continue 语句

break 退出循环,continue 跳过当前循环

$ awk 'BEGIN{for (i=1;i<=5;i++){if (i==3) {break};print i}}'
1
2

$ awk 'BEGIN{for (i=1;i<=5;i++){if (i==3) {continue};print i}}'
1
2
4
5

删除数组和元素

格式:
    删除元素:
        delete 数组名[索引]
    删除数组:
        delete 数组名

示例

$ seq -f "str%.g" 5 | awk '{a[NR]=$0}END{delete a;for (v in a) print v,a[v]}'
# 没有结果

$ seq -f "str%.g" 5 | awk '{a[NR]=$0}END{delete a[4];for (v in a) print v,a[v]}'
1 str1
2 str2
3 str3
5 str5
# 索引为4的元素被删除了

exit 语句

与 shell 的 exit 一样,退出码为 0-255 之间

$ seq 5 | awk '{if ($1==3) exit (255)}'
$ echo $?
255

数组

awk 中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标。

数组元素的键和值存储在 awk 程序内部的一个表中,该表采用散列算法,因此数组元素是随机排序。

上面的示例过程中已经使用过数组的形式了,和 shell 数组也是类似的。

格式:
    数组名[索引]=值
# awk 不支持类似shell的统一赋值,只能一个索引一个索引赋值

自定义数组

$ awk 'BEGIN{a[0]="FeiYi";print a[0]}'
FeiYi

使用 NR 内置变量做索引

$ tail -n3 /etc/services | awk 'BEGIN{OFS="\n"}{a[NR]=$1}END{print a[1],a[2],a[3]}'
cloudcheck-ping
cloudcheck
spremotetablet

for 循环遍历数组的方式在流程控制中的 for 遍历数组 已经有例子了。

++ 设置下标

$ tail -n5 /etc/services | awk '{a[x++]=$1}END{for (i=0;i<=x-1;i++)print i,a[i]}'
0 axio-disc
1 pmwebapi
2 cloudcheck-ping
3 cloudcheck
4 spremotetablet

解析

# 因为x是未定义值,使用++后,被转换为数值型,故值为0
# END最后执行
# 处理每一行 a[0]=axio-disc,a[1]=pmwebapi,a[2]=cloudcheck-ping,a[3]=cloudcheck,a[4]=spremotetablet
# 执行END,此时x的值为5
# i=0;i<=5-1成立 print 0,a[0] 0++,i=1
# 1<=5-1成立 print 1,a[1] 1++,i=2
# 2<=5-1成立 print 0,a[0] 2++,i=3
# 3<=5-1成立 print 0,a[0] 3++,i=4
# 4<=5-1成立 print 0,a[0] 4++,i=5
# 5<=5-1不成立

字段作为下标

$ tail -n5 /etc/passwd | awk -F':' '{a[$1]=$7}END{for (v in a) print v" 用户的执行环境为:"a[v]}'
ssjinyao 用户的执行环境为:/bin/bash
redis 用户的执行环境为:/sbin/nologin
lei 用户的执行环境为:/bin/bash
renjin 用户的执行环境为:/bin/bash
guochao 用户的执行环境为:/bin/bash

统计相同字段的次数

$ tail /etc/services | awk '{a[$1]++}END{for (v in a) print a[v],v}'
1 cloudcheck-ping
1 edi_service
1 aigairserver
1 cloudcheck
1 spremotetablet
1 ka-sddp
1 pmwebapi
2 axio-disc
1 ka-kdp
# 表示 axio-disc有相同的两行的 $1 的值一样

$1 作为下标,$1 的值被 ++ 初始化是 0,a[cloudcheck-ping]++ 的值为 0,

每次遇到下标(第一个字段)一样时,对应的值就会被 +1,因此实现了统计出现次数。

只输出大于等于 2 的重复值

$ tail /etc/services | awk '{a[$1]++}END{for (v in a) if (a[v]>=2) {print a[v],v}}'
2 axio-disc

统计 TCP 连接状态

使用 netstat 命令,第六个字段为 state ,连接状态

$ netstat -anput | awk '/^tcp/{a[$6]++}END{for (v in a) print a[v],v}'
23 LISTEN
14 ESTABLISHED
2 TIME_WAIT

去重

只输出重复的行

$ tail /etc/services | awk 'a[$1]++'
axio-disc       35100/udp               # Axiomatic discovery protocol

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

输出不重复的行(重复的行也会输出,仅输出一次)

$ tail /etc/services | awk '!a[$1]++'
aigairserver    21221/tcp               # Services for Air Server
ka-kdp          31016/udp               # Kollective Agent Kollective Delivery
ka-sddp         31016/tcp               # Kollective Agent Secure Distributed Delivery
edi_service     34567/udp               # dhanalakshmi.org EDI Service
axio-disc       35100/tcp               # Axiomatic discovery protocol
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp               # ASSIA CloudCheck WiFi Management keepalive
cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp               # Capture handwritten signatures

# 原理
# 当处理第一条记录时,执行了++,初始值是 0 为假,感叹号取反为真,打印
# 如果再遇到相同的记录,值就会+1,不为 0 为真,取反为假就不打印

还有类似其他方法也可做到去重的功能

# 判断去重
tail /etc/services |awk '{if(a[$1]++)print $1}'

# 三目运算去重
tail /etc/services |awk '{print a[$1]++?$1:"no"}'

# 判断取反,输出不重复的
tail /etc/services |awk '{if(!a[$1]++)print $1}'

计算相同字段某数值字段的和

+=的使用和++类似,+=是将相同下标的值的相加

$ tail /etc/services |awk -F'[ /]+' '{a[$1]+=$2}END{for(v in a)print a[v],v}'
45514 cloudcheck-ping
34567 edi_service
21221 aigairserver
45514 cloudcheck
46998 spremotetablet
31016 ka-sddp
44323 pmwebapi
70200 axio-disc     # 此条为2条重复值合一
31016 ka-kdp
# 以上顺序是for循环后打乱的

# 以空格或者/为分隔符,取值
# 当处理第一条记录时,$1=cloudcheck-ping $2=45514 a[cloudcheck-ping]=45514
# ...
# 当处理第八条记录时,$1=axio-disc $2=35100 a[axio-disc]=35100
# 当处理与第八条$1字段相同的行时,+=的作用才会体现,此时$1=axio-disc $2=35100 a[axio-disc]=35100+35100
# 所以看到了处理结果中的 70200 axio-disc

多维数组

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

SUBSEP 就是上面awk内置变量中提到的,当时并没有做示例

$ awk 'BEGIN{a["x","y"]=123;for(v in a) print v,a[v]}'
xy 123
# 也可以看出其实\034的值就是什么都没有

# 我们可以重新复制 SUBSEP 变量,改变下标默认分隔符:
$ awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a) print v,a[v]}'
x:y 123

# 示例文本
# 统计多个字段重复出现的次数
$ 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

$ cat file | awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}{print a[$1,$2]}END{for (v in a) print v,a[v]}'
1 D-192.168.1.4
1 A-192.168.1.1
2 C-192.168.1.1
2 B-192.168.1.2
# 逻辑分析
a[A,192.168.1.1]=1
a[B,192.168.1.2]=2
a[C,192.168.1.1]=2
a[D,192.168.1.4]=1

内置函数

建议结合下方示例来理解函数的应用

函数 描述
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() 当前时间戳
mktime() 获取指定时间的时间戳
strftime([format [,timestamp[, utc-flag]]]) 格式化输出时间,将时间戳转为字符串

示例

int()

# 截取整数 等同于运算符中的截取整数
$ echo "123abc 456def ghi789 1011jkl1213" | awk 'BEGIN{RS=" "}{print int($0)}'
123
456
0
1011

# 整数计算
$ awk 'BEGIN{print int(10/3)}'
3

sqrt()

# 获取9的平方根
$ awk 'BEGIN{print sqrt(9)}'
3

rand()和stand()

rand()并不是每次运行就是一个随机数,会一直保持一个不变

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

$ awk 'BEGIN{print rand()}'
0.924046
$ awk 'BEGIN{print rand()}'
0.924046

$ awk 'BEGIN{srand();print rand()}'
0.908026
$ awk 'BEGIN{srand();print rand()}'
0.537683
$ awk 'BEGIN{srand();print rand()}'
0.785635

$ awk 'BEGIN{srand();print (rand()*10)}'
4.32129

指定生成范围内整数,反问限于10/100/1000这样的以内

$ awk 'BEGIN{srand();print int(rand()*10)}'
6

asort()和asorti()

asort 对值排序

# 排序数组
$ 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

asorti 对下标进行排序

$ 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

排序逻辑分析

# a[x++]=$0
a[0]=str1
...
a[4]=str5
# s=asort(a,b)
对数组a中的元素进行排序,存储到数组b中,b的下标从1开始,最后将数组b的总行号赋值给s
# s=asorti(a,b)
对数组a中的下标0-4进行排序,存储到数组b中,b的下标从1开始,最后将数组b的总行号赋值给s
# for(i=1;i<=s;i++) print b[i],i
i=1;i<=5成立 print b[1],1  1++
i=2;i<=5成立 print b[2],2  2++
...
i=5;i<=5成立 print b[5],5  5++
i=6;i<=5不成立

sub()和gsub()

sub(r, s [, t]) 对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,但只替换第一个字符

$ tail /etc/services | awk '/axio/{sub(/0/,"1");print $0}'
axio-disc       35110/tcp               # Axiomatic discovery protocol
axio-disc       35110/udp               # Axiomatic discovery protocol

# 匹配axio的行,将匹配行中第一个0替换为1

# $1 的位置没有0,所以没有改变
$ tail /etc/services | awk '/axio/{sub(/0/,"1",$1);print $0}'
axio-disc       35100/tcp               # Axiomatic discovery protocol
axio-disc       35100/udp               # Axiomatic discovery protocol

gsub(r, s [, t]) 对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,否则替换所有字符

$ tail /etc/services | awk '/axio/{gsub(/p/,"C");print $0}'
axio-disc       35100/tcC               # Axiomatic discovery Crotocol
axio-disc       35100/udC               # Axiomatic discovery Crotocol
# 匹配axio的行,将匹配行中所有的p替换为C

# 指定字段的替换与不指定字段的对比
$ echo "0 0 0 a b c" | awk 'gsub(/0/,9){print $0}'
9 9 9 a b c
$ echo "0 0 0 a b c" | awk 'gsub(/0/,9,$2){print $0}'
0 9 0 a b c

在指定行前加一行

# NR为行号,行号为2时,通配符.*匹配整行,整行替换为txt\n&,\n换行,&表示匹配字符,也就是.*
$ seq 5 | awk 'NR==2{sub(/.*/,"txt\n&")}{print $0}'
1
txt
2
3
4
5

在指定行后加一行

$ seq 5 | awk 'NR==2{sub(/.*/,"&\ntxt")}{print $0}'
1
2
txt
3
4
5

index()

index(s, t)返回 s 中字符串 t 的索引位置,0为不存在

# $2的字段中,tcp所在的位置是第7个字符为起始
$ tail /etc/services | awk '{print index($2,"tcp")}'
7
0
7
0
7
0
7
0
7
7
# 为0时不输出
$ tail /etc/services | awk '{a=index($2,"tcp")}{if (a != 0) print $0}'
aigairserver    21221/tcp               # Services for Air Server
ka-sddp         31016/tcp               # Kollective Agent Secure Distributed Delivery
axio-disc       35100/tcp               # Axiomatic discovery protocol
pmwebapi        44323/tcp               # Performance Co-Pilot client HTTP API
cloudcheck      45514/tcp               # ASSIA CloudCheck WiFi Management System
spremotetablet  46998/tcp               # Capture handwritten signatures

length()

length(s) 返回 s 的长度

# 返回端口好为4位的行,因为太多,只显示了5行
$ netstat -anput |grep java | awk -F'[ :]+' '{if (length($5) == 4)print $0}' | head -5
tcp        0      0 127.0.0.1:8247          0.0.0.0:*               LISTEN      4743/java
tcp        0      0 0.0.0.0:8248            0.0.0.0:*               LISTEN      4743/java
tcp        0      0 127.0.0.1:8217          0.0.0.0:*               LISTEN      23683/java
tcp        0      0 127.0.0.1:8377          0.0.0.0:*               LISTEN      22348/java
tcp        0      0 127.0.0.1:8249          0.0.0.0:*               LISTEN      4743/java       

match()

match(s, r [, a]) 测试字符串 s 是否包含匹配 r 的字符串,如果不包含返回 0

和 index 异曲同工

# 包含tcp的返回tcp,否则返回udp
$ tail -n3 /etc/services | awk '{if (match($2,"tcp") != 0) print "tcp";else print "udp"}'
udp
tcp
tcp

split()

split(s, a [, r [, seps] ]) 根据分隔符 seps 将 s 分成数组 a

# 默认分隔符为table或者空格,将一整行的输出放入数组a
# 每处理一行,索引重新从1开始
$ 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
# 每处理一行,索引重新从1开始
$ 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()

substr(s, i [, n]) 截取字符串 s 从 i 开始到长度 n,如果 n 没指定则是剩余部分

$ cat test.txt
Bipolar, adj.:
        Refers to someone who has homes in Nome, Alaska, and Buffalo, New York.

$  cat test.txt | awk '{print substr($0,11,17)}'
dj.:
 someone who has 
# 一行的第11个字符开始,截止到11+17的字符的位置

tolower()和 toupper()

tolower(str) 大写转换小写

$ cat test.txt | awk '{print tolower($0)}'
bipolar, adj.:
        refers to someone who has homes in nome, alaska, and buffalo, new york.

toupper(str) 小写转换大写

$ cat test.txt | awk '{print toupper($0)}'
BIPOLAR, ADJ.:
        REFERS TO SOMEONE WHO HAS HOMES IN NOME, ALASKA, AND BUFFALO, NEW YORK.

systime()

当前时间戳

$ awk 'BEGIN{print systime()}'
1638964410

mktime()

获取指定时间的时间戳

# 2001年10月22日 12:35:09
$ awk 'BEGIN{print mktime("2001 10 22 12 35 09")}'
1003725309

strftime()

strftime([format [,timestamp[, utc-flag]]]) 将时间戳转换为日期和时间

不指定时间戳会输出当前时间

$ awk 'BEGIN{print strftime("现在时间是:%Y-%m-%d %H:%M:%S")}'
现在时间是:2021-12-08 20:05:03

$ awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S",mktime("2001 10 22 12 35 09"))}'
2001-10-22 12:35:09

# 如果 utc-flag 存在且为非零或非空,则结果为 UTC 时区,否则为结果是当地时间
$ awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S",mktime("2001 10 22 12 35 09"),1)}'
2001-10-22 04:35:09
$ awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S",mktime("2001 10 22 12 35 09"),769)}'
2001-10-22 04:35:09
$ awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S",mktime("2001 10 22 12 35 09"),0)}'
2001-10-22 12:35:09

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 打印输出作为命令输入

getline

获取匹配到的下一行

示例文本

$ 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

文本中匹配到的第一行的下一行

$ cat file | awk '/MYSQL/{getline;print}'
C 192.168.1.1 MYSQL

匹配的下一行进行替换

# 结合sub替换函数
$ cat file | awk '/HTTP/{getline;sub(".*","& *");print}'
B 192.168.1.2 HTTP *

$ cat file | awk '/HTTP/{print;getline;sub(".*","& *")}{print}'
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

getline var

读取下一个输入记录并赋值给变量var

示例文本

$ cat a 
a
b
c
$ cat b 
1 one
2 two
3 three

把 a 文件的行追加到 b 文件的行尾

$ awk '{getline line<"a";print $0,line}' b
1 one a
2 two b
3 three c

# 分析
# awk开始处理b文件的第一行
# 1 one 
# getline line<"a" 读取了a文件的第一行并赋值给line
# print $0 为 1 one,line 为 a

把 a 文件的行替换为 b 文件的指定字段,指定字段替换使用gsub函数

$ awk '{getline line<"a";gsub($2,line,$2);print}' b
1 a
2 b
3 c

# 只替换指定行的某一字段
$ awk '{getline line<"a";gsub(/two/,line,$2);print $0}' b
1 one
2 b
3 three

$ awk '{getline line<"a";if (NR==2) gsub($2,line,$2);print $0}' b
1 one
2 b
3 three

command | getline [var]

允许shell命令管道输出到 $0 或 var

# 获取执行 shell 命令后结果的第一行
$ awk 'BEGIN{"seq 5"|getline var;print var}'
1

# 循环输出执行 shell 命令的结果
$ awk 'BEGIN{while("seq 5"|getline)print}'
1
2
3
4
5

next

停止当前处理的输入记录后面动作,可以理解为 流程控制的 continue

不处理指定行

$ seq 5 | awk '{if ($0==3) {next} else {print}}'
1
2
4
5

删除指定行

$  seq 5 | awk 'NR==1{next}{print}'
2
3
4
5
$ seq 5 | awk 'NR!=1{print}'
2
3
4
5

把第一行的内容放到每一行的前面

# 示例文本
$ cat a
hello
1 a 
2 b 
3 c

$ awk 'NR==1{s=$0;next}{print s,$0}' a
hello 1 a 
hello 2 b 
hello 3 c

system(cmd-line)

执行命令和返回状态

$ awk 'BEGIN{if (system("grep root /etc/passwd &> /dev/null")==0)print "yes";else print "no"}'
yes

重定向

tail -n5 /etc/services | awk '{print $2}' > aaa
tail -n5 /etc/services | awk '{print $2 > "bbb"}'

管道符

$ tail -n5 /etc/services | awk '{print $2 | "grep tcp"}'
44323/tcp
45514/tcp
46998/tcp

printf 语句

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

格式:
    printf [format],arguments
format: 输出格式
arguments: 对象

format定义的格式,对 arguments 起作用
format调用与arguments的顺序是一致的

如:
# 当需要调用多对象时,对应顺序写格式化参数即可
$  awk 'BEGIN{printf "%s %s\n","FeiYi","1234.a1"}'
FeiYi 1234.a1
# 第一个%s代表FeiYi,第二个%s,代表1234.a1
即使换了其他的格式化参数也是一个道理
Format 描述
%s 一个字符串
%.ns 输出字符串,n是字符个数
%.nf 保留n位小数
%d 将字符串或者小数格式化为整数,字符串为0,小数也格式化为整数
%x 不带正负号的十六进制,使用a-f表示10-15,也就是-15到15的绝对值
%X 不带正负号的十六进制,使用A-F表示10-15,也就是-15到15的绝对值
%% 输出单个%
%-5s 左对齐,对参数每个字段左对齐,宽度为5
%-4.2f 左对齐,宽度为4,保留2位小数
%5s 右对齐,不加横线表示右对齐

示例

# %s 一个字符串
$ seq 5 | awk '{if ($0!=5) printf "%s,",$0; else print $0}'
1,2,3,4,5
# "%s,",$0  "%s,"是format ,$0是处理对象

# %.ns 输出字符串,n是字符个数
$ awk 'BEGIN{printf "%.2s\n","MuPei"}'
Mu

# %.nf 保留n位小数
$ awk 'BEGIN{printf "%.1f",10/3}'
3.3

# $d 将字符串或者小数格式化为整数,字符串为0,小数也格式化为整数
$ awk 'BEGIN{printf "User:%s \t Pass:%d\n","FeiYi","1234.a1"\n}'
User:FeiYi      Pass:1234

# %x,%X  7B  B=B*16^0=11*1=11 7=7*16^1=7*16=112 112+11=123
$ awk 'BEGIN{printf "%x %X\n",123,123}'
7b 7B

# 左对齐,字段间隔10,下方N-S的间隔是10个字符
$ awk 'BEGIN{printf "%-10s %-10s %-10s\n","Name","Sex","Age"}'
Name       Sex        Age       

# 右对齐,字段间隔10,下方e-x的间隔是10个字符
$ awk 'BEGIN{printf "%10s %10s %10s\n","Name","Sex","Age"}'
      Name        Sex        Age

# 打印表格
$ cat test.awk 
BEGIN {
print  "+--------------------+--------------------+";
printf "|%-20s|%-20s|\n","Id","Name";
print  "+--------------------+--------------------+"
}

$ awk -f test.awk 
+--------------------+--------------------+
|Id                  |Name                |
+--------------------+--------------------+

# 输出表格,内容为用户名和执行shell
$ cat test.awk 
BEGIN {
    print "+-------------------------|--------------------+";
    printf "|%-25s|%-20s|\n","User","Exec Shell";
    print "+-------------------------|--------------------+";
      }
{
    printf "|%-25s|%-20s|\n",$1,$NF  
}
END{
    print "+-------------------------|--------------------+";
    }

$ tail /etc/passwd | awk -F: -f test.awk
+-------------------------|--------------------+
|User                     |Exec Shell          |
+-------------------------|--------------------+
|qemu                     |/sbin/nologin       |
|pulse                    |/sbin/nologin       |
|gdm                      |/sbin/nologin       |
|gnome-initial-setup      |/sbin/nologin       |
|numeric                  |/sbin/false         |
|guochao                  |/bin/bash           |
|renjin                   |/bin/bash           |
|ssjinyao                 |/bin/bash           |
|redis                    |/sbin/nologin       |
|lei                      |/bin/bash           |
+-------------------------|--------------------+

自定义函数

格式:
    function name(参数) {参数要执行的动作}

示例

$ awk 'function feiyi(a,b){return a+b}BEGIN{print feiyi(1,2)}'
3

真实案例

Nginx 日志数据统计

以下内容纯属抄袭,咱学完也不会,不知道是为啥,其实也就是上面用过的照搬就行了,只不过数据不一样而已

# 统计每个ip的访问次数
awk '{a[$1]++}END{for (v in a) print a[v],v}' access.log

# 只统计同一个ip访问在100以上的ip
awk '{a[$1]++}END{for (v in a) if (a[v]>=100) {print a[v],v}}' access.log

# 统计访问ip次数前10
awk '{a[$1]++}END{for (v in a) print a[v],v | "sort -k2 -nr | head -10"}' access.log

# 统计时间段访问最多的ip
# 时间字段在 $4
$ awk '{$4>="[13/Dec/2021:17:00:00" && $4>="[13/Dec/2021:17:50:00" a[$1]++}END{for (v in a) print a[v],v}' access.log

# 统计访问页面次数
# 路径字段在$7
awk '{a[$7]++}END{for (v in a) print a[v],v | "sort -k1 -nr | head -10"}' access.log

# 统计每个 URL 数量和返回内容总大小
$ awk '{a[$7]++;size[$7]+=$10}END{for (v in a) print "访问次数:"a[v],"页面大小:"size[v],"访问路径:"v}' access.log

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

# 状态码为404的ip
awk '{if ($9~/404/)a[$1" "$9]++}END{for(v in a)print v,a[v]}' access.log

文件对比

b文件在a文件相同的记录

$ seq 1 5 > a
$ seq 3 7 > b

# 方法一
$ awk 'FNR==NR{c[$0];next}{if ($0 in c) print FILENAME,$0 }' a b
b 3
b 4
b 5
# 当处理两个文件时{..;next}{..},第一个文件前段处理,第二个文件后段处理
# c[$0]的值为1/2/3/4/5
# if ($0 in c)中的$0是b文件的内容,3 in c/4 in c/5 in c,6/7并不在c的索引中,所以不输出

# 方法二
# 这个好理解一点
awk 'FNR==NR{c[$0]}NR>FNR{if ($0 in c) print FILENAME,$0 }' a b

# 方法三
# 上面的理解之后就都好理解了
awk 'FNR==NR{a[$0]=1;next}{if (a[$0]==1) print FILENAME,$0}' a b

# 方法四,根据文件名判断
awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' a b

# 方法五,和上一个方法类似,后者需同时满足2个条件
awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' a b

b文件与a文件不同的记录

# 以上部分方法取反即可
awk 'FNR==NR{c[$0];next}!($0 in c){print FILENAME,$0 }' a b
awk 'FNR==NR{a[$0]=1;next}{if (a[$0]!=1) print FILENAME,$0}' a b
awk 'FILENAME=="a"{a[$0]=1}(FILENAME=="b" && a[$0]!=1){print FILENAME,$0}' a b
awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' a b

合并文件

将a文件合并到b文件

$ cat a
zhangsan 20
lisi 23
wangwu 29

$ cat b
zhangsan man
lisi woman
wangwu man

$ awk 'FNR==NR{a[$1]=$0;next}{if ($1 in a) print a[$1],$2}' a b
zhangsan 20 man
lisi 23 woman
wangwu 29 man

$  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合并

$ 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

$ awk 'BEGIN{FS=":";OFS=":"}{a[$1]=$2}END{for(v in a)print v,a[v]}' a
192.168.1.1: httpd tomcat
192.168.1.2: httpd postfix
192.168.1.3: mysqld
192.168.1.4: httpd
# a[$1]=a[$1] $2,
# 处理第一行时,a[192.168.1.1]为空,所以a[192.168.1.1]=httpd
# 处理第二行时,a[$1]的值为httpd,所以a[192.168.1.1]=httpd tomcat    

本来还有一些案例,我实在是最近看懵了,我假装感觉我以后会用了

评论




正在载入...
PoweredHexo
HostedAliyun
DNSAliyun
ThemeVolantis
UV
PV
BY-NC-SA 4.0