Linux入门——文本处理三剑客之gnu awk

Linux 文本处理三剑客 ,grep ,sed,awk。Grep是文本过滤工具,sed文本行编辑器,前两者我们已经在前面的博客中做出了介绍,今天介绍awk,awk是一种报表生成器,也就是对文件进行格式化处理,这里的格式化不是文件系统的格式化,而是对文件内容进行各种“排版”,进行格式化显示

        在linux上我们使用的是GUN awk简称gawk,gawk是一种过程式编程语言。既然是一种语言那么他就支持条件判断,数组,循环等各种编程语言中所有可以使用的功能,我们可以把gawk称为一种脚本语音解释器。

简单的awk

[root@localhost ~]#awk '{ print }' /etc/passwd

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

..............

liang:x:1004:1004::/home/liang:/bin/bash
zhaoyun:x:1005:1005::/home/zhaoyun:/bin/bash

[root@localhost ~]# awk '{ print }' /etc/passwd|wc -l
48
[root@localhost ~]# cat /etc/passwd |wc -l
48

/etc/passwd 文件的内容出现在眼前。现在,解释 awk 做了些什么。调用 awk 时,我们指定 /etc/passwd 作为输入文件。执行 awk 时,它依次对 /etc/passwd 中的每一行执行 print 命令。所有输出都发送到 stdout,所得到的结果与与执行cat /etc/passwd完全相同。

现在,解释 { print } 代码块。在 awk 中,花括号用于将几块代码组合到一起,这一点类似于 C 语言。在代码块中只有一条 print 命令。在 awk 中,如果只出现 print 命令,那么将打印当前行的全部内容。

另一个 awk 示例,它的作用与上例完全相同

[root@localhost ~]# awk '{ print $0 }' /etc/passwd

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

..............

liang:x:1004:1004::/home/liang:/bin/bash
zhaoyun:x:1005:1005::/home/zhaoyun:/bin/bash

在 awk 中,$0 变量表示整个当前行,所以 print 和 print $0 的作用完全一样。同时我们从前两个例子中也可以看出awk和sed一样是一行一行处理数据的,对于awk来说每一行内容是一个数据。

如果您愿意,可以创建一个 awk 程序,让它输出与输入数据完全无关的数据。以下是一个示例

[root@localhost ~]# awk '{ print "" }' /etc/passwd

 

 ...

 

 

[root@localhost ~]# awk '{ print "" }' /etc/passwd|wc -l
48

只要将 "" 字符串传递给 print 命令,它就会打印空白行。如果测试该脚本,将会发现对于 /etc/passwd 文件中的每一行,awk 都输出一个空白行。再次说明, awk 对输入文件中的每一行都执行这个脚本。以下是另一个示例

[root@localhost ~]# awk '{ print "hi" }' /etc/passwd
hi
hi
hi
hi

....

hi

[root@localhost ~]# awk '{ print "hi" }' /etc/passwd|wc -l
48

awk 工作原理

  1. awk 使用一行作为输入(通过文件或者管道),并将这一行赋给内部变量 $0

  2. 行被空格分解为字段(单词),每一个字段存储在已编号的变量中,从 $1 开始。( awk 的内部变量 FS 用来确定字段的分隔符。初始时,为空格,包含制表符和空格符)

  3. 对于一行,按照给定的正则表达式的顺序进行匹配,如果匹配则执行对应的 Action ,如果没有匹配上则不执行任何动作 , Search Pattern 和 Action 是可选的,但是必须提供其中一个 。如果 Search Pattern 未提供,则对所有的输入行执行 Action 操作。如果 Action 未提供,则默认打印出该行的数据 。 {} 这种 Action 不做任何事情,和未提供的 Action 的工作方式不一样

  4. 打印字段,用 print 、 printf 、 sprintf ,格式: { print $1, $3 } 内部变量 output field separator ( OFS ),默认为空格, $n 之间的逗号被 OFS 中的字符替换。

  5. 输出之后,从文件中另取一行,并将其复制到 $0 中,覆盖原来的内容。重复进行……

Print与printf

 print 函数用于打印不需要特别编排格式的简单输出。更为复杂的格式编排则要使用 printf 和 sprintf 。若懂得 C 语言,则也一定懂得如何使用 printf 和 sprintf 

print 函数的的转义序列

  /b 退格

  /f 换页

  /n 换行

  /r 回车

  /t 制表符

  /047 八进制值 47 ,即单引号

  /c c 代表任意其他字符

打印数字时,可能需要控制数字的格式。可以通过 printf 来实现,但是通过设置一个特殊的变量 OFMT ,是用 print 函数也可以控制数字打印格式。 OFMT 默认为“ %.6gd” ,表示只打印小数部分的前 6 位。

[root@localhost ~]# awk 'BEGIN { OFMT="%.2f"; print 1.2456789, 1.234-2 }'
1.25 -0.77

[root@localhost ~]# awk 'BEGIN { printf "%.2f %.2f\n" ,1.2456789, 1.234-2 }'

1.25 -0.77

printf 函数

printf 函数返回一个带格式的字符串给标准输出,如同 C 语言中的 printf 语句。 printf 语句包括一个加引号的控制串,控制串中可能嵌套有若干格式说明和修饰符。控制串后面跟逗号,之后是一列由逗号分隔的表达式。与 print 函数不同的是, printf 函数不会在行尾自动换行。若要换行,在控制串中提供转义字符 /n 。每个百分号和格式说明都必须有一个对应的变量 。要打印百分号就必须在控制串中给出两个百分号。 

  printf 函数的转义字符

    c  字符

    s  字符串

    d  十进制整数

    ld 十进制长整数

    u  十进制无符号整数

    lu 十进制无符号长整数

    x  十六进制整数

    lx 十六进制长整数

    o  八进制整数

    lo 八进制长整数

    e  用科学计数法表示浮点数

    f  浮点数

    g  选用 e 或 f 中较短的一种形式

  printf 函数的修饰符

    - 左对齐修饰符

    # 显示八进制整数时,前面加 0 ,显示十六进制整数时,前面加 0x

    + 显示使用 d 、 e 、 f 、 g 转换的整数时,加上正负号

    0 用 0 而不是空白符来填充所显示的值

printf 函数控制串里的管道符(竖杠)是文本的一部分,用于指示格式的起始与结束。

|[root@localhost ~]#echo "UNIX" | awk ' { printf "|%-15s|\n", $11 }'      #%15s表示15个字符的字符串若没有则用空格代替
|UNIX      |
[root@localhost ~]# echo "UNIX" | awk ' { printf "|%-15s\n|", $1 }'
|UNIX
|[root@localhost ~]#

BEGIN与END

通常,对于每个输入行, awk 都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在 awk 开始处理输入文件中的文本之前执行初始化代码。对于这种情况, awk 允许您定义一个 BEGIN 块。

因为 awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。

awk 还提供了另一个特殊块,叫作 END 块。 awk 在处理了输入文件中的所有行之后执行这个块。通常, END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。

有一点要明确就是BEGIN{}与END{}仅且只执行一次

[root@localhost ~]# awk 'BEGIN {print 1}{print 2}END{print 3}' /etc/passwd
1
2
2
2
...
2
3

Awk运算符

算术操作符:

x+y, x-y, x*y, x/y, x^y, x%y

-x: 转换为负数

+x: 转换为数值

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

赋值操作符:

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

++, --

比较操作符:

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

模式匹配符:~:左边是否和右边匹配包含!~:是否不匹配

[root@localhost ~]#awk –F: '$0 ~ /root/{print $1}' /etc/passwd
root
operator
[root@localhost ~]#awk –F: '$3==0' /etc/passwd
root:x:0:0:root:/root:/bin/bash

逻辑操作符:与&&,或||,非!

示例:


[root@localhost ~]# awk -F: '$3>=0 && $3<=1000 {print $1}' /etc/passwd
root
bin
daemon
adm
lp

......

liang
zhaoyun

[root@localhost ~]# awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
root
nfsnobody
centos
zhangsan
lisi
li
liang
zhaoyun


[root@localhost ~]# awk -F: '!($3==0) {print $1}' /etc/passwd
bin
daemon
adm
lp
sync
shutdown

.....

liang
zhaoyun

[root@localhost ~]# awk -F: '!($3>=500){print $3}' /etc/passwd
0
1
2
3
4
5
6
7
8
...
74
70
89
72

条件表达式(三目表达式):

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

示例:

[root@localhost ~]# awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf"%15s:%-s\n",$1,usertype}' /etc/passwd
root:Sysadmin or SysUser
bin:Sysadmin or SysUser
daemon:Sysadmin or SysUser
adm:Sysadmin or SysUser
lp:Sysadmin or SysUser
sync:Sysadmin or SysUser
shutdown:Sysadmin or SysUser
halt:Sysadmin or SysUser
mail:Sysadmin or SysUser
operator:Sysadmin or SysUser
games:Sysadmin or SysUser
ftp:Sysadmin or SysUser
nobody:Sysadmin or SysUser
systemd-network:Sysadmin or SysUser
dbus:Sysadmin or SysUser
polkitd:Sysadmin or SysUser
sssd:Sysadmin or SysUser
libstoragemgmt:Sysadmin or SysUser
rpc:Sysadmin or SysUser
colord:Sysadmin or SysUser
gluster:Sysadmin or SysUser
saslauth:Sysadmin or SysUser
abrt:Sysadmin or SysUser
setroubleshoot:Sysadmin or SysUser
rtkit:Sysadmin or SysUser
pulse:Sysadmin or SysUser
chrony:Sysadmin or SysUser
rpcuser:Sysadmin or SysUser
nfsnobody:Common User
unbound:Sysadmin or SysUser
tss:Sysadmin or SysUser
usbmuxd:Sysadmin or SysUser
geoclue:Sysadmin or SysUser
radvd:Sysadmin or SysUser
qemu:Sysadmin or SysUser
ntp:Sysadmin or SysUser
gdm:Sysadmin or SysUser
gnome-initial-setup:Sysadmin or SysUser
sshd:Sysadmin or SysUser
avahi:Sysadmin or SysUser
postfix:Sysadmin or SysUser
tcpdump:Sysadmin or SysUser
centos:Common User
zhangsan:Common User
lisi:Common User
li:Common User
liang:Common User
zhaoyun:Common User

Awk变量及

自定义变量(区分字符大小写)
  (1) -v var=value
  (2) 在program中直接定义
示例:


[root@localhost ~]# awk -v test='hello gawk' '{print test}' /etc/fstab
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk
hello gawk

[root@localhost ~]# awk -v test='hello gawk' 'BEGIN{print test}'

hello gawk

 

                                  awk中常见内置变量

变量名 属性
FS 输入字段分隔符,默认为空白字符
OFS 输出字段分隔符,默认为空白字符
RS 输入记录分隔符,指定输入时的换行符
ORS 输出记录分隔符,输出时用指定符号代替换行
NF 字段数量
NR 行号
FNR 各文件分别计数,行号
FILENAME 当前文件名
ARGC 命令行参数的个数
ARGV 数组,保存的是命令行所给定的各参数

Awk 的If,循环,与数组

If

语法:

  if(condition){statement;…}[else statement]
  if(condition1){statement1}else if(condition2){statement2}
  else{statement3}
使用场景:

  对awk取得的整行或某个字段做条件判断
示例:


[root@localhost ~]# awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
nfsnobody 65534
centos 1000
zhangsan 1001
lisi 1002
li 1003
liang 1004
zhaoyun 1005

[root@localhost ~]# awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
root
centos
zhangsan
lisi
li
liang
zhaoyun

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-862.el7.x86_64 30
root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46
ro 2
crashkernel=auto 16
rhgb 4
quiet 5
LANG=zh_CN.UTF-8 16
linux16 7
/vmlinuz-0-rescue-e650feaecc624ac7ac2823eaad4cf6ca 50
root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46
ro 2
crashkernel=auto 16
rhgb 4
quiet 5

For

语法:

  for(expr1;expr2;expr3) {statement;…}
常见用法:
  for(variable assignment;condition;iterationprocess)
  {for-body}
特殊用法:

  能够遍历数组中的元素
语法:

  for(varin 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-862.el7.x86_64 30
root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46
ro 2
crashkernel=auto 16
rhgb 4
quiet 5
LANG=zh_CN.UTF-8 16
linux16 7
/vmlinuz-0-rescue-e650feaecc624ac7ac2823eaad4cf6ca 50
root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46
ro 2
crashkernel=auto 16
rhgb 4
quiet 5

同样在awk中同样支持break与countinue

[root@localhost ~]# awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
root 0
daemon 2
lp 4
shutdown 6
mail 8
games 12
ftp 14
systemd-network 192
sssd 998
rpc 32
colord 996
saslauth 994
rtkit 172
chrony 992
nfsnobody 65534
geoclue 990
ntp 38
gdm 42
sshd 74
avahi 70
tcpdump 72
centos 1000
lisi 1002
liang 1004

数组

数组在 awk 中被称为关联数组 ( associative arrays ),其下表既可以是字符串也可以是数字。数组的键和值都存储在 awk 程序内部的一个表中,该表采用的是 散列算法,所以数组元素不是顺序存储的。数组也是被用到时才被创建。 awk 还能判定数组用于保存数字还是字符串,根据上下文被初始化为 0 或者空字符串。数组大小不需要声明。

 当下标为字符串或者非连续的数字时,不能用 for 循环来遍历数组。这是就要使用特殊的 for 循环。

[root@localhost ~]# cat test
4234 Tom 43
4571 Tom 22
3298 Eliza 21
4622 Tom 53
2345 Mary 24

[root@localhost ~]# awk '{count[$2]++};END{for(name in count)printf"%s %s\n" ,name,count[name]}' test
Tom 3
Eliza 1
Mary 1

Awk常用的内部函数

数值处理:

  rand():返回0和1之间一个随机数
  

[root@localhost ~]# awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'
98
82
85
18
32
18
40
98
63
80

字符串处理:

  length([s]):返回指定字符串的长度
  sub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为s

[root@localhost ~]# echo "2008:08:08 08:08:08" |awk 'sub(/:/,"-",$1)'
2008-08:08 08:0808

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

[root@localhost ~]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0) '
2008-08-08 08-08-08

  split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,…

[root@localhost ~]# netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++};END{for (i in count) {print i,count[i]}}'
192.168.75.1 1
0.0.0.0 6

Awk中调用shell命令

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


[root@localhost ~]# awk BEGIN'{system("hostname") }'
localhost.localdomain

[root@localhost ~]# awk 'BEGIN{score=100; system("echo your score is " score) }'
your score is 100

 

 

 

posted @ 2018-09-08 16:15  正在来的4号  阅读(298)  评论(0编辑  收藏  举报