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}' 文件名

awk '{print $0}' 文件名

输出所有内容

[root@gh date]# cat aa.txt
11
22
33
44
[root@gh date]# awk '{print}' aa.txt
11
22
33
44
[root@gh date]# awk '{print $0}' aa.txt
11
22
33
44

awk 'NRn,NRm {print}' 文件名

awk '(NR>=n) && (NR<=m) {print}' 文件名

awk 'NRn || NRm {print}' 文件名

输出第n行至第m行的内容

[root@gh date]# awk 'NR==1,NR==2 {print}' aa.txt
11
22
[root@gh date]# awk '(NR>=1) && (NR<=2) {print}' aa.txt
11
22
[root@gh date]# awk 'NR==1 || NR==2 {print}' aa.txt
11
22

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

[root@gh date]# awk '(NR%2)==1 {print}' aa.txt
11
33

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

[root@gh date]# awk '(NR%2)==0 {print}' aa.txt
22
44

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

[root@gh date]# awk '/^3/ {print}' aa.txt
33

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

[root@gh date]# awk '/4$/ {print}' aa.txt
44

2. 按字段输出文本

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

[root@gh ~]# awk -F ":" '{print $1}' /etc/passwd|tail
sssd
setroubleshoot
saned
gdm
gnome-initial-setup
sshd
avahi
postfix
tcpdump
gh

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

[root@gh ~]# awk -F ":" '{print $1,$3}' /etc/passwd|tail
sssd 991
setroubleshoot 990
saned 989
gdm 42
gnome-initial-setup 988
sshd 74
avahi 70
postfix 89
tcpdump 72
gh 1000

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

[root@gh ~]# 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@gh ~]# awk -F ":" '!($3<200){print}' /etc/passwd|tail
saslauth:x:995:76:Saslauthd user:/run/saslauthd:/sbin/nologin
unbound:x:994:989:Unbound DNS resolver:/etc/unbound:/sbin/nologin
chrony:x:993:988::/var/lib/chrony:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
geoclue:x:992:986:User for geoclue:/var/lib/geoclue:/sbin/nologin
sssd:x:991:985:User for sssd:/:/sbin/nologin
setroubleshoot:x:990:984::/var/lib/setroubleshoot:/sbin/nologin
saned:x:989:983:SANE scanner daemon user:/usr/share/sane:/sbin/nologin
gnome-initial-setup:x:988:982::/run/gnome-initial-setup/:/sbin/nologin
gh:x:1000:1000:gh:/home/gh:/bin/bash

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

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

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

[root@gh ~]# awk -F ":" '{max=($3>=$4)?$3:$4;{print max}}' /etc/passwd|tail
991
990
989
42
988
74
70
89
72
1000

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

[root@gh ~]# awk -F ":" '{print NR,$0}' /etc/passwd|tail
35 sssd:x:991:985:User for sssd:/:/sbin/nologin
36 setroubleshoot:x:990:984::/var/lib/setroubleshoot:/sbin/nologin
37 saned:x:989:983:SANE scanner daemon user:/usr/share/sane:/sbin/nologin
38 gdm:x:42:42::/var/lib/gdm:/sbin/nologin
39 gnome-initial-setup:x:988:982::/run/gnome-initial-setup/:/sbin/nologin
40 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
41 avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
42 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
43 tcpdump:x:72:72::/:/sbin/nologin
44 gh:x:1000:1000:gh:/home/gh:/bin/bash

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

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

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

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

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

[root@gh ~]# 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@gh ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@gh ~]# echo $PATH | awk 'BEGIN{RS=":"};END{print NR}'
5

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

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

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

[root@gh ~]# free -m
              total        used        free      shared  buff/cache   available
Mem:           1980         447        1192          12         340        1375
Swap:          2047           0        2047
[root@gh ~]# free -m|awk '/Mem:/ {print int($3/$2*100)"%"}'
22%

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

[root@gh ~]# top -b -n 1 | awk '/%Cpu/ {print $0}'
%Cpu(s):  0.0 us,  3.1 sy,  0.0 ni, 96.9 id,  0.0 wa,  0.0 h
[root@gh ~]# top -b -n 1 | awk -F"," '/%Cpu/ {print $4}' | awk '{print $1}'
94.3

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@gh ~]# date -d "$(awk -F "." '{print $1}' /proc/uptime) second ago" +"%F %H:%M:%S"
2022-08-15 08:55:49

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

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

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

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

注:当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@gh ~]# seq 10 | awk '{getline; print}'
2
4
6
8
10
[root@gh ~]# seq 10 | awk '{print; getline}'
1
3
5
7
9

CPU使用率

[root@gh date]# cat 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@gh date]# ./cpu.sh 
3.1
0.0
3.1

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

[root@gh date]# 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@gh date]# awk 'BEGIN {a[0]=10; a[1]=20; print a[0]}'
10
[root@gh date]# 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@gh date]# awk 'BEGIN {a["abc"]=10; a["xyz"]=20; print a["abc"]}'
10
[root@gh date]# 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@gh date]# awk 'BEGIN {a["abc"]="aabbcc"; a["xyz"]="xxyyzz"; print a["abc"]}'
aabbcc
[root@gh date]# 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@gh date]# 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@gh date]# cat test1.txt
aaa
aaa
bbb
ccc
aaa
bbb
aaa
[root@gh date]# 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@gh date]# awk '{a[$1]++} END{for(i in a) {print a[i],i}}' test1.txt
4 aaa
1 ccc
2 bbb
[root@gh date]# 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的下标。

posted @ 2022-08-15 08:35  玖拾一  阅读(101)  评论(0编辑  收藏  举报