Shell之awk

Shell之awk

目录

一、awk概述

1. awk的工作原理

逐行读取文本,默认以空格或tab键为分隔符进行分割,将分割所得的各个字段保存到内建变量中,并按模式或者条件执行编辑命令。
sed命令常用语一整行的处理,而awk比较倾向于将一行分成多个“字段”然后再进行处理。awk信息的读入也是逐行读取的,执行结果可以通过print的功能将字段数据打印显示。在使用awk命令的过程中,可以使用逻辑操作符“&&”表示“与”、“||”表示“或”、“!”表示“非”;还可以进行简单的数学运算,如+、-、*、/、%、^分别表示加、减、乘、除、取余和乘方。

2. 命令格式

awk 选项 '模式或条件 {操作}' 文件1 文件2 ……
awk -f 脚本文件 文件1 文件2 ……

3. awk常见的内建变量(可直接用)

内建变量 说明
FS 列分隔符。指定每行文本的字段分隔符,默认为空格或制表位。与“-F”作用相同
NF 当前处理的行的字段个数
NR 当前处理的行的行号(序数)
$0 当前处理的行的整行内容
$n 当前处理行的第n个字段(第n列)
FILENAME 被处理的文件名
RS 行分隔符。awk从文件上读取资料时,将根据RS的定义把资料切割成许多条记录,而awk一次仅读入一条记录,以进行处理。预设值是“\n”

二、操作实例

1. 按行输出文本

awk '{print}' 文件名
输出所有内容

[root@localhost ~]# cat test.txt 
11
22
33
44
[root@localhost ~]# awk '{print}' test.txt 
11
22
33
44

awk '{print $0}' 文件名
输出所有内容

[root@localhost ~]# awk '{print $0}' test.txt 
11
22
33
44

awk 'NR==n,NR==m {print}' 文件名
输出第n行至第m行的内容

[root@localhost ~]# awk 'NR==1,NR==3 {print $0}' test.txt 
11
22
33

awk '(NR>=n) && (NR<=m) {print}' 文件名
输出第n行至第m行的内容

[root@localhost ~]# awk '(NR>=1) && (NR<=3) {print}' test.txt 
11
22
33

awk 'NR==n || NR==m {print}' 文件名
输出第n行和第m行的内容

[root@localhost ~]# awk 'NR==1 || NR==3 {print}' test.txt 
11
33

awk '(NR%2)==1 {print}' 文件名
输出所有奇数行的内容

[root@localhost ~]# awk '(NR%2)==1 {print}' test.txt 
11
33

awk '(NR%2)==0 {print}' 文件名
输出所有偶数行的内容

[root@localhost ~]# awk '(NR%2)==1 {print}' test.txt 
22
44

awk '/^a/ {print}' 文件名
输出以字符串a开头的行

[root@localhost ~]# awk '/^3/ {print}' test.txt 
33

awk '/a$/ {print}' 文件名
输出以字符串a结尾的行

[root@localhost ~]# awk '/4$/ {print}' test.txt 
44

awk 'BEGIN {x=0}; /^a/ {x++}; END {print x}' 文件名
输出文件中以字符串a开头行的数量
BEGIN模式表示,在处理指定的文本之前,需要先执行BEGIN模式中指定的动作;awk再处理指定的文本,之后再执行END模式中指定的动作,END{}语句块中,往往会放入打印结果等语句。

[root@localhost ~]# awk 'BEGIN{X=0}; /^root/ {x++}; END{print x}' /etc/passwd
1
[root@localhost ~]# awk 'BEGIN{X=0}; /nologin$/ {x++}; END{print x}' /etc/passwd
39

2. 按字段输出文本

awk -F ":" '{print $n}' 文件名
以:号为分隔符,输出每行的第n个字段

[root@localhost ~]# awk -F ":" '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
operator
games
ftp
nobody
systemd-network
dbus
polkitd
abrt
libstoragemgmt
rpc
colord
saslauth
setroubleshoot
rtkit
pulse
qemu
ntp
radvd
chrony
tss
usbmuxd
geoclue
sssd
gdm
rpcuser
nfsnobody
gnome-initial-setup
avahi
postfix
sshd
tcpdump
123456
named
dhcpd
apache

awk -F ":" '{print $n,$m}' 文件名
以:号为分隔符,输出每行的第n个和第m个字段

[root@localhost ~]# awk -F ":" '{print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
games 12
ftp 14
nobody 99
systemd-network 192
dbus 81
polkitd 999
abrt 173
libstoragemgmt 998
rpc 32
colord 997
saslauth 996
setroubleshoot 995
rtkit 172
pulse 171
qemu 107
ntp 38
radvd 75
chrony 994
tss 59
usbmuxd 113
geoclue 993
sssd 992
gdm 42
rpcuser 29
nfsnobody 65534
gnome-initial-setup 991
avahi 70
postfix 89
sshd 74
tcpdump 72
123456 1000
named 25
dhcpd 177
apache 48

awk -F ":" '$n<m {print $n}' 文件名
以:号为分隔符,当第n个字段小于m时,输出第n个字段

[root@localhost ~]# awk -F ":" '$3<5 {print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4

awk -F ":" '!($n<m) {print}' 文件名
以:号为分隔符,当第n个字段不小于m时,输出整行内容

[root@localhost ~]# awk -F ":" '!($3<200) {print}' /etc/passwd
polkitd:x:999:997:User for polkitd:/:/sbin/nologin
libstoragemgmt:x:998:995:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
colord:x:997:994:User for colord:/var/lib/colord:/sbin/nologin
saslauth:x:996:76:Saslauthd user:/run/saslauthd:/sbin/nologin
setroubleshoot:x:995:993::/var/lib/setroubleshoot:/sbin/nologin
chrony:x:994:990::/var/lib/chrony:/sbin/nologin
geoclue:x:993:988:User for geoclue:/var/lib/geoclue:/sbin/nologin
sssd:x:992:987:User for sssd:/:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
gnome-initial-setup:x:991:986::/run/gnome-initial-setup/:/sbin/nologin
123456:x:1000:1000:123456:/home/123456:/bin/bash

awk 'BEGIN {FS=":"}; {if ($n>=m) {print}}' 文件名
以:号为分隔符,当第n列大于等于m时,输出整行内容

[root@localhost ~]# awk 'BEGIN {FS=":"}; {if ($3>=1000) {print}}' /etc/passwd
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
123456:x:1000:1000:123456:/home/123456:/bin/bash

awk -F ":" '{max=($n>=$m) ? $n : $m; {print max}}' 文件名
三元运算符。以:号为分隔符,如果第n个字段的值大于等于第m个字段的值,则把第n个字段的值赋给max,否则把第m个字段的值赋给max

[root@localhost ~]# awk -F ":" '{max=($3>=$4) ? $3 : $4; {print max}}' /etc/passwd
0
1
2
4
7
5
6
7
12
11
100
50
99
192
81
999
173
998
32
997
996
995
172
171
107
38
75
994
59
113
993
992
42
29
65534
991
70
89
74
72
1000
25
177
48

awk -F ":" '{print NR,$0}' 文件名
以:号为分隔符,处理每行内容和行号,没处理完一条记录,NR值加1

[root@localhost ~]# awk -F ":" '{print NR,$0}' /etc/passwd
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:997:User for polkitd:/:/sbin/nologin
17 abrt:x:173:173::/etc/abrt:/sbin/nologin
18 libstoragemgmt:x:998:995:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
19 rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
20 colord:x:997:994:User for colord:/var/lib/colord:/sbin/nologin
21 saslauth:x:996:76:Saslauthd user:/run/saslauthd:/sbin/nologin
22 setroubleshoot:x:995:993::/var/lib/setroubleshoot:/sbin/nologin
23 rtkit:x:172:172:RealtimeKit:/proc:/sbin/nologin
24 pulse:x:171:171:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
25 qemu:x:107:107:qemu user:/:/sbin/nologin
26 ntp:x:38:38::/etc/ntp:/sbin/nologin
27 radvd:x:75:75:radvd user:/:/sbin/nologin
28 chrony:x:994:990::/var/lib/chrony:/sbin/nologin
29 tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
30 usbmuxd:x:113:113:usbmuxd user:/:/sbin/nologin
31 geoclue:x:993:988:User for geoclue:/var/lib/geoclue:/sbin/nologin
32 sssd:x:992:987:User for sssd:/:/sbin/nologin
33 gdm:x:42:42::/var/lib/gdm:/sbin/nologin
34 rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
35 nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
36 gnome-initial-setup:x:991:986::/run/gnome-initial-setup/:/sbin/nologin
37 avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
38 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
39 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
40 tcpdump:x:72:72::/:/sbin/nologin
41 123456:x:1000:1000:123456:/home/123456:/bin/bash
42 named:x:25:25:Named:/var/named:/sbin/nologin
43 dhcpd:x:177:177:DHCP server:/:/sbin/nologin
44 apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

awk -F ":" '$n~ "a" {print $m}' 文件名
以:号为分隔符,输出第n个字段中含有字符串a的行的第m个字段

[root@localhost ~]# awk -F ":" '$7~ "/bash" {print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
123456:x:1000:1000:123456:/home/123456:/bin/bash

awk -F ":" '($n~ "a") && (NF==m) {print}' 文件名
以:号为分隔符,输出第n个字段中含有字符串a且有m个字段的行

[root@localhost ~]# awk -F ":" '($1~ "root") && (NF==7) {print $1,$2}' /etc/passwd
root x

awk -F “:” '($n != "a") && ($m != "b") {print}' 文件名
以:号为分隔符,输出第n个字段既不是字符串a也不是字符串b的行

[root@localhost ~]# awk -F ":" '($7!="/bin/bash")&&($7!="/sbin/nologin") {print}' /etc/passwd
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt

通过管道、双引号调用Shell命令
echo $PATH | awk 'BEGIN{RS=":"}; END{print NR}'
以:号为分隔符,输出总字段数

[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost ~]# echo $PATH | awk 'BEGIN{RS=":"}; END{print NR}'
5

awk -F: '/a$/ {print | "wc -l"}' 文件名
以:号为分隔符,统计以字符串a为结尾的行的行数,同等于“grep -c”命令

[root@localhost ~]# awk -F: '/bash$/ {print | "wc -l"}' /etc/passwd
2
[root@localhost ~]# grep -c "/bash"$ /etc/passwd
2

free -m | awk '/Mem:/ {print int($3/($3+$4)*100)"%"}'
查看当前内存使用百分比

[root@localhost ~]# free -m
              total        used        free      shared  buff/cache   available
Mem:           1984         326        1257           9         400        1439
Swap:          4095           0        4095
[root@localhost ~]# free -m | awk '/Mem:/ {print int($3/($3+$4)*100)"%"}'
20%

top -b -n 1 | grep Cpu | awk -F "," '{print $4}' | awk '{print $1}'
查看当前CPU空闲率,"-b -n 1"表示只需要1次的输出结果

[root@localhost ~]# top -b -n 1 | grep Cpu | awk -F "," '{print $4}' | awk '{print $1}'
97.0

date -d "$(awk -F "." '{print $1}' /proc/uptime) second ago" +"%F %H:%M:%S"
显示上次系统重启时间,等同于uptime;“second ago”为显示多少秒前的时间,+"%F %H:%M:%S"为时间格式,%F等同于%Y-%m-%d。

[root@localhost ~]# date -d "$(awk -F "." '{print $1}' /proc/uptime) second ago" +"%F %H:%M:%S"
2021-07-29 11:07:15

awk 'BEGIN {N=0; while ("w" | getline) n++; {print n=2}}'
调用w命令,并用来统计在线用户数

[root@localhost ~]# awk 'BEGIN {N=0; while ("w" | getline) n++; {print n=2}}'
2

awk 'BEGIN {"hostname" | getline; {print}}'
调用hostname,并输出当前的主机名

[root@localhost ~]# awk 'BEGIN {"hostname" | getline; {print}}'
localhost.localdomain

注:当getline左右无重定向符“<”或“|”时,awk首先读取到了第一行,就是1,然后getline,就得到了1下面的第二行,就是2,因为getline之后,awk会改变对应的NF,FNR和$0等内部变量,所以此时的$0的值就不再是1,而是2了,然后将它打印出来。
当getline左右有重定向符“<”或“|”时,getline则作用于定向输入文件,由于该文件是刚打开,并没有被awk读入一行,只是getline读入,那么getline返回的是该文件的第一行,而不是隔行。
FNR:awk当前读取的记录数,其变量值小于等于NR(比如当读取第二个文件时,FNR是从0开始重新计数,而NR不会),因此可使用“NR==FNR”来判断是否在读取第一个文件。

[root@localhost ~]# seq 10 | awk '{getline; print}'
2
4
6
8
10
[root@localhost ~]# seq 10 | awk '{print; getline}'
1
3
5
7
9

CPU使用率

vim cpu.sh
>#!/bin/bash
>
>cpu_us=`top -b -n 1 | grep Cpu | awk '{print $2}'`
>echo $cpu_us
>cpu_sy=`top -b -n 1 | grep Cpu | awk -F ',' '{print $2}' | awk '{print $1}'`
>echo $cpu_sy
>echo "$cpu_us+$cpu_sy" | bc
[root@localhost ~]# ./cpu.sh 
0.0
3.1
3.1

echo "A B C D" | awk '{OFS="|"; print $0 ;$1=$1; print $0}'
$1=$1是用来重新激活$0的重新赋值,也就是说每一个字段和字段数NF的改变会促使awk重新计算$0的值,通常是在改变OFS后面需要输出$0时这样做。

[root@localhost ~]# echo "A B C D" | awk '{OFS="|"; print $0 ;$1=$1; print $0}'
A B C D
A|B|C|D

3. awk数组循环

  1. 使用awk建立数组
    awk 'BEGIN {a[0]=10; a[1]=20; print a[0]}'
    awk 'BEGIN {a[0]=10; a[1]=20; print a[1]}'
[root@localhost ~]# awk 'BEGIN {a[0]=10; a[1]=20; print a[0]}'
10
[root@localhost ~]# awk 'BEGIN {a[0]=10; a[1]=20; print a[1]}'
20

awk 'BEGIN {a["abc"]=10; a["xyz"]=20; print a["abc"]}'
awk 'BEGIN {a["abc"]=10; a["xyz"]=20; print a["xyz"]}'

[root@localhost ~]# awk 'BEGIN {a["abc"]=10; a["xyz"]=20; print a["abc"]}'
10
[root@localhost ~]# awk 'BEGIN {a["abc"]=10; a["xyz"]=20; print a["xyz"]}'
20

awk 'BEGIN {a["abc"]="aabbcc"; a["xyz"]="xxyyzz"; print a["abc"]}'
awk 'BEGIN {a["abc"]="aabbcc"; a["xyz"]="xxyyzz"; print a["xyz"]}'

[root@localhost ~]# awk 'BEGIN {a["abc"]="aabbcc"; a["xyz"]="xxyyzz"; print a["abc"]}'
aabbcc
[root@localhost ~]# awk 'BEGIN {a["abc"]="aabbcc"; a["xyz"]="xxyyzz"; print a["xyz"]}'
xxyyzz

awk 'BEGIN {a[0]=10; a[1]=20; a[2]=30; for (i in a) {print i,a[i]}}'

[root@localhost html]# awk 'BEGIN {a[0]=10; a[1]=20; a[2]=30; for (i in a) {print i,a[i]}}'
0 10
1 20
2 30

注1:BEGIN中的命令只执行一次
注2:awk数组的下标除了可以使用数字,也可以使用字符串,字符串需要使用双引号

  1. awk循环遍历
[root@localhost ~]# cat test1.txt
aaa
aaa
bbb
ccc
aaa
bbb
aaa
[root@localhost ~]# awk '{a[1]++} END{for(i in a) {print a[i]}}' test1.txt
7

注:a[1]初始为0,a[1]++后即为1,而这里awk中的a[1]++最终的值是由test1.txt文本内容有多少行决定的,文本逐行读取完毕后再执行END中的命令。

使用awk获取文件test1.txt中的重复行及次数

[root@localhost ~]# awk '{a[$1]++} END{for(i in a) {print a[i],i}}' test1.txt
4 aaa
1 ccc
2 bbb
[root@localhost ~]# awk '{a[$1]++} END{for(i in a) {print a[i],i}}' test1.txt | sort -r
4 aaa
2 bbb
1 ccc

注:也可使用sort排序后通过“uniq -c”获取重复行次次数。以上操作是我们在日常工作中常用的运维手段,crontab日常监控以及排障时经常用得到。
扩展--运维工作常用的查看命令或文件

监控项目 监控命令或文件
cpu负载 uptime
内存容量 free -m
硬盘空间 df -h
网卡流量 ifconfig 网卡名称(如ens33)
安装的软件包数量 rpm -qa | wc -l
账户数量 /etc/passwd
当前登录的账户数量 who
进程数量 ps aux
异常登录信息 /var/log/secure

例如:
使用awk统计httpd访问日志中每个客户端IP的出现次数
awk '{ip[$1]++} END{for (i in ip) {print ip[i],i}}' /var/log/httpd/access_log | sort -r

[root@localhost ~]# awk '{ip[$1]++} END{for (i in ip) {print ip[i],i}}' /var/log/httpd/access_log | sort -r
82 192.168.122.88

注:定义数组,数组名称为ip,数字的下标为日志文件的第1列(也就是客户端的IP地址),++的目的在于对客户端进行统计技术,客户端IP出现一次计数器就加1.END中的指令在读取完文件后执行,通过循环将所有统计信息输出,for循环遍历的是数组名ip的下标。

上一篇:Java实现定时任务


下一篇:Java反射得到属性和属性的值和设置属性的值