跳至主要內容

Awk案例入门

PaperDragon...大约 25 分钟

Awk案例入门

简介

  • awk 擅长对列进行操作/进行数据信息操作
  • awk 的基本使用(高级使用在shell)
  • awk把文本文档看作是数据库,每一行看作一条数据库中的记录,可以指定数据列的分隔符,默认的分隔符是”\t”,即Tab。
  • awk工作流程是这样的:读入有’\n’换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域, 1 表 示
  • 第 一 个 域 , 1表示第一个域, 1表示第一个域,n表示第n个域。默认域分隔符是”空白键” 或 “[tab]键”
  • awk的执行模式是: awk '{pattern + action}' {filenames}
sed [参数] ‘条件-处理方式 文件

sed [参数]  ’模式-动作'  文件

awk [-F|-f|-v] ‘BEGIN{} //{command1; command2} END{}file

awk 变量

变量描述
$n当前记录的第n个字段,字段间由FS分隔
$0完整的输入记录(默认就是文本的每一行)
ARGC命令行参数的数目
ARGIND命令行中当前文件的位置(从0开始算)
ARGV包含命令行参数的数组
CONVFMT数字转换格式(默认值为%.6g)ENVIRON环境变量关联数组
ERRNO最后一个系统错误的描述
FIELDWIDTHS字段宽度列表(用空格键分隔)
FILENAME当前文件名
FS字段分隔符(默认是任何空格) Field Separator
IGNORECASE如果为真,则进行忽略大小写的匹配
NF一条记录的字段的数目 Number for Field,表示的是浏览记录的域的个数,即被分隔符切开以后,一共有多少列
$NF从后往前数, N F 表 示 的 最 后 一 个 F i e l d ( 列 ) , 即 输 出 最 后 一 个 字 段 的 内 容 , 倒 数 第 二 列 ‘ NF 表示的最后一个Field(列),即输出最后一个字段的内容,倒数第二列NF表示的最后一个Field(列),即输出最后一个字段的内容,倒数第二列‘(NF-1)
NR已经读出的记录数,就是行号,从1开始 Number of Record
FNR各文件分别计数的行号 FILE Number of Record
OFMT数字的输出格式(默认值是%.6g)
OFS输出记录分隔符(输出换行符),输出时用指定的符号代替换行符 Output Field Separator
ORS输出记录分隔符(默认值是一个换行符) Output Record Separator
RLENGTH由match函数所匹配的字符串的长度
RS记录分隔符(默认是一个换行符) Record Separator
RSTART由match函数所匹配的字符串的第一个位置
SUBSEP数组下标分隔符(默认值是/034)
~匹配,与==相比不是精确比较
!~不匹配,不精确比较
==等于,必须全部相等,精确比较
!=不等于,精确比较
&&逻辑与
||逻辑或
+匹配时表示1个或1个以上
/[0-9][0-9]+/两个或两个以上数字
/[0-9][0-9]*/一个或一个以上数字

$n

$1这种内置变量的外侧不能加入双引号,否则$1会被当做文本输出

[root@boy ~]# cat test 
abc 123 iuy ddd
8ua 456 auv php 7y7

[root@boy ~]# awk '{print $1}' test 
abc
8ua
[root@boy ~]# awk '{print "$1"}' test
$1
$1

awk '{print $1; print $2}' test
=
awk '{print $1} {print $2}'

    1
    2
    3

OFS

[root@boy ~]# cat test 
abc 123 iuy ddd
8ua 456 auv php 7y7

[root@boy ~]# awk -v FS=' ' -v OFS='---' '{print $1,$2}' test
abc---123
8ua---456

FNR

[root@boy ~]# cat test test1
abc 123 iuy ddd
8ua 456 auv php 7y7
abc 123 iuy ddd
8ua 456 auv php 7y7

# awk处理多个文件的时候,如果使用NR显示行号,那么,多个文件的所有行会按照顺序进行排序
[root@boy ~]# awk '{print NR,$0}' test test1
1 abc 123 iuy ddd
2 8ua 456 auv php 7y7
3 abc 123 iuy ddd
4 8ua 456 auv php 7y7

# FNR的作用就是当awk处理多个文件时,分别对每个文件的行数进行计数
[root@boy ~]# awk '{print FNR,$0}' test test1
1 abc 123 iuy ddd
2 8ua 456 auv php 7y7
1 abc 123 iuy ddd
2 8ua 456 auv php 7y7

RS

# RS是输入行分隔符,如果不指定,默认的”行分隔符”就是我们所理解的”回车换行
# 假设,我们不想以默认的”回车换行”作为”行分隔符”,而是想使用空格作为所谓的行分隔符,也就是说,我们想让awk认为,每遇到一个空格,就换行,换句话说,我们想让awk以为每次遇到一个空格就是新的一行

[root@boy ~]# awk '{print NR,$0}' test 
1 abc 123 iuy ddd
2 8ua 456 auv php 7y7
[root@boy ~]# awk -v  RS=" " '{print NR,$0}' test 
1 abc
2 123
3 iuy
4 ddd
8ua
5 456
6 auv
7 php
8 7y7

ORS

# awk将人类眼中的”回车换行”,当做”输出行分隔符”,此时,awk的”世界观”与人类的”世界观”是相同的。现在,我们改变一下awk的想法,我们让awk认为,”+++”才是真正的输出行分隔符

[root@boy ~]# awk '{print NR,$0}' test 
1 abc 123 iuy ddd
2 8ua 456 auv php 7y7

[root@boy ~]# awk -v ORS="+++" '{print NR,$0}' test
1 abc 123 iuy ddd+++2 8ua 456 auv php 7y7+++[root@boy ~]#

[root@boy ~]# awk -v RS=" " -v ORS="+++" '{print NR,$0}' test
1 abc+++2 123+++3 iuy+++4 ddd
8ua+++5 456+++6 auv+++7 php+++8 7y7
+++[root@boy ~]# 

FILENAME

# FILENAME这个内置变量,从字面上,就能看出是什么意思,没错,就是显示文件名,演示效果如下
[root@boy ~]# cat test test1
abc 123 iuy ddd
8ua 456 auv php 7y7
abc 123 iuy ddd
8ua 456 auv php 7y7

[root@boy ~]# awk '{print FILENAME,FNR,$0}' test test1
test 1 abc 123 iuy ddd
test 2 8ua 456 auv php 7y7
test1 1 abc 123 iuy ddd
test1 2 8ua 456 auv php 7y7

ARGC&ARGV

# ARGV内置变量表示的是一个数组,这个数组中保存的是命令行所给定的参数
# ARGV内置变量表示的是一个数组,既然是数组,就需要用下标的方式,引用对应元素的值,因为数组的索引都是从0开始的,所以,ARGV[1]表示引用ARGV数组中的第二个元素的值,从返回结果可以看出,ARGV[1]对应的值为test,同理,我们又使用第三条命令,多打印了一个ARGV[2]的值,发现ARGV[2]对应的值为test1,这个时候,你明白ARGV内置变量的含义了吗,说白了,ARGV内置变量表示的是:所有参数组成的数组,ARGV[0]: awk本身

[root@boy ~]# awk 'BEGIN{print "aaa"}' test test1
aaa
[root@boy ~]# awk 'BEGIN{print "aaa",ARGV[1]}' test test1
aaa test
[root@boy ~]# awk 'BEGIN{print "aaa",ARGV[1],ARGV[2]}' test test1
aaa test test1
[root@boy ~]# awk 'BEGIN{print "aaa",ARGV[1],ARGV[2],ARGV[0]}' test test1
aaa test test1 awk
# awk、test1、test2,这三个参数作为数组的元素存放于ARGV中,现在,而ARGC则表示参数的数量,也可以理解为ARGV数组的长度
[root@boy ~]# awk 'BEGIN{print "aaa",ARGV[1],ARGV[2],ARGV[0],ARGC}' test test1
aaa test test1 awk 3

自定义变量

有两种方法可以自定义变量

  • -v varname=value 变量名区分字符大小写
  • 在program中直接定义
# -v varname=value 自定义变量
[root@boy ~]# awk -v myVar="test var" 'BEGIN{print myVar}'
test var

# 当我们需要在awk中引用shell中的变量的时候,则可以通过方法一间接的引用
[root@boy ~]# aaa=111
[root@boy ~]# awk -v myvar=$aaa 'BEGIN{print myvar}'
111
# 直接在program中定义即可,但是注意,变量定义与动作之间需要用分号”;”隔开

[root@boy ~]# awk 'BEGIN{myvar="ttt"; print myvar}'
ttt

# 定义多个变量
[root@boy ~]# awk 'BEGIN{myvar1="ttt"; myvar2="ttt";  print myvar1,myvar2}'
ttt ttt

awk -f

-f指定脚本文件

awk -f script.awk  file

BEGIN{
FS=":"

}

{print $1}               //效果与awk -F":" '{print $1}'相同,只是分隔符使用FS在代码自身中指定

awk -F

# 选取二个作为分隔符,用:和,二个作为分隔符
awk -F ":|," XXX  

# 将每一行的前二个字段,分行输出,进一步理解一行一行处理文本
awk -F: '{print $1; print $2}'   /etc/passwd                   

# 输出字段1,3,6,以制表符作为分隔符
awk  -F: '{print $1,$3,$6}' OFS="\t" /etc/passwd
# -F指定分隔符,$1 指指定分隔符后,第一个字段,$3第三个字段, \t是制表符
# 一个或多个连续的空格或制表符看做一个定界符,即多个空格看做一个空格

# 姓氏是zhang的人,显示他的第二次捐款金额及他的名字,[ :]+这个是正则表达式,+表示一个或多个,这里就表示一个或多个空格或冒号
awk -F "[ :]+" '/Zhang/{print $1,$2,$(NF-1)}' reg.txt

awk -F":" '{print $1 $3}'  /etc/passwd                       # $1与$3相连输出,不分隔

awk -F":" '{print $1,$3}'  /etc/passwd                      # 多了一个逗号,$1与$3使用空格分隔

awk -F":" '{print $1 " " $3}'  /etc/passwd                  # $1与$3之间手动添加空格分隔

awk -F":" '{print "Username:" $1 "\t\t Uid:" $3 }' /etc/passwd   # 自定义输出  

awk -F: '{print NF}' /etc/passwd                                 # 显示每行有多少字段

awk -F: '{print $NF}' /etc/passwd                               # 将每行第NF个字段的值打印出来

 awk -F: 'NF==4 {print }' /etc/passwd                       # 显示只有4个字段的行

awk -F: 'NF>2{print $0}' /etc/passwd                        # 显示每行字段数量大于2的行

awk '{print NR,$0}' /etc/passwd                             # 输出每行的行号

awk -F: '{print NR,NF,$NF,"\t",$0}' /etc/passwd             # 依次打印行号,字段数,最后字段值,制表符,每行内容

awk -F: 'NR==5{print}'  /etc/passwd                         # 显示第5行

awk -F: 'NR==5 || NR==6{print}'  /etc/passwd                # 显示第5行和第6行

awk -F: '{if($3>100) print "large"; else print "small"}' /etc/passwd

#  ID大于100,A加1,否则B加1
awk -F: 'BEGIN{A=0;B=0} {if($3>100) {A++; print "large"} else {B++; print "small"}} END{print A,"\t",B}' /etc/passwd                         
awk -F: '{if($3<100) next; else print}' /etc/passwd                         # 小于100跳过,否则显示

awk -F: 'BEGIN{i=1} {if(i<NF) print NR,NF,i++ }' /etc/passwd   

awk -F: 'BEGIN{i=1} {if(i<NF) {print NR,NF} i++ }' /etc/passwd

# 另一种形式

awk -F: '{print ($3>100 ? "yes":"no")}'  /etc/passwd 

awk -F: '{print ($3>100 ? $3":\tyes":$3":\tno")}'  /etc/passwd
awk -F'[:#]' '{print NF}'  helloworld.sh          # 指定多个分隔符: #,输出每行多少字段

awk -F'[:#]' '{print $1,$2,$3,$4,$5,$6,$7}' OFS='\t' helloworld.sh   //制表符分隔输出多字段

awk 匹配

# 第一种语法是正则模式的语法,表示被正则表达式匹配到的行,将会执行对应的动作
 awk '/正则表达式/{动作}'  /xxx/

# 二种语法是行范围模式的语法,它表示,从被正则1匹配到的行开始,到被正则2匹配到的行结束,之间的所有行都会执行对应的动作,所以,这种模式被称为行范围模式,因为它对应的是一个范围以内的所有行,但是需要注意的是,在行范围模式中,不管是正则1,还是正则2,都以第一次匹配到的行为准,就像上述示例中,即使Lee在第2行与第3行中都出现了,但是由于正则1先匹配到第2行中的lee,所以,最终打印出的内容从第2行开始,即使Kevin在第5行与第7行中都出现了,但是由于Kevin第一次出现在第5行,所以最终打印出的内容到第5行结束,也就是说,最终打印出了第2行到第5行以内的所有行
 awk '/正则1/,/正则2/{动作}'  /xxx/
# 匹配代码块,//纯字符匹配   !//纯字符不匹配   ~//字段值匹配    !~//字段值不匹配   ~/a1|a2/字段值匹配a1或a2   

cat /etc/passwd | awk '$NF !~/nologin|sync|shutdown|halt/'

lastlog | awk '$1 ~/'$i'/ {printf "用户名: %5s 端口:%5s 源IP:%5s 最后登录时间:%s-%s-%s日 %s\n",$1,$2,$3,$NF,$(NF-4),$(NF-3),$(NF-2)}'

awk '/mysql/' /etc/passwd

awk '/mysql/{print }' /etc/passwd

awk '/mysql/{print $0}' /etc/passwd                   //三条指令结果一样

awk '!/mysql/{print $0}' /etc/passwd                  //输出不匹配mysql的行

awk '/mysql|mail/{print}' /etc/passwd

awk '!/mysql|mail/{print}' /etc/passwd

awk -F: '/mail/,/mysql/{print}' /etc/passwd         //区间匹配

awk '/[2][7][7]*/{print $0}' /etc/passwd               //匹配包含27为数字开头的行,如27,277,2777...

awk -F: '$1~/mail/{print $1}' /etc/passwd           //$1匹配指定内容才显示

awk -F: '{if($1~/mail/) print $1}' /etc/passwd     //与上面相同

awk -F: '$1!~/mail/{print $1}' /etc/passwd          //不匹配

awk -F: '$1!~/mail|mysql/{print $1}' /etc/passwd        

# ~//字段值匹配
awk -F: '$1~/mail/' /etc/passwd#显示$1字段匹配mail的行
awk -F: '$1~/mail|sshd/' /etc/passwd#显示$1字段匹配mail或sshd的行

# !~//字段值不匹配
awk -F: '$1!~/mail/' /etc/passwd#显示$1字段不匹配mail的行
awk -F: '$1!~/mail|sshd/' /etc/passwd#显示$1字段不匹配mail或sshd的行

awk begin&end

  • BEGIN 模式:读入第一行文本之前执行的语句,一般用来初始化操作(后续读取不会执行了,只第一次执行)
  • END 模式:处理完最后以行文本后执行,一般用来处理输出结果:被放置在所有的数据读取完成以后执行
  • {}: 逐行处理

exit

它表示不再执行awk命令,相当于退出了当前的awk命,当在awk中使用了END模式时,exit的作用并不是退出整个awk命令,而是直接执行END模式中的动作
# 当在awk中执行了exit语句以后,之后的所有动作都不会再被执行,相当于退出了整个awk命令
[root@boy ~]# awk 'BEGIN{print 1 ; exit ; print 2 ; print 3 }'
1

# 在awk中使用了END模式时,exit的作用并不是退出整个awk命令,而是直接执行END模式中的动作
[root@boy ~]# awk 'BEGIN{print "start"; exit} { print "123" } END{ print "456" }'
start
456

next

next命令即可让awk直接从下一行开始处理,换句话说就是,next命令可以促使awk不对当前行执行对应的动作,而是直接处理下一行
next与continue有些类似,只是,continue是针对”循环”而言的,continue的作用是结束”本次循环”,而next是针对”逐行处理”而言的,next的作用是结束”对当前行的处理”,从而直接处理”下一行”,其实,awk的”逐行处理”也可以理解成为一种”循环”,因为awk一直在”循环”处理着”每一行”
[root@boy ~]# awk '{ if(NR>1){ next } print $0 }' /etc/passwd
root:x:0:0:root:/root:/bin/bash
=
[root@boy ~]# awk '{ if(NR>1){ next } ; print $0 }' /etc/passwd
root:x:0:0:root:/root:/bin/bash

实例

# 文件开头加REDHAT,末尾加WESTOS,打印行号和内容
 awk -F: 'BEGIN {print "REDHAT"} {print NR;print} END {print "WESTOS"}' passwd 

[root@server19 mnt]# awk 'BEGIN{i=0}{i+=NF}END{print i}' test.txt 
7 
# 统计Nginx连接数
 netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a,S[a]}'

awk if

if 分支

# IF语句,必须用在{}中,且比较内容用()扩起来

awk -F: '{if($1~/mail/) print $1}' /etc/passwd                                       //简写

awk -F: '{if($1~/mail/) {print $1}}'  /etc/passwd                                   //全写

awk -F: '{if($1~/mail/) {print $1} else {print $2}}' /etc/passwd            //if...else...
# 统计登录shell为bash的用户
 awk -F: 'BEGIN{i=0}{if($7~/bash$/){i++}}END{print i}' /etc/passwd
# 统计/etc/passwd下uid小于500的用户个数
 awk -F: 'BEGIN{i=0}{if($3<500){i++}}END{print i}' /etc/passwd

if 双分支

# 统计uid小于等于500和大于500的用户个数
 awk -F: 'BEGIN{i=0;j=0}{if($3<=500){i++}else{j++}}END{print i,j}' /etc/passwd

实例

# ”if”对应的大括号中有多条语句,所以”if”语法中的大括号不能省略,但是,如果”if”对应的大括号中只有一条命令,那么”if”对应的大括号则可以省略

awk '{ if(NR == 1){ print $0 }}' xxx
=
awk '{ if(NR == 1) print $0}'  xxx


# 判断出/etc/passwd文件中的哪些用户属于系统用户,哪些用户属于普通用户
awk -F ":" '{ if($3 < 500) {print $1,"系统用户"}} else{ print $1,"普通用户"}' xxx

awk 'NR != 1 {if($2 <= 30){print $1,"年轻人"} else if($2>=30 && $2<=50){print $1,"中年人"} else{print $1,"老年人"}}' xxx

awk for

  • continue的作用:跳出”当前”循环

  • break的作用:跳出”整个”循环

# 生成1-5序列
 awk 'BEGIN{for(i=1;i<=5;i++){print i}}'

# 
 awk 'BEGIN{ for(i=1;i<=6;i++){print i}}'


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


awk while

  • continue的作用:跳出”当前”循环
  • break的作用:跳出”整个”循环

while

# 实例一:
 awk 'i=1 {} BEGIN {while (i<3) {++i;print i}}' test.txt 

# 实例二:
 awk 'BEGIN {do {++i;print i} while (i<3)}' test.txt 


awk -F: 'BEGIN{i=1} {while(i<NF) print NF,$i,i++}' /etc/passwd 


[root@boy ~]# awk -v i=1 'BEGIN{ while(i<=5){print i; i++}}'


[root@boy ~]# awk 'BEGIN{i=1;while(i<=5){print i;i++}}'

do while

do…while循环则是无论是否满足条件,都会先执行一遍do对应的代码,然后再判断是否满足while中对应的条件,满足条件,则执行do对应的代码,如果不满足条件,则不再执行do对应的代码

[root@boy ~]# awk 'BEGIN{i=1; do{print "test",i;i++}while(i<=5)}'
test 1
test 2
test 3
test 4
test 5

# i如果没有默认赋值,初始为空,i++后从1开始
[root@boy ~]# awk 'BEGIN{do{print "test",i;i++}while(i<=5)}'
test 
test 1
test 2
test 3
test 4
test 5

awk 条件表达式

# 条件表达式    ==   !=   >   >=  

awk -F":" '$1=="mysql"{print $3}' /etc/passwd  

awk -F":" '{if($1=="mysql") print $3}' /etc/passwd          //与上面相同 

awk -F":" '$1!="mysql"{print $3}' /etc/passwd                 //不等于

awk -F":" '$3>1000{print $3}' /etc/passwd                      //大于

awk -F":" '$3>=100{print $3}' /etc/passwd                     //大于等于

awk -F":" '$3<1{print $3}' /etc/passwd                            //小于

awk -F":" '$3<=1{print $3}' /etc/passwd                         //小于等于

awk 逻辑运算符

# 逻辑运算符 ,&& || 

awk -F: '$1~/mail/ && $3>8 {print }' /etc/passwd         //逻辑与,$1匹配mail,并且$3>8

awk -F: '{if($1~/mail/ && $3>8) print }' /etc/passwd

awk -F: '$1~/mail/ || $3>1000 {print }' /etc/passwd       //逻辑或

awk -F: '{if($1~/mail/ || $3>1000) print }' /etc/passwd 

awk 数值运算



awk -F: '$3 > 100' /etc/passwd    

awk -F: '$3 > 100 || $3 < 5' /etc/passwd  

awk -F: '$3+$4 > 200' /etc/passwd

awk -F: '/mysql|mail/{print $3+10}' /etc/passwd                    # 第三个字段加10打印 

awk -F: '/mysql/{print $3-$4}' /etc/passwd                         # 减法

awk -F: '/mysql/{print $3*$4}' /etc/passwd                         # 求乘积

awk '/MemFree/{print $2/1024}' /proc/meminfo                       # 除法

awk '/MemFree/{print int($2/1024)}' /proc/meminfo                  # 取整
# 计算/home目录下,普通文件的大小,使用KB作为单位
ls -l|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is:",sum/1024,"KB"}'

ls -l|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is:",int(sum/1024),"KB"}'      # int是取整的意思


# 统计netstat -anp 状态为LISTEN和CONNECT的连接数量分别是多少
netstat -anp|awk '$6~/LISTEN|CONNECTED/{sum[$6]++} END{for (i in sum) printf "%-10s %-6s %-3s \n", i," ",sum[i]}'

# 统计/home目录下不同用户的普通文件的总数是多少?
ls -l|awk 'NR!=1 && !/^d/{sum[$3]++} END{for (i in sum) printf "%-6s %-5s %-3s \n",i," ",sum[i]}'   

# 统计/home目录下不同用户的普通文件的大小总size是多少?
ls -l|awk 'NR!=1 && !/^d/{sum[$3]+=$5} END{for (i in sum) printf "%-6s %-5s %-3s %-2s \n",i," ",sum[i]/1024/1024,"MB"}'
# 输出成绩表
[root@localhost home]# cat test0 
Marry   2143 78 84 77
Jack    2321 66 78 45
Tom     2122 48 77 71
Mike    2537 87 97 95
Bob     2415 40 57 62

awk 'BEGIN{math=0;eng=0;com=0;printf "Lineno.   Name    No.    Math   English   Computer    Total\n";printf "------------------------------------------------------------\n"}{math+=$3; eng+=$4; com+=$5;printf "%-8s %-7s %-7s %-7s %-9s %-10s %-7s \n",NR,$1,$2,$3,$4,$5,$3+$4+$5} END{printf "------------------------------------------------------------\n";printf "%-24s %-7s %-9s %-20s \n","Total:",math,eng,com;printf "%-24s %-7s %-9s %-20s \n","Avg:",math/NR,eng/NR,com/NR}' test0

awk 数组

数组遍历

[root@boy ~]# cat test.sh 
#!/bin/awk

BEGIN{
 array[1]="boy1";
 array[2]="boy2";
 array[3]="boy3";
 for(key in array)
  print key,array[key];
}
[root@boy ~]# awk -f test.sh 
1 boy1
2 boy2
3 boy3

# 和下面效果一样,如果for只有后边只有一条命令{}可以不用加
[root@boy ~]# awk 'BEGIN{ array[1]="boy1"; array[2]="boy2"; array[3]="boy3"; for(key in array){print key,array[key]}}'
1 boy1
2 boy2
3 boy3

[root@boy ~]# cat test
1 test1
2 test2
[root@boy ~]# awk '{S[$1]=$2}END{for(key in S) print key,S[key]}' test
1 test1
2 test2
# 统计每个域名次数
[root@boy ~]# cat test
http://www.a.com/index.html
http://www.x.com/index.html
http://www.y.com/index.html
http://www.z.com/index.html
http://www.z.com/index.html
http://www.a.com/index.html
http://www.b.com/index.html
http://www.d.com/index.html
http://www.c.com/index.html
http://www.r.com/index.html
http://www.r.com/index.html

[root@boy ~]# awk -F"/" '{S[$3]=S[$3]+1}END{for(key in S) print S[key],key}' test
1 www.c.com
1 www.y.com
2 www.r.com
1 www.x.com
1 www.d.com
2 www.z.com
1 www.b.com
2 www.a.com

[root@boy ~]# awk -F"/" '{S[$3]++}END{for(key in S) print S[key],key}' test
1 www.c.com
1 www.y.com
2 www.r.com
1 www.x.com
1 www.d.com
2 www.z.com
1 www.b.com
2 www.a.com
# 统计网络套接字情况
[root@boy ~]# netstat -tunlp | awk 'NR>2 && NF==7 && /^tcp/ {S[$6]++}END{for(key in S){print key,S[key]}}' | sort -rn -k2 | head
LISTEN 4

实例

netstat -anp|awk 'NR!=1{a[$6]++} END{for (i in a) print i,"\t",a[i]}'

netstat -anp|awk 'NR!=1{a[$6]++} END{for (i in a) printf "%-20s %-10s %-5s \n", i,"\t",a[i]}'

awk 分隔符

  • awk内置变量FS可以用于指定输入分隔符,但是在使用变量时,需要使用-v选项,用于指定对应的变量,比如 -v FS=’#’,效果与-F一样
  • 内置变量OFS来设定awk的输出分隔符,当然,使用变量的时候要配合使用-v选项
[root@boy ~]# cat test 
abc 123 iuy ddd
8ua 456 auv php 7y7

[root@boy ~]# awk -v FS=' ' -v OFS='---' '{print $1,$2}' test
abc---123
8ua---456

# 输出分隔符OFS,输出字段6匹配WAIT的行,其中输出每行行号,字段4,5,6,并使用制表符分割字段
awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt

awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt     

awk 内置函数

awk 三目运算符

  • 条件 ? 结果1 : 结果2
  • 在awk中,0或者空字符串表示”假”,非0值或者非空字符串表示”真”
# ”$3<500″就是上述语法中的”条件”,”系统用户”就是上述语法中”?”后面的”结果1″,”普通用户”就是上述语法中”:”后面的”结果2″  ,同时,在上例中我们使用usertype变量接收了三元运算后的返回值,所以,当条件成立时,usertype变量被赋值为”系统用户”,当条件不成立时,usertype变量被赋值为”普通用户”
[root@boy ~]# awk -F: '{ usertype=$3<500?"系统用户":"普通用户"; print $1,usertype}' /etc/passwd
root 系统用户
bin 系统用户


# 可见a,b默认是空的
[root@boy ~]# awk -F: 'BEGIN{print a;b} {$3<500?a++:b++;print $0,a,b}END{print a,b}' /etc/passwd

root:x:0:0:root:/root:/bin/bash 1 
bin:x:1:1:bin:/bin:/sbin/nologin 2 
daemon:x:2:2:daemon:/sbin:/sbin/nologin 3 
adm:x:3:4:adm:/var/adm:/sbin/nologin 4 
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin 5 
sync:x:5:0:sync:/sbin:/bin/sync 6 
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown 7 
halt:x:7:0:halt:/sbin:/sbin/halt 8 
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin 9 
operator:x:11:0:operator:/root:/sbin/nologin 10 
games:x:12:100:games:/usr/games:/sbin/nologin 11 
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin 12 
nobody:x:99:99:Nobody:/:/sbin/nologin 13 
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin 14 
dbus:x:81:81:System message bus:/:/sbin/nologin 15 
polkitd:x:999:998:User for polkitd:/:/sbin/nologin 15 1
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin 16 1
postfix:x:89:89::/var/spool/postfix:/sbin/nologin 17 1
chrony:x:998:996::/var/lib/chrony:/sbin/nologin 17 2
17 2

[root@boy ~]# awk '1{print $0}' test 
name   sex    age
jack   men    18
cnm    women  18
[root@boy ~]# awk '0{print $0}' test
[root@boy ~]# awk '!0{print $0}' test
name   sex    age
jack   men    18
cnm    women  18

[root@boy ~]# awk '0' test 
[root@boy ~]# awk '!0' test 
name   sex    age
jack   men    18
cnm    women  18


# 当awk开始处理第一行时,变量 i 被初始化,变量 i 在被初始化时,值为”空”,而awk中,数字0或者”空字符串”表示假,所以可以认为模式为假,但是 i 直接取反了,对假取反后的值为真,将取反后的值又赋值给了变量i,此刻,变量i的值为真,所以当awk处理第一行文本时,变量i的值被赋值为真,模式成立则需要执行对应的动作,而上例中又省略了动作,所以默认动作为”{print $0}”,所以,第一行被整行打印了
# 当第一行文本处理完毕后,awk开始处理第二行文本,此时,i 为真,但是取反后,i 为假,所以第二行没有被输出,依次类推,最终只打印了奇数行
[root@boy ~]# awk 'i=!i{print NR}' /etc/passwd
1
3
5
7
9
11
13
15
17
19

awk printf

printf

# 格式
 %s 字符串
 %f 浮点格式(也就是我们概念中的float或者double)
 %b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义。
 %c ASCII字符。显示相对应参数的第一个字符
 %d, %i 十进制整数
 %o 不带正负号的八进制值
 %u 不带正负号的十进制值
 %x 不带正负号的十六进制值,使用a至f表示10至15
 %X 不带正负号的十六进制值,使用A至F表示10至15
 %% 表示”%”本身

# 转义字符
    \a 警告字符,通常为ASCII的BEL字符
    \b 后退
    \c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
    \f 换页(formfeed)
    \n 换行
    \r 回车(Carriage return)
    \t 水平制表符
    \v 垂直制表符
    \\ 一个字面上的反斜杠字符,即”\”本身。
    \ddd 表示1到3位数八进制值的字符,仅在格式字符串中有效
    \0ddd 表示1到3位的八进制值字符

# 对齐
 ”-“:表示左对齐,默认不加”-“时表示右对齐,其实”-“也是修饰符
 “+”:在默认没有添加”+”作为修饰符时,烈火兽的体温输出为180,而使用了”+”修饰符以后,烈火兽的体温输出为+180,看到这里你应该明白了,当替换符对应的参数为数字时,可以使用”+”作为修饰符,输出”正数”前面的”正号”
 “%12.3”:第一条命令的数字修饰符为12,表示对应的替换符”%f”的输出宽度为12个字符,第二条命令的数字修饰符为12.3 ,表示对应的替换符”%f”的输出宽度为12个字符,并且小数点的精度为3
 ”%d”:如果数字修饰符带有小数点,则数字修饰符小数点后的数字表示整数的长度,长度不够时,高位用0补全
 
[root@boy ~]# printf "(%s)" 1 19 18 16 ; echo ""
(1)(19)(18)(16)

[root@boy ~]# printf "%s %s\n" a b c d e f
a b
c d
e f
# 我们只是在原来的”格式替换符”中间加入了特定的数字,貌似显示效果就比刚才好了一点,起码年龄字段对应数字与”年龄”两个字对齐了。那么这些数字是什么意思呢?上图中第一个”%7s”中间的7表示当前替换符对应的输出宽度为7个字符宽,如果对应的输出不足7个字符,则用空格补全,如果输出的长度超过7个字符,超出的部分也会显示。同理”%5s”表示当前替换符对应的输出宽度为5个字符的宽度。而这些数字,我们可以将其称之为”修饰符”,修饰符会对相应的”替换符”进行修饰

[root@boy ~]# printf "%s %s %s\n" 姓名 性别 年龄 尼玛 男 20 你妹 女 18
姓名 性别 年龄
尼玛 男 20
你妹 女 18

[root@boy ~]# printf "%7s %5s %4s\n" 姓名 性别 年龄 尼玛 男 20 你妹 女 18
 姓名 性别 年龄
 尼玛   男   20
 你妹   女   18

awk

我们来总结一下,在awk中使用printf动作时,需要注意以下3点。

  • 使用printf动作输出的文本不会换行,如果需要换行,可以在对应的”格式替换符”后加入”\n”进行转义。

  • 使用printf动作时,”指定的格式” 与 “被格式化的文本” 之间,需要用”逗号”隔开。

  • 使用printf动作时,”格式”中的”格式替换符”必须与 “被格式化的文本” 一一对应

netstat -anp|awk '{printf "%-8s %-8s %-10s\n",$1,$2,$3}' 

printf表示格式输出

%格式化输出分隔符

-8长度为8个字符

s表示字符串类型

打印每行前三个字段,指定第一个字段输出字符串类型(长度为8),第二个字段输出字符串类型(长度为8),

第三个字段输出字符串类型(长度为10)

netstat -anp|awk '$6=="LISTEN" || NR==1 {printf "%-10s %-10s %-10s \n",$1,$2,$3}'

netstat -anp|awk '$6=="LISTEN" || NR==1 {printf "%-3s %-10s %-10s %-10s \n",NR,$1,$2,$3}'

# 查看文件
[root@boy ~]# cat test
abc 123 iuy ddd
8ua 456 auv php 7y7

[root@boy ~]# awk '{printf "%s\n",$1}' test
abc
8ua

[root@boy ~]# awk 'BEGIN{printf "%s\n%s\n%s\n",1,2,3}'
1
2
3

[root@boy ~]# awk -v FS=":" 'BEGIN{printf "%-10s\t %s\n","用户名称","用户ID"} {printf "%-10s\t %s\n",$1,$3}' /etc/passwd
用户名称        用户ID
root        0
bin         1
daemon      2
adm         3
lp          4

输出处理结果到文件

# 输出处理结果到文件

①在命令代码块中直接输出    route -n|awk 'NR!=1{print > "./fs"}'   

②使用重定向进行输出           route -n|awk 'NR!=1{print}'  > ./fs

awk 替换

# sub匹配第一次出现的符合模式的字符串,相当于 sed 's//' 
# gsub匹配所有的符合模式的字符串,相当于 sed 's//g' 

awk -F '|'  '{sub(/[0-9]+/,"",$2);print $0}' data.test 

awk '{sub(/Mac/,"Macintosh");print}' urfile    用Macintosh替换Mac
awk '{sub(/Mac/,"MacIntosh",$1); print}' file    第一个域内用Macintosh替换Mac

# sub函数匹配指定域/记录中最大、最靠左边的子字符串的正则表达式,并用替换字符串替换这些字符串,如果没有指定目标字符串就默认使用整个记录。替换只发生在第一次匹配的时候。格式如下
    sub (regular expression, substitution string):
    sub (regular expression, substitution string, target string)
    $ awk '{ sub(/test/, "mytest"); print }' testfile  #在整个记录中匹配,替换只发生在第一次匹配发生的时候
    $ awk '{ sub(/test/, "mytest", $1); print }' testfile #在整个记录的第一个域中进行匹配,替换只发生在第一次匹配发生的时候


​ 
# 如要在整个文件中进行匹配需要用到gsubgsub函数作用如sub,但它在整个文档中进行匹配。格式如下:
 gsub (regular expression, substitution string)
 gsub (regular expression, substitution string, target string)
 $ awk '{ gsub(/test/, "mytest"); print }' testfile #在整个文档中匹配test,匹配的都被替换成mytest
 $ awk '{ gsub(/test/, "mytest", $1); print }' testfile #在整个文档的第一个域中匹配,所有匹配的都被替换成mytest


​ 
# 删除所有空白:(空格、TAB等)【注:三条语句等效】:
    [oracle@localhost 201402261951.script]$ echo 'a bc  d' | awk '{gsub(/[[:blank:]]/, "",$0)}'
    abcd
    [oracle@localhost 201402261951.script]$ echo 'a bc  d' | awk 'gsub(/[[:blank:]]/, "",$0)'
abcd
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3