Shell文本处理三剑客(二)

2018-06-08 17:08 更新

防伪码:锄禾日当午,汗滴禾下土。


8.3 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 表示整个行。

8.3.1  选项

选项  描述

-f program-file  从文件中读取 awk 程序源文件

-F fs  指定 fs 为输入字段分隔符

-v var=value  变量赋值

--posix  兼容 POSIX 正则表达式

--dump-variables=[file]  把 awk 命令时的全局变量写入文件,

默认文件是 awkvars.out

--profile=[file]  格式化 awk 语句到文件,默认是 awkprof.out

8.3.2  模式

常用模式有:

Pattern  Description

BEGIN{ }  给程序赋予初始状态,先执行的工作

END{ }  程序结束之后执行的一些扫尾工作

/regular expression/  为每个输入记录匹配正则表达式

pattern && pattern  逻辑 and,满足两个模式

pattern || pattern  逻辑 or,满足其中一个模式

! pattern  逻辑 not,不满足模式

pattern1, pattern2  范围模式,匹配所有模式 1 的记录,直到匹配到模式 2

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

示例:

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

# vi test.awk

{print $2}

# tail -n3 /etc/services |awk -f test.awk

48049/tcp

48128/tcp

49000/tcp

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

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

# tail -n3 /etc/services |awk '{print $2}'

48049/tcp

48128/tcp

48128/udp

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

# awk -F ':' '{print $1}' /etc/passwd

root

bin

daemon

adm

lp

sync

......

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

# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'

iqobject

iqobject

Matahari Broker

# tail -n3 /etc/services |awk -F'[/#]' '{print $1}'

iqobject 48619

iqobject 48619

matahari 49000

# tail -n3 /etc/services |awk -F'[/#]' '{print $2}'

tcp

udp

tcp

# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'

iqobject

iqobject

Matahari Broker

# tail -n3 /etc/services |awk -F'[ /]+' '{print $2}'

48619

48619

49000

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

分隔符时,就能更方面处理字段了。

3)变量赋值

# awk -v a=123 'BEGIN{print a}'

123

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

# a=123

# awk -v a=$a 'BEGIN{print a}'

123

或使用单引号

# awk 'BEGIN{print '$a'}'

123

4)输出 awk 全局变量到文件

# seq 5 |awk --dump-variables '{print $0}'

1

2

3

4

5

# 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 模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标

题。

例如:打印页眉

# 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 模式是在程序处理完才会执行。

例如:打印页尾

# 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 命令到文件

# 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......

# cat awkprof.out

# 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 的行:

# 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 的行:

# tail /etc/services |awk '/^blp5/{print $0}'

blp5 48129/tcp # Bloomberg locator

blp5 48129/udp # Bloomberg locator

匹配第一个字段是 8 个字符的行:

# 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 的行:

# tail /etc/services |awk '/blp5/ && /tcp/{print $0}'

blp5 48129/tcp # Bloomberg locator

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

# 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

不匹配开头是#和空行:

# awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf

# awk '! /^#|^$/' /etc/httpd/conf/httpd.conf

# awk '/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf

9)匹配范围

# tail /etc/services |awk '/^blp5/,/^com/'

blp5 48129/tcp # Bloomberg locator

blp5 48129/udp # Bloomberg locator

com-bardac-dw 48556/tcp # com-bardac-dw

对匹配范围后记录再次处理,例如匹配关键字下一行到最后一行:

# seq 5 |awk '/3/,/^$/{printf /3/?"":$0"\n"}'

4

5

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

# 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 数字)

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

# seq 5 |awk '/3/{t=1}t'

3

4

5


8.3.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 一样。

# awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5

root x

bin x

daemon x

adm x

lp x

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

# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5 # 中间逗号被换成了 OFS 的默

认值

root x

bin x

daemon x

adm x

lp x

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

# awk 'BEGIN{FS=":";OFS=":"}{print $1,$2}' /etc/passwd |head -n5

root:x

bin:x

daemon:x

adm:x

lp:x

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

# 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 分隔每行,如果想指定以某个字符作为分隔符来处理记录:

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

www.baidu.com

user

test.html

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

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

str01

str04

str07

str10

将输出的换行符替换为+号:

# seq 10 |awk 'BEGIN{ORS="+"}{print $0}'

1+2+3+4+5+6+7+8+9+10+

替换某个字符:

# tail -n2 /etc/services |awk 'BEGIN{RS="/";ORS="#"}{print $0}'

iqobject 48619#udp # iqobject

matahari 49000#tcp # Matahari Broker

3)NF

NF 是字段个数。

# echo "a b c d e f" |awk '{print NF}'

6

打印最后一个字段:

# echo "a b c d e f" |awk '{print $NF}'

f

打印倒数第二个字段:

# echo "a b c d e f" |awk '{print $(NF-1)}'

e

排除最后两个字段:

# echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print $0}'

a b c d

排除第一个字段:

# echo "a b c d e f" |awk '{$1="";print $0}'

b c d e f

4)NR 和 FNR

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

打印行数:

# 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

打印总行数:

# tail -n5 /etc/services |awk 'END{print NR}'

5

打印第三行:

# tail -n5 /etc/services |awk 'NR==3'

iqobject 48619/tcp # iqobject

打印第三行第二个字段:

# tail -n5 /etc/services |awk 'NR==3{print $2}'

48619/tcp

打印前三行:

# 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 的区别:

# 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

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

两个文件时,是合并到一起处理。

# 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 开始

# awk 'BEGIN{print ARGC}' 1 2 3

4

# awk 'BEGIN{print ARGV[0]}'

awk

# awk 'BEGIN{print ARGV[1]}' 1 2

1

# awk 'BEGIN{print ARGV[2]}' 1 2

2

6)ARGIND

ARGIND 是当前正在处理的文件索引值,第一个文件是 1,第二个文件是 2,以此类推,从而可以通

过这种方式判断正在处理哪个文件。

# awk '{print ARGIND,$0}' a b

1 a

1 b

1 c

2 c

2 d

2 e

# 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 调用系统变量。

# awk 'BEGIN{print ENVIRON["HOME"]}'

/root

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

# awk 'BEGIN{print ENVIRON["a"]}'

# export a

# awk 'BEGIN{print ENVIRON["a"]}'

123

8)FILENAME

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

9)忽略大小写

# echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'

A

a

等于 1 代表忽略大小写。

8.3.4 4 操作符

运算符  描述

(....)  分组

$  字段引用

++ --  递增和递减

+ - !  加号,减号,和逻辑否定

* / %  乘,除和取余

+ -  加法,减法

| |&  管道,用于 getline,print 和 printf

< > <= >= != ==  关系运算符

~ !~  正则表达式匹配,否定正则表达式匹配

in  数组成员

&& ||  逻辑 and,逻辑 or

?:  简写条件表达式:

expr1 ? expr2 : expr3

第一个表达式为真,执行 expr2,否则执行 expr3

= += -= *= /= %= ^=  变量赋值运算符

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

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

举例测试:

# awk 'BEGIN{n=0;if(n)print "true";else print "false"}'

false

# awk 'BEGIN{s="";if(s)print "true";else print "false"}'

false

# awk 'BEGIN{if(s)print "true";else print "false"}'

false

示例:

1)截取整数

# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print +$0}'

123

0

123

# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print -$0}'

-123

0

-123

2)感叹号

打印奇数行:

# seq 6 |awk 'i=!i'

1

3

5

读取第一行,i 是未定义变量,也就是 i=!0,!取反意思。感叹号右边是个布尔值,0 或空字符串为

假,非 0 或非空字符串为真,!0 就是真,因此 i=1,条件为真打印当前记录。

没有 print 为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。

读取第二行,因为上次 i 的值由 0 变成了 1,此时就是 i=!1,条件为假不打印。

读取第三行,上次条件又为假,i 恢复初始值 0,取反,继续打印。以此类推...

可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。

打印偶数行:

# seq 6 |awk '!(i=!i)'

2

4

6

2)不匹配某行

# 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)乘法和除法

# seq 5 |awk '{print $0*2}'

2

4

6

8

10

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

1

0

1

0

1

打印偶数行:

# seq 5 |awk '$0%2==0{print $0}'

2

4

打印奇数行:

# seq 5 |awk '$0%2!=0{print $0}'

1

3

5

4)管道符使用

# seq 5 |shuf |awk '{print $0|"sort"}'

1

2

3

4

5

5)正则表达式匹配

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

3

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

1

2

4

5

# seq 5 |awk '$0~/[34]/{print $0}'

3

4

# seq 5 |awk '$0!~/[34]/{print $0}'

1

2

5

# seq 5 |awk '$0~/[^34]/{print $0}'

1

2

5

6)判断数组成员

# awk 'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}' </dev/null

yes

7)三目运算符

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

yes

# seq 3 |awk '{print $0==2?"yes":"no"}'

no

yes

no

替换换行符为逗号:

# seq 5 |awk '{print n=(n?n","$0:$0)}'

1

1,2

1,2,3

1,2,3,4

1,2,3,4,5

# seq 5 |awk '{n=(n?n","$0:$0)}END{print n}'

1,2,3,4,5

说明:读取第一行时,n 没有变量,为假输出$0 也就是 1,并赋值变量 n,读取第二行时,n 是 1 为

真,输出 1,2 以此类推,后面会一直为真。

每三行后面添加新一行:

# seq 10 |awk '{print NR%3?$0:$0 "\ntxt"}'

1

2

3

txt

4

5

6

txt

7

8

9

txt

10

两行合并一行:

# seq 6 |awk '{printf NR%2!=0?$0" ":$0" \n"}'

1 2

3 4

5 6

# seq 6 |awk 'ORS=NR%2?" ":"\n"'

1 2

3 4

5 6

# seq 6 |awk '{if(NR%2)ORS=" ";else ORS="\n";print}'

8)变量赋值

字段求和:

# seq 5 |awk '{sum+=1}END{print sum}'

5

# seq 5 |awk '{sum+=$0}END{print sum}'

15

8.3.5 5 流程控制

1 1 )f if  语句

格式:if (condition) statement [ else statement ]

单分支:

# 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

多分支:

# cat file

1 2 3

4 5 6

7 8 9

# 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 2 )e while  语句

格式:while (condition) statement

遍历打印所有字段:

# awk '{i=1;while(i<=NF){print $i;i++}}' file

1

2

3

4

5

6

7

8

9

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

3 3 )r for  语句 C C  语言风格

格式:for (expr1; expr2; expr3) statement

遍历打印所有字段:

# 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

倒叙打印文本:

# awk '{for(i=NF;i>=1;i--)print $i}' file

3

2

1

6

5

4

9

8

7

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

# awk '{for(i=NF;i>=1;i--){printf $i" "};print ""}' file # print 本身就会新打印一行

3 2 1

6 5 4

9 8 7

# awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' file

3 2 1

6 5 4

6 5 4

9 8 7

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

排除第一行:

# awk '{for(i=2;i<=NF;i++){printf $i" "};print ""}' file

2 3

5 6

8 9

排除第二行:

# awk '{for(i=1;i<=NF-1;i++){printf $i" "};print ""}' file

1 2

4 5

7 8

IP 加单引号:

# 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 4 )r for  语句遍历 数组

格式:for (var in array) statement

# 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 5 )k break 和 和 e 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

6 6 )删除数组和元素

格式:

delete array[index] 删除数组元素

delete array 删除数组

# 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[3];for(v in a)print v,a[v]}'

4 str4

5 str5

1 str1

2 str2

7 7 )t exit  语句

格式:exit [ expression ]

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

# seq 5 |awk '{if($0~/3/)exit (123)}'

# echo $?

123

8.3.6 6 数组

数组:存储一系列相同类型的元素,键/值方式存储,通过下标(键)来访问值。

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

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

序。

数组格式:array[index]=value

1)自定义数组

# awk 'BEGIN{a[0]="test";print a[0]}'

test

2)通过 NR 设置记录下标,下标从 1 开始

# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}'

systemd-network

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

zabbix

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

user

3)通过 for 循环遍历数组

# 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

# 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)通过++方式作为下标

# 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)使用字段作为下标

# 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)统计相同字段出现次数

# 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

# 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

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

2 blp5

第一个字段作为下标,值被++初始化是 0,每次遇到下标(第一个字段)一样时,对应的值就会被

+1,因此实现了统计出现次数。

想要实现去重的的话就简单了,只要打印下标即可。

7)统计 TCP 连接状态

# netstat -antp |awk '/^tcp/{a[$6]++}END{for(v in a)print a[v],v}'

9 LISTEN

6 ESTABLISHED

6 TIME_WAIT

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

# 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)去重

只打印重复的行:

# 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

不打印重复的行:

# 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 为真,取反为假就不打印。

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

isnetserv

blp5

com-bardac-dw

iqobject

使用三目运算:

# tail /etc/services |awk '{print a[$1]++?$1:"no"}'

no

no

isnetserv

no

blp5

no

com-bardac-dw

no

iqobject

no

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

3gpp-cbsp

isnetserv

blp5

com-bardac-dw

iqobject

matahari

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

# 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。

示例:

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

xy 123

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

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

x:y 123

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

# cat a

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

# awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(v in a)print a[v],v}' a

1 D-192.168.1.4

1 A-192.168.1.1

2 C-192.168.1.1

2 B-192.168.1.2


本文出自 “一盏烛光” 博客,谢绝转载!

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号