文本处理三剑客之awk

一、知识整理

1、awk报告生成器,格式化文本输出

发明人:a.k.a. Aho,Kernighan,weinberger

awk程序通常由:BEGIN语句块、能够使用模式匹配的通用语句块、END语句块三部分组成。program通常是放在单引号或双引号中。

基本用法:awk [] program var=value fiel…

program:pattern{action statements;…}

    patternaction:pattern部分决定动作语句何时出发及触发事件

    如:(BEGIN,END)

    action statements对数据进行处理,放在{}内指明

    如:(printprintf

awk [] -f programfile var=value file

awk [] BEGIN{action;…} pattern{action;…} END{action;…} file…

2、分隔符、域和记录

awk执行时,由分隔符分隔的字段标记$1,$2..成为域标识。$0为所有域,注意:和shell中变量$符含义不同。

文件的每一行称为记录

省略action,则默认执行print $0的操作。

3、readlink命令:查看文件是否为链接文件并显示原文件,相对目录。

[root@centos68 ~]# readlink /etc/rc3.d/K01smartd 
../init.d/smartd

centos7中,在/usr/sbin/下的pidof是软链接,不同的脚本软链接的执行结果不同。原因是给的值不同时,触发了不同的情况。例如:

[root@localhost ~]# bash 1
1
basename is:1
[root@localhost ~]# bash 1.sh 
1
basename is:1.sh
[root@localhost ~]# cat 1
#!/bin/bash
echo 1
echo "basename is:`basename $0`"

4、布尔值是” True 或 False 中的一个。动作脚本也会在适当时将值 True 和 False 转换为 1 和 0。布尔值经常与动作脚本语句中通过比较控制脚本流的逻辑运算符一起使用。

5、查看httpd的访问记录:

[root@centos68 ~]# cat /var/log/httpd/access_log 从其中取出访问的地址及次数

[root@centos68~]#awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log 
10.1.252.66 122

每行的第一个字段是IP地址,赋值数组,重复则加一。

二、命令详解及事例

1、工作原理:第一步:执行BEGIN{action…}语句块中的语句;

第二步:从文件或标准输入读取一行,然后执行pattern{action…}语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。

第三步:当读至输入流末尾时,执行END{action…}语句块。

BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量的初始化、打印输出表格的表头等语句通常是可以写在BEGIN语句块中的。

END语句块在awk从输入流中读取完所有的行之后被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,也是一个可选语句块。

pattern语句块中的通用命令是最重要的部分,也是可选的。如果没提供pattern语句块,则默认执行{print},即打印每一个读取到的行,awk读取的每一行都会执行该语句块。

2、awk变量:内置和自定义变量

FS:输入字符分隔符,默认为空白字符

OFS:输出字段分隔符,默认空白字符

RS:输入记录分隔符,指定输入时的换行符,原换行符仍有效

ORS:输出记录分隔符,输出时用指定符号代替换行符

NF:字段数量,引用内置变量不用$

NR:行号

FNR:各文件分别计数,行号

FILENAME:当前文件名

ARGC:命令行参数的个数

ARGV:数组,保存的是命令行给定的各参数

[root@centos68 ~]# awk 'BEGIN{print ARGV[1]}' /etc/fstab 
/etc/fstab
[root@centos68 ~]# awk 'BEGIN{print ARGV[0]}' /etc/fstab 
awk

自定义变量:-v VAR=VALUE 变量名区分字符大小写;在program中直接定义

3、printf命令:

格式化输出:printf FORMAT,item1,item2,..

必须指定FORMAT

不会自动换行,需要显式各处换行控制符

FORMAT中需要分别为后面每个item指定格式符

格式符:与item一一对应

%c 显式字符的ASCII

%d%i 显示十进制整数

%e%E 显示科学计数法数值

%f 显示为浮点数

%g%G 以科学计数法或浮点形式显示数值

%s 显示字符串

%u 无符号整数

%% 显示%自身

修饰符:#[.#] 第一个数字控制显示的宽度;第二个#表示小数点后精度

左对齐(默认右对齐)%-15s

+ 显示数值的正负符号 %+d0也会添加正号

4、算术操作符:x+yx-yx*yx/yx^yx%y

-x:转换为负数

+x:转换为数值

字符串操作符:没有符号的操作符,字符串连接

赋值操作符:

=+=-=*=/=%=^=

++

比较操作符:

>,>=,<,<=,!=,==

模式匹配符:

~:左边是否和右边匹配包含

!~:是否不匹配

逻辑操作符:&&||,!

函数调用:

function_name(argu1,argu2,…)

条件表达式:

selector?if-true-expression:if-false-expression

[root@centos68 ~]# awk -F: '{$3>=1000?usertype="common user":usertype="sysadmin or sysuser";printf "%15s:%-s\n",$1,usertype}' /etc/passwd
 root:sysadmin or sysuser
 saslauth:sysadmin or sysuser
 postfix:sysadmin or sysuser
 rpcuser:sysadmin or sysuser
 nfsnobody:common user

5、PATTERN:根据pattern条件,过滤匹配的行,再做处理

①如果未指定:空模式,匹配每一行

/ /:仅处理能够模式匹配到的行

relational expression:关系表达式;结果有真有假;结果为真时才会被处理

真:结果为非0值,非空字符串

假:结果为空字符串

[root@centos68 ~]# awk -F: '$NF~/bash$/{print $1,$NF}' /etc/passwd
root /bin/bash
mysql /bin/bash
tom /bin/bash
gejingyi /bin/bash
user1 /bin/bash
[root@centos68 ~]# seq 10 | awk 'i=!i'
1
3
5
7
9

首先读取第一行数据,也就是1,然后进行模式匹配,i是一个未定义的量,默认初值为0,也就是i=!0,对0取反就是1,显然0=0成立,为真,输出第一行。在第二行时,i已经由0变为1,即为1=!1,取反就是1=0显然不成立,为假,则第二行不输出。依次推算,只输出奇数行。若为awk !(i=!i),即将每次判断的真假取反,就会输出偶数行。

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

line ranges 行范围

startlineendline/PAT1/,/PAT2/ 不支持直接给出数字格式。

[root@centos68 ~]# awk -F: '/^root/,/^nobody/{print $1}' /etc/passwd
root
bin
daemon
adm
operator
games
gopher
ftp
nobody

BEGIN/END模式:

BEGIN:仅在开始处理文件中的文本之前执行一次

END{}:仅在文本处理完成之后执行一次

[root@centos68 ~]# awk -F: 'BEGIN{print "USER UID \n----------"}{print $1,$3}END{print "========="}' /etc/passwd
USER UID 
----------
root 0
bin 1
daemon 2
=========

6awk控制语句之if  else

语法:if(condition1){statement1}else if(condition2){statement2}else{statement3}

[root@centos68 ~]# awk -F: '{if($3>=1000){printf "common user: %s\n",$1}else{printf "root or sysuser:%s\n",$1}}' /etc/passwd
root or sysuser:root
root or sysuser:bin
common user: nfsnobody
root or sysuser:haldaemon

while循环:

语法:while(condition) statement

[root@localhost ~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF){print $i,length($i);i++}}' /etc/grub2.cfg
linux16 7
/vmlinuz-3.10.0-327.el7.x86_64 30
root=/dev/mapper/centos-root 28
ro 2
crashkernel=auto 16
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
rhgb 4
quiet 5
linux16 7

对匹配到的行处理,awk中默认用空格为分隔符,将每个单词单独统计字符长度并打印。

do while循环:

语法:do statement while(condition) 无论真假,至少执行一次循环体

[root@localhost ~]# awk 'BEGIN{total=0;i=0;do{total+=i;i++;}while(i<=100);print total}'
5050

for循环:

语法:for(expr1;expr2;expr3)statement for(variable assignment;condition;iteration process) {for-body}

特殊用法:能够遍历数组中的元素:

语法:for(var in array) {for-body}

[root@localhost ~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++){print $i,length($i)}}' /etc/grub2.cfg 
linux16 7
/vmlinuz-3.10.0-327.el7.x86_64 30
root=/dev/mapper/centos-root 28
ro 2
crashkernel=auto 16
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
rhgb 4
quiet 5

7、性能测试:

[root@localhost ~]# time (awk 'BEGIN{total=0;for(i=0;i<=10000;i++){total+=i;};print total;}')
50005000
real 0m0.009s
user 0m0.000s
sys 0m0.009s
[root@localhost ~]# time (total=0;for i in $(seq 10000);do total=$(($total+i));done;echo $total)
50005000
real 0m0.071s
user 0m0.065s
sys 0m0.008s

awk语法是c语言语法,执行速度更快。

8、switch语句:

语法:switch(expression) {case VALUE1 or /REGEXP/:statement;case VALUE2 or /REGEXP2/:statement;…;default:statement}

breakcontinue;退出循环和退出本次循环执行下一次循环。

[root@localhost ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0){continue}sum+=i}print sum}'
2500
[root@centos68 ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==11){break}sum+=i}print sum}'
55
[root@centos68 ~]# seq 10 | tr "\n" "+" | grep -Eo ".*[[:digit:]]" | bc
55

next:提前结束对本行处理而进入下一行处理(awk自身循环)

[root@centos68 ~]# awk -F: '{if($3%2!=0) next;print $1,$3}' /etc/passwd
root 0
daemon 2
lp 4
shutdown 6
mail 8
uucp 10
games 12
ftp 14
rpc 32

9、awk数组:

关联数组:array[index-expression]

index-expression:①可使用任意字符串;字符串要使用双括号

  ②如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为空

若要判断数组中是否存在某元素,要使用INDEX in array”格式进行遍历,但是index在此处有特殊意义,使用i等代替。

[root@localhost shelltest]# awk 'BEGIN{weekdays["mon"]="monday";print weekdays["mon"]}'
monday
[root@localhost ~]# awk  '!array[$0]++'  testawk.txt 
a
b
c
[root@localhost ~]# cat testawk.txt
a
b
c
a
b
c

为何上面的命令将重复的行去掉了呢?原因如下:首先,当读入第一个字符a时,关联数组array的以a为索引的值为空,即array[a]=0,将此取反为1,逻辑上为真,则输出第一行,然后自相加为2。其次,当读入第二个值b时,同理可知为1array[c]也为1。当第二次读入a时,因为array[a]的值已经为2,(逻辑)取反之后为0,逻辑上是假,则不会输出,自相加最后为1

[root@localhost ~]# awk '!a[$0]++;{print $0,a["a"],a["b"],a["c"]}' testawk.txt
a
a 1
b
b 1 1
c
c 1 1 1
a 2 1 1
b 2 2 1
c 2 2 2

注意:第一点,!的运算顺序比++要更优先;第二点,++是在print之后才会执行。

若要遍历数组中的每个元素,要使用for循环。

语法:for(var in array) {for-body}

10、数值处理:

rand() 返回0和1之间的一个随机数。random返回的是一个0到(2^31 – 1)的long类型整数。rand返回的是一个0到RAND_MAX的int类型整数,没有定义时为1RAND_MAX至少为32767。还有一个srandom,这个函数是为random设置种子的,参数和srand一样。rand和srand函数是linux c的内容。

[root@centos68 ~]# awk 'BEGIN{srand();for (i=1;i<=5;i++)print int(rand()*100)}' 
14
7
26
55
90

没有种子无法生成随机数,是个固定值。

[root@centos68 ~]# awk 'BEGIN{print rand()}'
0.237788
[root@centos68 ~]# awk 'BEGIN{srand();print rand()}'
0.831959
[root@centos68 ~]# awk 'BEGIN{srand();print rand()}'
0.420602

字符串处理:

length([S]) 返回指定字符串长度

sub(r,s,[t]) t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为S代表的字符串。

gsub([r,s,[t]]) t字符串进行搜索r表示的模式匹配的内容,并全部替换为s

split(s,array,[r]) r为分割符切割字符s,并将切割后的结果存至array表示的数组中。

[root@centos68 ~]# echo "2008:08:08:08 08:08:08" | awk 'sub(/:/,"",$1)'
200808:08:08 08:08:08
[root@centos68 ~]# echo "2008:08:08:08 08:08:08" | awk 'gsub(/:/,"",$1)'
2008080808 08:08:08
[root@centos68 ~]# echo "2008:08:08:08 08:08:08" | awk 'gsub(/:/,"",$2)'
2008:08:08:08 080808
[root@centos68 ~]# netstat -tan | awk '/^tcp\>/{split($5,ip,":")count[ip[1]]++}END{for (i in count){print i,count[i]}}'
 6
10.1.252.66 1
0.0.0.0 6

11、awk函数:

自定义函数:格式:function nameparameterparameter{

statements

return expression

}

[root@centos68 ~]# cat fun.awk 
function max(v1,v2){
v1>v2?var=v1:var=v2
return var
}
END{a=3;b=2;print max(a,b);}
[root@centos68 ~]# awk -f fun.awk  fun.awk 
3

12、awk脚本:将awk程序写成脚本,头部写awk -f,直接调用或执行,也可同shell脚本一样传递参数:

[root@centos68 ~]# ./f2.awk -F: /etc/passwd
nfsnobody 65534
[root@centos68 ~]# cat f2.awk 
#!/bin/awk -f
{if($3>=1000)print $1,$3}

13awk中调用shell命令:system命令:

空格是awk中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,或者说除了awk的变量外其他一律用””引用起来。

awk BEGIN{system(hostname)}

awk BEGIN{a=12;system(echo   a)} 使用echo时要加空格。

三、课后练习

1、统计/etc/fstab文件中每个文件系统类型出现的次数。

[root@centos68 ~]# awk '/^[^#]/{print $3}' /etc/fstab | sort | uniq -c
      1 devpts
      4 ext4
      1 iso9660
      1 proc
      1 swap
      1 sysfs
      1 tmpfs
[root@centos68 ~]# awk '/^[^#]/{split($3,array)count[array[1]]++}END{for(i in count){print i,count[i]}}' /etc/fstab
devpts 1
swap 1
sysfs 1
proc 1
tmpfs 1
iso9660 1
ext4 4

2、统计/etc/fstab文件中每个单词出现的次数。

[root@centos68 ~]# awk '{for(i=1;i<=NF;i++){print $i}}' /etc/fstab | sort | uniq -c
      1 /
      7 #
     14 0
      4 1
      1 18
      1 18:38:10
      2 2
      1 2016
      1 Accessible
      1 anaconda
      1 and/or
      1 are
      1 blkid(8)
      1 /boot
      2 by
      1 Created
      9 defaults
      1 /dev/cdrom
      1 '/dev/disk'
      2 devpts
      1 /dev/pts
      1 /dev/sda6
      1 /dev/shm
      1 /etc/fstab
      4 ext4
      1 filesystems,
      1 findfs(8),
      1 for
      1 fstab(5),
      1 gid=5,mode=620
      1 /home
      1 info
      1 iso9660
      1 Jul
      1 maintained
      1 man
      1 /media/cdrom
      1 Mon
      1 more
      1 mount(8)
      1 on
      1 pages
      2 proc
      1 /proc
      1 reference,
      1 See
      2 swap
      1 /sys
      2 sysfs
      1 /testdir
      2 tmpfs
      1 under
      1 UUID=86ad06a2-cd08-4f18-b06b-98d7cb2e23df
      1 UUID=aaad69ad-d716-4676-941f-851223ddab7f
      1 UUID=aae1cc08-6344-4424-9275-cfc4d6569d95
      1 UUID=ca1cb209-66f0-4d0d-a595-e85e33baf336

3、求每班总成绩和平均成绩。

[root@localhost ~]# cat awktest.txt
name class score
wang   1    100
zhang  2    90
li      1    80
a      1    55
b      2    85
c       2    74

求总成绩:

[root@localhost ~]# awk '/[[:digit:]]$/{i=$3;total=total+i}END{print total}' awktest.txt 
484

不同班级的成绩:

[root@localhost ~]# awk '$2==1{i=$3;total=total+i}END{print total}' awktest.txt 
235
[root@localhost ~]# awk '/1/{i=$3;total=total+i}END{print total}' awktest.txt 
235
[root@localhost ~]# awk '/2/{i=$3;total=total+i}END{print total}' awktest.txt 
249
[root@localhost ~]# awk '$2==2{i=$3;total=total+i}END{print total}' awktest.txt 
249

平均成绩:

[root@localhost ~]# awk '($2==2){i=$3;total=total+i}END{print total/NF}' awktest.txt 
83
[root@localhost ~]# awk '($2==1){i=$3;total=total+i}END{print total/NF}' awktest.txt 
78.3333

原创文章,作者:SilencePavilion,如若转载,请注明出处:http://www.178linux.com/49228

评论列表(1条)

  • 马哥教育
    马哥教育 2016-09-26 13:33

    文章对awk的工作原理和使用方法解析的很详细,特别对awk的函数都通过示例展示了,很具有学习价值,赞一个。