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 date]# awk 'NR==1,NR==2 {print}' aa.txt
11
22
[root date]# awk '(NR>=1) && (NR<=2) {print}' aa.txt
11
22
[root 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 ~]# awk -F ":" '{print $1}' /etc/passwd|tail
sssd
setroubleshoot
saned
gdm
gnome-initial-setup
sshd
avahi
postfix
tcpdump
gh
awk -F ":" '{print
以:号为分隔符,输出每行的第n个和第m个字段
[root ~]# 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 ~]# 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个字段的值赋给max,否则把第m个字段的值赋给max
[root ~]# 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也不是字符串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(
查看当前内存使用百分比
[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 ~]# 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 ~]# 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和
当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
[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数组循环
- 使用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数组的下标除了可以使用数字,也可以使用字符串,字符串需要使用双引号
- 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 date]# awk '{a[$1]++} END{for(i in a) {print a[i],i}}' test1.txt
4 aaa
1 ccc
2 bbb
[root 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 ~]# 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的下标。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了