文本处理三剑客之 GREP

一、grep

grep:

​ 作用:文本搜索工具,根据指定的“模式”来对需要处理的文本逐行进行检查,打印匹配到的行

​ 模式:由正则表达式及字符构成的搜索条件

​ 格式:grep [OPTION] PATTERN [FILE...]

常见选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-color=auto    对匹配到的文本进行着色显示,默认
-m # 匹配#次后停止
-v 显示没有被pattern匹配到的行,即取反
-i 忽略字符大小写
-n 显示匹配的行号
-c 统计匹配到的模式出现的行数
-o 仅显示匹配到的字符串
-q 静默模式,不输出任何信息
-A # after,后#行
-B # before,前#行
-C # context,前后各#行
-e 实现多个选项间的逻辑or关系,如grep –e 'cat' -e 'dog' file
-w 匹配整个单词
-E 使用扩展的正则表达式,相当于egrep
-F 不支持正则表达式,相当于fgrep
-f file 根据模式文件处理,将一个文件作为模式的条件
-r 递归目录,但不处理软链接
-R 递归目录,处理软链接

范例

(1)取两个文件相同的行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 将a.txt文件作为条件匹配b.txt文件中的内容
[root@Rocky8-mini ~]# cat a.txt
2
3
4
a
[root@Rocky8-mini ~]# cat b.txt
1
3
4
5
a
[root@Rocky8-mini ~]# grep -f a.txt b.txt
3
4
a

(2)分区利用率最大的值

1
2
[root@Rocky8-mini ~]# df | grep '^/dev/sd' | tr -s ' ' | cut -d' ' -f5 | sort -n | tail -1
[root@Rocky8-mini ~]# df | grep '^/dev/sd' | grep -o '\<[0-9]\{,3\}%' | sort -nr | head -n1

(3)连接当前主机最多的前3个IP及连接数

1
[root@Rocky8-mini ~]# ss -nt | grep '^ESTAB' | tr -s ' ' : | cut -d: -f6 | sort | uniq -c | sort -nr | head -n3

(4)统计当前连接状态

1
[root@Rocky8-mini ~]# ss -nta | grep -v '^State' | cut -d" " -f1 | sort | uniq -c

(5)显示 /etc/profile 文件中非#开头及空白行的内容

1
2
3
[root@Rocky8-mini ~]# grep -v "^#" /etc/profile | grep -v '^$'
[root@Rocky8-mini ~]# grep -v -e "^#" -e "^$" /etc/profile
[root@Rocky8-mini ~]# grep -v '^#\|^$' /etc/profile

(6)显示IP地址相关信息

1
[root@Rocky8-mini ~]# ifconfig | grep -E '([0-9]{1,3}.){3}[0-9]{1,3}'

(7)算出所有人的年龄总和

1
2
3
4
5
6
7
[root@Rocky8-mini ~]# cat age.txt 
xiaoming:20
xiaohong:18
xiaoqiang:22
[root@Rocky8-mini ~]# cut -d: -f2 age.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc
[root@Rocky8-mini ~]# grep -Eo '[0-9]+' age.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc
[root@Rocky8-mini ~]# grep -Eo '[0-9]+' age.txt | paste -s -d+ | bc

(8)其它选项示例

1
2
3
4
5
6
7
8
9
10
11
[root@Rocky8-mini ~]# grep -w root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@Rocky8-mini ~]# grep '\<root\>' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

[root@Rocky8-mini ~]# grep -E "^(.*)\>.*\<\1$" /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

文本处理三剑客之 SED

一、sed

1.1 sed 工作原理

​ 当sed处理某个文件时,首先读取该文件的第一行进入到模式空间中,然后使用sed命令处理该模式空间中的内容,处理完成后,将模式空间中的内容输出到屏幕,接着处理下一行,直至文件末尾。

​ sed命令默认执行的是p操作,即打印操作,当没有指定特定的操作时,直接将该内容输出至屏幕;如果模式空间中执行了打印操作,那么该行内容就会被打印输出至屏幕,模式空间中的内容默认打印至屏幕,这是该行内容在屏幕上就会出现两次。

1.2 sed基本用法

SYNOPSIS
sed [OPTION]... {script-only-if-no-other-script} [input-file]...

常用选项:

1
2
3
4
5
6
-n        不输出模式空间中的内容至屏幕,即不自动打印
-e 多点编辑
-f FILE 从指定的文件中读取编辑脚本
-r,-E 使用扩展的正则表达式
-i.bak 备份文件并原处编辑
-s 将多个文件视为独立文件

script 格式:地址+命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 地址格式
1. 没有给地址范围,默认对全文做处理
2. 单个地址
#:指定第#行
$:最后一行
/pattern/:被模式匹配到的每一行
3. 地址范围
#,# 从第#行到第#行
#,+# 如3,+4;从第3行到第7行
/pat1/,/pat2/ 从被pat1模式匹配到的行到被pat2模式匹配到的行
#,/pat/
/pat/,#
4. 步进 ~
1~2 奇数行
2~2 偶数行

# 命令
p 打印当前模式空间中的内容,追加到默认输出之后
Ip 忽略大小写输出
d 删除模式空间中匹配到的行,并读取下一行内容进行处理
a [\]text 在指定行后面追加文本,支持使用\n实现多行追加
i [\]text 在行前面插入文本
c [\]text 替换行为单行或多行文本
w file 保存模式匹配到的行到指定文件
r file 读取指定文件的文本至模式空间中匹配到的行后
= 为模式空间中的行打印行号
! 模式空间中匹配行取反处理
q 退出sed

# 查找替换
s/pattern/string/修饰符 分隔符可以是s@@@或s###
替换时可用修饰符:
g 行内全局替换
p 显示替换成功的行
w /PATH/FILE 将替换成功的行保存至文件中
I,i 忽略大小写

简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
[root@Rocky8-mini ~]# sed '' /etc/issue
\S
Kernel \r on an \m

[root@Rocky8-mini ~]# sed 'p' /etc/issue
\S
\S
Kernel \r on an \m
Kernel \r on an \m

[root@Rocky8-mini ~]# ifconfig ens33 | sed -n '2p'
inet 192.168.119.128 netmask 255.255.255.0 broadcast 192.168.119.255

[root@Rocky8-mini ~]# ifconfig ens33 | sed -n '/netmask/p'
inet 192.168.119.128 netmask 255.255.255.0 broadcast 192.168.119.255

[root@Rocky8-mini ~]# sed -n '$p' /etc/passwd
saslauth:x:994:76:Saslauthd user:/run/saslauthd:/sbin/nologin

[root@Rocky8-mini ~]# df | sed -n '/^\/dev\/sd/p'
/dev/sda1 1038336 216624 821712 21% /boot

[root@Rocky8-mini ~]# seq 10 | sed -n '1,2p'
1
2

# seq.log 文件中有1~5行数
[root@Rocky8-mini ~]# sed -e '2d' -e '5d' seq.log
1
3
4

# 显示非注释行和空行
[root@Rocky8-mini ~]# sed '/^#/d;/^$/d' /etc/profile

# 实时编辑seq.log文件,并备份原来的文件
[root@Rocky8-mini ~]# cat seq.log
1
2
3
4
5
[root@Rocky8-mini ~]# sed -i.bak '2d;3d' seq.log
[root@Rocky8-mini ~]# cat seq.log;cat seq.log.bak
1
4
5
1
2
3
4
5

# 删除/etc/fstab文件中所有非#开头的行
[root@centos7 ~]# sed -n '/^#/!p' /etc/fstab

范例

(1)修改网卡名为eth0,并使其生效

1
2
3
[root@centos7 ~]# sed -Ei.bak '/^GRUB_CMDLINE_LINUX/s/(.*)(")$/\1 net.ifnames=0\2/' /etc/default/grub
[root@centos7 ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
[root@centos7 ~]# reboot

(2)取IP地址

1
[root@centos7 ~]# ifconfig eth0 | sed -nr '2s/[^0-9]+([0-9.]+).*/\1/p'

(3)取基名和目录名

1
2
3
4
# 取基名
[root@centos7 ~]# echo "/etc/sysconfig/network-scripts/" | sed -r 's@(^/.*/)([^/]+/?)@\2@'
# 取目录名
[root@centos7 ~]# echo "/etc/sysconfig/network-scripts/" | sed -r 's@(^/.*/)([^/]+/?)@\1@'

(4)取文件的前缀和后缀

1
2
3
4
5
6
7
8
9
[root@centos7 ~]# ls
anaconda-ks.cfg
# 取前缀
[root@centos7 ~]# ls | sed -En 's/(.*)\.([^.]+)/\1/p'
# 取后缀
[root@centos7 ~]# ls | sed -En 's/(.*)\.([^.]+)/\2/p'
# 同理,当想取更多的后缀时
[root@centos7 ~]# echo a.b.c.tar.gz | sed -nr 's@.*\.([^.]+)\.([^.]+)@\1.\2@p'
tar.gz

(5)将非#开头的行加#

1
2
3
# "&"表示引用前面匹配到的所有内容
[root@centos7 ~]# sed -nr 's/^[^#]/#&/p' /etc/fstab
[root@centos7 ~]# sed -nr 's/^[^#](.*)/#\1/p' /etc/fstab

(6)取分区利用率

1
2
3
4
5
[root@centos7 ~]# df | sed -nr '/^\/dev\/sd/s@ .* ([0-9]+)%.*@ \1@p'
/dev/sda1 15
/dev/sdb1 55
/dev/sdc1 56
# 空格之前的内容默认会被打印出来

(7)查看配置文件

1
2
3
4
5
[root@centos7 ~]# yum -y install httpd
# 删除空行和以#开头的行
[root@centos7 ~]# sed -r '/^(#|$)/d' /etc/httpd/conf/httpd.conf
# 将行首后添加多个空白符之后有#的行也删除
[root@centos7 ~]# sed -e '/^$/d' -e '/^[[:space:]]*#/d' /etc/httpd/conf/httpd.conf

(8)引用变量

1
2
[root@centos7 ~]# echo | sed "s/^/$RANDOM/"
29428

文本处理三剑客之 AWK

一、AWK

1.1 AWK 简介及基本用法

gawk - pattern scanning and processing language

  • 文本处理
  • 输出格式化的文本报表
  • 执行算术运算
  • 执行字符串操作

格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
awk [options] 'program' var=value file...
awk [options] -f programfile var=value file...

program 通常放在单引号中,通常由三种部分组成
- BEGIN 语句块
- 模式匹配的通用语句块
- END 语句块

常见选项:
- -F"分隔符" 指明输入时用到的字段分隔符,默认的分隔符是若干个连续空白符
- -v var=value 变量赋值

program 格式:
pattern{action statements;...}
pattern: 决定动作语句何时触发及触发事件,如:BEGIN、END、正则表达式等
action statements:对数据进行处理,放在{}内指明,常见printprintf

awk 工作过程

image-20220919232411154
1
2
3
4
5
6
7
1、通过关键字BEGIN执行BEGIN块的内容,即BEGIN后花括号{}的内容
2、完成BEGIN块的执行,开始执行body块
3、读入有\n换行符分割的记录
4、将每个记录按指定的域分隔符划分域,填充域,$0表示所有域(即该行所有内容),$1表示第一个域,$n表示第n个域
5、依次执行各body块,pattern部分表示匹配到该行内容后才会执行awk-command的内容
6、循环读取每一行直至结束,完成body块执行
7、执行END块语句,输出最终结果

分隔符、域和记录

  • 由分隔符将字段分隔形成的各项成为域,标记$1,$2,$3…$n称为域标识,$0为所有域
  • 文件的每一行称为记录
  • 如果省略action,默认执行print $0 的操作

常用的action分类

  • output statements:print,printf
  • Expressions:算术,比较表达式等
  • Compound statements
  • Control statements
  • input statements

awk控制语句

  • { statements;··· } 组合语句
  • if 语句
  • while 语句
  • do ··· while 语句
  • for 语句
  • break、continue、exit

1.2 动作 print

格式:

1
print item1,item2,···

说明:

  • 逗号分隔符
  • 输出item可以是字符串、数值、当前记录的字段、变量或awk的表达式
  • 如果省略item,则默认print $0
  • 固定字符使用“ ”引起来,变量和字符不需要

示例:

1
2
3
4
5
[root@centos7 ~]# seq 1 | awk '{print "hello,awk!"}'
hello,awk!

[root@centos7 ~]# awk -F: '{print $1,$3}' /etc/passwd
[root@centos7 ~]# awk -F: '{print $1"\t"$3}' /etc/passwd

Example1:取出网站访问量最大的前3个IP

1
2
3
4
5
6
7
8
9
10
[root@centos7 ~]# cat nginx.access.log-20200428 | head -5
58.87.87.99 - - [27/Apr/2020:03:10:51 +0800] "POST /wp-cron.php?doing_wp_cron=1587928251.0032949447631835937500 HTTP/1.1" ""sendfileon
61.131.3.225 - - [27/Apr/2020:03:10:51 +0800] "GET / HTTP/1.1" ""sendfileon
157.245.106.153 - - [27/Apr/2020:03:10:52 +0800] "GET /wp-login.php HTTP/1.1" ""sendfileon
157.245.106.153 - - [27/Apr/2020:03:10:53 +0800] "POST /wp-login.php HTTP/1.1" ""sendfileon
157.245.106.153 - - [27/Apr/2020:03:10:54 +0800] "POST /xmlrpc.php HTTP/1.1" ""sendfileon
[root@centos7 ~]# awk '{print $1}' nginx.access.log-20200428 | sort | uniq -c | sort -nr | head -n 3
5498 122.51.38.20
2161 117.157.173.214
953 211.159.177.120

Example2:取出分区利用率

1
2
3
4
5
6
7
8
9
10
# 分隔符支持使用扩展的正则表达式
[root@centos7 ~]# df | grep "^/dev/sd" | awk -F"[[:space:]]+|%" '{print $1,$5}'
/dev/sdc1 56
/dev/sdb1 55
/dev/sda1 15

[root@centos7 ~]# df | awk -F"[[:space:]]+|%" '/^\/dev\/sd/{print $1,$5}'
/dev/sdc1 56
/dev/sdb1 55
/dev/sda1 15

Example3:取Nginx的访问日志中的IP和时间

1
2
3
4
[root@centos7 ~]# awk -F'[[ ]' '{print $1,$5}' nginx.access.log-20200428 | head -3
58.87.87.99 27/Apr/2020:03:10:51
61.131.3.225 27/Apr/2020:03:10:51
157.245.106.153 27/Apr/2020:03:10:52

Example4:取 ifconfig 输出结果中的地址

1
2
3
4
[root@centos7 ~]# ifconfig eth0 | awk '/\<inet\>/{print $2}'
192.168.119.165
[root@centos7 ~]# ifconfig eth0 | sed -nr '2s/^[^0-9]+([0-9.]+) .*$/\1/p'
192.168.119.165

Example5:参考主机文件host.log格式,提取“.wuhaolam.top”前面主机名部分并写入到该文件中

1
2
3
4
5
6
7
8
9
10
11
12
[root@centos7 ~]# cat host.log 
1 www.wuhaolam.top
2 blog.wuhaolam.top
3 study.wuhaolam.top
[root@centos7 ~]# awk -F'[ .]' '{print $2}' host.log >> host.log
[root@centos7 ~]# cat host.log
1 www.wuhaolam.top
2 blog.wuhaolam.top
3 study.wuhaolam.top
www
blog
study

1.3 awk 变量

1.3.1 awk常见的内置变量

  • FS:输入字段分隔符,默认为空白字符,相当于 -F
1
2
3
4
5
6
7
8
9
# 使用FS指定,可以当作变量在输出时当作分隔符
[root@centos7 ~]# awk -v FS=':' '{print $1,FS,$3}' /etc/passwd | head -3
root : 0
bin : 1
daemon : 2
[root@centos7 ~]# awk -v FS=":" '{print $1FS$3}' /etc/passwd | head -n 3
root:0
bin:1
daemon:2
  • OFS:输出字段分隔符,默认为空白字符
1
2
3
4
[root@centos7 ~]# awk -v FS=':' '{print $1,$3,$7}' /etc/passwd  | head -n1 
root 0 /bin/bash
[root@centos7 ~]# awk -v FS=':' -v OFS=':' '{print $1,$3,$7}' /etc/passwd | head -n1
root:0:/bin/bash
  • RS:输入记录record分隔符,以指定的符号作为一个新纪录的分割符
1
2
3
4
5
6
7
8
[root@centos7 ~]# grep "polkitd" /etc/passwd
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
[root@centos7 ~]# grep "polkitd" /etc/passwd | awk -v RS=' ' '{print}'
polkitd:x:999:998:User
for
polkitd:/:/sbin/nologin

[root@centos7 ~]#
  • ORS:输出记录分隔符,输出时用指定符号代替换行符
1
2
3
[root@centos7 ~]# grep "polkitd" /etc/passwd | awk -v RS=' ' -v ORS=' ' '{print}'
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
[root@centos7 ~]#
  • NF:字段个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 引用内置变量不需要使用$
# 将显示以:作为分隔符的每一个记录将会被分割成多少个域的数量
[root@centos7 ~]# awk -F: '{print NF}' /etc/fstab
0
1
1
3
1
1
1
1
1
1
1
1
1

Example1:连接数最多的前三个IP

1
2
3
4
5
6
7
8
9
10
11
12
[root@centos7 ~]# cat ss.log 
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 123.57.218.140:80 210.21.36.228:17036
ESTAB 0 0 127.0.0.1:55388 127.0.0.1:27017
ESTAB 0 0 123.57.218.140:22 101.200.188.230:42002
ESTAB 0 96 123.57.218.140:22 61.149.193.234:50314
···
[root@centos7 ~]# awk -F" +|:" '{print $(NF-2)}' ss.log | sort | uniq -c | sort -nr | head -n3
44 127.0.0.1
10 113.234.28.244
8 124.64.18.135
[root@centos7 ~]# awk -F" +|:" '/^ESTAB/{print $(NF-2)}' ss.log | sort | uniq -c | sort -nr | head -n3

Example2:将连接数超过100个以上的IP放入黑名单拒绝访问

1
2
3
4
5
6
7
8
9
[root@centos7 ~]# cat deny_IP.sh 
LINK=100
while true;do
ss -nt | awk -F' +|:' '/^ESTAB/{print $(NF-2)}' | sort | uniq -c | while read count ip;do
if [ $count -gt $LINK ];then
iptables -A INPUT -s $ip -j REJECT
fi
done
done
  • NR:记录的编号
1
2
3
4
[root@centos7 ~]# awk -F: 'END{print NR}' /etc/passwd
21
[root@centos7 ~]# awk -F: 'BEGIN{print NR}' /etc/passwd
0

Example1:显示awk处理文本时的记录及每条记录对应的内容

1
2
3
4
5
[root@centos7 ~]# awk '{print NR,$0}' /etc/issue /etc/centos-release
1 \S
2 Kernel \r on an \m
3
4 CentOS Linux release 7.9.2009 (Core)

Example2:取IP地址

1
2
[root@centos7 ~]# ifconfig eth0 | awk '/\<inet\>/{print $2}'
[root@centos7 ~]# ifconfig eth0 | awk 'NR==2{print $2}'
  • FNR:对各自的文件的记录进行编号
1
2
3
4
5
6
7
8
9
10
[root@centos7 ~]# awk '{print NR,$0}' /etc/issue /etc/centos-release
1 \S
2 Kernel \r on an \m
3
4 CentOS Linux release 7.9.2009 (Core)
[root@centos7 ~]# awk '{print FNR,$0}' /etc/issue /etc/centos-release
1 \S
2 Kernel \r on an \m
3
1 CentOS Linux release 7.9.2009 (Core)
  • FILENAME:当前文件名
1
2
# 每行记录前显示该条记录所在的文件
[root@centos7 ~]# awk '{print FILENAME}' /etc/fstab
  • ARGC:命令行参数的个数
1
2
[root@centos7 ~]# awk 'BEGIN{print ARGC}' /etc/issue /etc/redhat-release 
3
  • ARGV:数组,保存的是命令行所给的各参数,ARGV[0],····
1
2
3
4
[root@centos7 ~]# awk 'BEGIN{print ARGV[0]"\n"ARGV[1]"\n"ARGV[2]}' /etc/issue /etc/redhat-release 
awk
/etc/issue
/etc/redhat-release

1.3.2 自定义变量

​ 自定义变量区分大小写,有如下两种定义方式:

  • -v var=value
  • 在program中直接定义
1
2
3
4
5
6
7
[root@centos7 ~]# awk -v test1=test2="hello" 'BEGIN{print test1,test2}'
test2=hello

[root@centos7 ~]# awk 'BEGIN{test1=test2="hello";print test1,test2}'
hello hello

注:若两种方式同时使用,在program中定义的变量优先级更高

1.4 printf 动作

printf 可以实现格式化输出

1
2
3
4
5
6
printf "FORMAT", item1, item2, ...

说明:
- 必须指定FORMAT
- 不会自动换行,需要显示给出换行控制符\n
- FORMAT 中需要分别为后面每个item指定格式符

格式符:与 item 对应

1
2
3
4
5
6
7
8
%s: 显示字符串
%d, %i: 显示十进制整数
%f: 显示为浮点数
%e, %E: 显示科学计数法数值
%c: 显示字符的ASCII码
%g, %G: 以科学计数法或浮点数显示数值
%u: 无符号整数
%%: 显示百分号本身

修饰符

1
2
3
#[.#]  第一个数字控制显示的宽度;第二个#表示小数点后精度
- 左对齐(默认右对齐),如 %-15s
+ 显示数值的正负符号,如 %+d

示例

1
2
3
4
5
6
7
8
awk -F: '{printf "%s\n",$1}' /etc/passwd

awk -F: '{printf "%20s\n",$1}' /etc/passwd
awk -F: '{printf "%-20s\n",$1}' /etc/passwd | cat -A

awk -F: '{printf "%-20s %d\n",$1,$3}' /etc/passwd

awk -F: '{printf "Username: %-20s UID: %d\n",$1,$3}' /etc/passwd

1.5 操作符

算数操作符

1
2
3
x+y, x-y, x*y, x/y, x^y, x%y
-x 取负数
+x 将字符串转换为数值

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

赋值操作符

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

示例

1
2
[root@wh-aliyun ~]# awk 'BEGIN{i=0;print i++,i}'
0 1

比较操作符

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

示例

1
2
3
4
5
6
[root@wh-aliyun ~]# awk 'NR==2' /etc/issue
Kernel \r on an \m

[root@wh-aliyun ~]# awk -F: '$3>=1000' /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
wh:x:1000:1000::/home/wh:/bin/bash

示例:取奇数行和偶数行

1
2
3
4
5
6
7
8
9
10
11
12
[root@wh-aliyun ~]# seq 10 | awk 'NR%2!=0'
1
3
5
7
9
[root@wh-aliyun ~]# seq 10 | awk 'NR%2==0'
2
4
6
8
10

模式匹配符

1
2
~       左边是否和右边匹配,包含关系
!~ 是否不匹配

示例

1
2
3
4
5
6
7
8
9
[root@wh-aliyun ~]# awk -F: '$0 ~ "^root" ' /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@wh-aliyun ~]# awk -F: '$0 ~ "^root"{print $1}' /etc/passwd
root

[root@wh-aliyun ~]# df | awk -F"[ %]+" '$0 ~ /^\/dev\/vd/ {print $5}'
9
[root@wh-aliyun ~]# ip a show eth0 | awk -F"[ /]+" 'NR==5 {print $3}'
172.22.21.46

逻辑操作符

1
2
3
与: &&
或: ||
非: !

示例

1
2
3
4
5
[root@wh-aliyun ~]# awk -v i=abc 'BEGIN{print !i}'
0
[root@wh-aliyun ~]# awk -F: '$3>=0 && $3<=1000 {print $1,$3}' /etc/passwd
[root@wh-aliyun ~]# awk -F: '$3==0 || $3>=1000 {print $1,$3}' /etc/passwd
[root@wh-aliyun ~]# awk -F: '!($3>=500) {print $1,$3}' /etc/passwd

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

1
2
# ? 左边的表达式成立执行就执行 : 左边的表达式,否则执行 : 右边的表达式
selector?if-true-expression:if-false-express

示例

1
2
3
4
awk -F: '{$3>=1000?usertype="common user":usertype="system user";printf "%-20s:%12s\n",$1,usertype}' /etc/passwd

[root@wh-aliyun ~]# df | awk -F"[ %]+" '/^\/dev\/vd/{$(NF-1)>10?disk="full":disk="OK";print $(NF-1),disk}'
9 OK

1.6 模式 PATTERN

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

  • 如果未指定:空模式,匹配每一行
1
awk -F: '{print $1,$3}' /etc/passwd
  • /regular expression/:仅处理模式匹配到的行,需要用/ /括起来
1
2
3
4
5
[root@centos7 ~]# df -Th | awk '/^\\/dev\\/sd/{print $1,$6}'
/dev/sda3 5%
/dev/sdb1 4%
/dev/sda2 11%
/dev/sda1 3%
  • relational expression:关系表达式,结果为“真”才会被处理

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

    假:结果为空字符串或0值

1
2
3
4
5
6
7
8
9
10
11
12
[root@centos7 ~]# seq 3 | awk ''
[root@centos7 ~]# seq 3 | awk '1'
1
2
3

[root@centos7 ~]# seq 3 | awk 'abc'
[root@centos7 ~]# seq 3 | awk -v abc=0 'abc'
[root@centos7 ~]# seq 3 | awk -v abc="aa" 'abc'
1
2
3

示例:

1
2
3
4
5
6
[root@centos7 ~]# awk -v FS=':' '$NF=="/bin/bash"{print $1FS$NF}' /etc/passwd
root:/bin/bash
ftpuser:/bin/bash
[root@centos7 ~]# awk -v FS=':' '$NF ~ "/bin/bash"{print $1FS$NF}' /etc/passwd
root:/bin/bash
ftpuser:/bin/bash
  • line ranges:行范围

不支持直接用行号,但可以使用变量NR间接指定行号 /pat1/,/pat2/ 不支持直接给出数字格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@centos7 ~]# seq 7 | awk 'NR>=3 && NR<6'
3
4
5

[root@centos7 ~]# awk 'NR>3 && NR<6{print NR,$0}' /etc/passwd
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

[root@centos7 ~]# awk '/^bin/,/^adm/' /etc/passwd
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
[root@centos7 ~]# sed -n '/^bin/,/^adm/p' /etc/passwd
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
  • BEGIN/END 模式

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

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

1
[root@centos7 ~]# awk -F: 'BEGIN {print "--------------"}NR>3 && NR<6{print $1,$3,$NF}END{print "------------"}' /etc/passwd

1.7 条件判断 if-else

语法:

1
2
if(condition){statement; ......} [else statement]
if(condition1){statement1} else if (condition2){statement2} else if (condition3) {statements} ...... else {statementN}

使用场景:对 awk 取得的整行或某个字段做条件判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1、UID 大于 1000 
[root@centos7 ~]# awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd

2、小于3,打印a; 小于5,打印b; 其它打印c
[root@centos7 ~]# seq 7 | awk '{if($1<3){print "a"} else if($1<5){print "b"} else {print "c"}}'
a
a
b
b
c
c
c

3、磁盘使用率大于10%就打印出该文件系统的设备名称和使用率
[root@centos7 ~]# df -Th | awk -F"[ %]+" '/\\/dev\\/sd/{if($6>10){print $1,$6}}'
/dev/sda2 11

1.8 条件判断 switch

语法:

1
switch(expression) {case VALUE1 or /REGEXP/: statement1; case VALUE2 or /REGEXP2/: statement2; ...; default: statementN}

1.9 循环 while

语法:

1
2
3
4
5
6
while (condition) {statement; ......}

条件为“真”,进入循环;条件为“假”,退出循环
使用场景:
对一行内的多个字段逐一类似处理时使用
对数组中的各元素逐一处理时使用

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@centos7 ~]# awk -v i=1 -v sum=0 'BEGIN{while(i<=100) {sum=sum+i;i++}; print sum}'
5050

# 内置函数length()返回字符数,而非字节数
[root@centos7 ~]# awk 'BEGIN{print length("hello")}'
5
[root@centos7 ~]# awk 'BEGIN{print length("中国")}'
2

[root@centos7-mini3 ~]# awk '/^[[:space:]]*linux16/{i=1; while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
linux16 7
/vmlinuz-3.10.0-1160.el7.x86_64 31
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
net.ifnames=0 13
linux16 7
/vmlinuz-0-rescue-b4f1e030bb9b4be0b3ac55e533a88806 50
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
net.ifnames=0 13

[root@centos7-mini3 ~]# awk '/^[[:space:]]*linux16/{i=1; while(i<=NF) {if(length($i)>20) {print $i,length($i)} i++}}' /etc/grub2.cfg
/vmlinuz-3.10.0-1160.el7.x86_64 31
root=/dev/mapper/centos-root 28
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21
/vmlinuz-0-rescue-b4f1e030bb9b4be0b3ac55e533a88806 50
root=/dev/mapper/centos-root 28
rd.lvm.lv=centos/root 21
rd.lvm.lv=centos/swap 21

1.10 循环 do-while

语法:

1
do {statement; ...} while(condition)

意义:无论真假,至少执行一次循环体

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

1.11 循环 for

语法:

1
2
3
4
5
6
7
for(expr1;expr2;expr3) {statement;...}

常见用法:
for(variable assignment;condition;iteration process) {for-body}

特殊用法:可遍历数组中的元素
for(var in array) {for-body}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@centos7-mini3 ~]# awk 'BEGIN{sum=0; for(i=1;i<=100;i++) {sum=sum+i} print sum}'
5050

[root@centos7-mini3 ~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {if (length($i)>30) {print $i,length($i)}}}' /etc/grub2.cfg
/vmlinuz-3.10.0-1160.el7.x86_64 31
/vmlinuz-0-rescue-b4f1e030bb9b4be0b3ac55e533a88806 50

[root@centos7-mini3 ~]# time (awk 'BEGIN{ total=0; for(i=0;i<=100000;i++) {total=total+i;} print total;}')
5000050000
real 0m0.012s
user 0m0.000s
sys 0m0.012s

[root@centos7-mini3 ~]# time ( total=0; for i in {1..100000}; do total=$(($total+$i)); done; echo $total )
5000050000
real 0m0.310s
user 0m0.301s
sys 0m0.010s

[root@centos7-mini3 ~]# time (for ((i=0;i<=100000;i++));do let total=$total+$i; done; echo $total)
5000050000
real 0m0.629s
user 0m0.575s
sys 0m0.054s

[root@centos7-mini3 ~]# time (seq -s "+" 100000 | bc)
5000050000
real 0m0.040s
user 0m0.007s
sys 0m0.034s


[root@centos7-mini3 ~]# echo '1*d3(#*(^y43' | awk -F "" '{ for(i=1;i<=NF;i++) {if ( $i ~ /[0-9]/ ) {str=(str $i)}} print str}'
1343

1.12 continue 和 break

语法:

1
2
3
4
5
continue 中断本次循环
continue [n]

break 中断整个循环
break [n]

示例

1
2
3
4
[root@centos7-mini3 ~]# awk 'BEGIN{sum=0; for (i=1;i<=100;i++) {if (i==50) continue; sum=sum+i} print sum}'
5000
[root@centos7-mini3 ~]# awk 'BEGIN{sum=0; for (i=1;i<=100;i++) {if (i==50) break; sum=sum+i} print sum}'
1225

1.13 next

next 可以提前结束对本行的处理而直接进入下一行处理(awk 自身循环)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 此循环为 awk 的自身循环,不能使用 continue 语句
[root@centos7-mini3 ~]# awk -F: '{if($3%2!=0) continue; print $1,$3}' /etc/passwd
awk: cmd. line:1: error: `continue' is not allowed outside a loop

[root@centos7-mini3 ~]# 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
sshd 74
geoclue 998

1.14 数组

语法:

1
2
3
4
5
6
7
8
9
10
awk 的数组为关联数组

array_name[index-expression]
weekdays["mon"]="Monday"

index-expression
- 利用数组,实现 key/value 功能
- 可使用任意字符串;字符串要使用双引号括起来
- 如果某数组元素事先不存在,在引用时,awk 会自动创建此元素,并将其值初始化为“空串”
- 若要判断数组中是否存在某元素,要使用“index in array”格式进行遍历

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos7-mini3 ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";weekdays["wed"]="wednesday";print weekdays["tue"]}'
Tuesday

[root@centos7-mini3 ~]# awk '!line[$0]++' /etc/issue
\S
Kernel \r on an \m

[root@centos7-mini3 ~]# awk '{print !line[$0]++, $0, line[$0]}' /etc/issue
1 \S 1
1 Kernel \r on an \m 1
1 1
[root@centos7-mini3 ~]# awk '{print !line[$0]++;print $0, line[$0]}' /etc/issue
1
\S 1
1
Kernel \r on an \m 1
1
1

示例:判断索引是否存在

1
2
3
4
[root@centos7-mini3 ~]# awk 'BEGIN{array["i"]="x";array["j"]="y";if ("i" in array) {print "存在,值为:",array["i"]} else {print "不存在"}}'
存在,值为: x
[root@centos7-mini3 ~]# awk 'BEGIN{array["i"]="x";array["j"]="y";if ("abc" in array) {print "存在,值为:",array["abc"]} else {print "不存在"}}'
不存在
  • 如果要遍历数组中的每一个元素,要使用 for 循环
1
2
3
for(var in array) {for-body}

note: var 会遍历 array 的每个索引

示例

1
2
3
4
5
6
7
8
9
10
[root@centos7 ~]# awk 'BEGIN{stu["1st"]="zhao";stu["2nd"]="qian";stu["3rd"]="sun";for(x in stu){print x":"stu[x]}}'
3rd:sun
2nd:qian
1st:zhao

# 显示主机连接状态出现的次数
[root@centos7 ~]# ss -nta | awk 'NR!=1{print $1}' | sort | uniq -c
25 ESTAB
14 LISTEN
50 TIME-WAIT

1.15 AWK 函数

AWK 的函数分为内置和自定义函数

官方文档:https://www.gnu.org/software/gawk/manual/gawk.html#Functions

1.15.1 常见的内置函数

  • 数值处理
1
2
3
rand(): 返回 0 和 1 之间的一个随机数
srand(): 配合 rand() 函数,生成随机数的种子
int(): 返回整数

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@centos7 ~]# awk 'BEGIN{srand();print rand()}'
0.518181
[root@centos7 ~]# awk 'BEGIN{srand();print rand()}'
0.39578
[root@centos7 ~]# awk 'BEGIN{srand();print rand()}'
0.471864

[root@centos7 ~]# awk 'BEGIN{srand();for(i=1;i<=5;i++)print int(rand()*100)}'
86
99
2
65
52
  • 字符串处理
1
2
3
4
length([s]): 返回指定字符串的长度
sub(r,s,[t]): 对t字符串搜索,r表示模式匹配的内容,并将第一个匹配内容替换为s
gsub(r,s,[t]): 对t字符串进行搜索,r表示的模式匹配的内容,并全部替换为s所表示的内容
split(s,array,[r]): 以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,.....

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 统计用户名的长度
[root@centos7 ~]# awk -F: '{print length($1)}' /etc/passwd

# sub 和 gsub 的区别
[root@centos7 ~]# echo "8888:08:08 08:08:08" | awk 'sub(/:/,"-",$0)'
8888-08:08 08:08:08
[root@centos7 ~]# echo "8888:08:08 08:08:08" | awk '{sub(/:/,"-",$0);print $0}'
8888-08:08 08:08:08

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

# split 用法示例
[root@centos7 ~]# netstat -tn | awk '/^tcp/{split($5,ip_port,":");count[ip_port[1]]++}END{for(i in count){print i,count[i]}}'
10.243.20.50 2
10.243.20.51 71
10.243.10.10 1
  • awk 中调用shell命令
1
system('cmd')

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

1
2
3
4
[root@centos7 ~]# awk 'BEGIN{system("hostname")}'
centos7
[root@centos7 ~]# awk 'BEGIN{score=100;system("echo your score is "score)}'
your score is 100
  • 时间函数

官方文档:https://www.gnu.org/software/gawk/manual/gawk.html#Time-Functions

1
2
systime()  当前时间到1970年1月1日的秒数
strftime() 指定时间格式

示例

1
2
3
4
5
[root@Rocky8-mini3 ~]# awk 'BEGIN{print systime()}'
1699323534

[root@Rocky8-mini3 ~]# awk 'BEGIN{print strftime("%Y-%m-%dT%H:%M",systime()-3600)}'
2023-11-06T20:20

1.15.2 自定义函数

自定义函数格式:

1
2
3
4
function name (parameter,parameter,...) {
statements
return expression
}

示例

1
2
3
4
5
6
7
8
9
[root@Rocky8-mini3 ~]# cat awk.file 
function sum(x,y) {
result=x+y
return result
}
BEGIN {print sum(a,b)}

[root@Rocky8-mini3 ~]# awk -v a=30 -v b=20 -f awk.file
50