Linux-文本三剑客
前言
grep、sed、awk 被称为 “文本三剑客”,它们是处理文本文件的强大工具,广泛用于日志分析、数据提取、文本转换等场景。三者各有侧重,常结合管道(|)使用
基于行处理的文本命令
- grep 过滤文本
- sed 过滤,替换文本
- awk 截取文本,统计文本
输出/etc/passwd文件里的uid大于1000的用户的信息,输入格式如下:
username: cali uid: 1001 gid: 1001
awk -F: ‘$3>1000{print “username:”$1,“uid:”$3,“gid:”$4}’ /etc/passwd
while 和 read
IFS 输入字段分割符 input field separator
[root@hz shell]# cat uid_gid.sh
#!/bin/bash
while IFS=“:” read username x uid gid others
do
if (( u i d > 1000 ) ) ; t h e n e c h o " u s e r n a m e : uid > 1000 ));then echo "username: uid>1000));thenecho"username:username uid: $uid gid: $gid"
fi
done </etc/passwd
[root@hz shell]# cat uid_gid_2.sh
#!/bin/bash
cat /etc/passwd|awk -F: ‘{print $1,$3,$4}’|while read username uid gid
do
if (( u i d > 1000 ) ) ; t h e n e c h o " u s e r n a m e : uid > 1000 ));then echo "username: uid>1000));thenecho"username:username uid: $uid gid: $gid"
fi
done
截取出根分区的使用率
grep
^ 表示以什么开头
$ 表示以什么结尾
[root@hz shell]# df |grep “/KaTeX parse error: Expected 'EOF', got '#' at position 72: …[root@hz shell]#̲ df |grep "/”|awk '{print KaTeX parse error: Expected 'EOF', got '}' at position 2: 5}̲' 33% [root@hz …"|awk ‘{print $5}’|tr -d “%”
一、grep
grep 过滤 通用的正则表达式分析程序
grep、egrep、fgrep
做匹配来过滤的
正则表达式:
文本过滤
输入校验
pattern 模式 --》模板
可以接受一个正则表达式
用途:在文件中查找并显示包含指定字符串的行
格式:grep [选项]… 模式 目标文件
-i
:查找时忽略大小写-v
:反转查找,输出与模式不相符的行-n
:显示符合模式要求的行号-r
:递归搜索所有文件-o
:只显示匹配的内容-E
: 支持更多的元字符(支持扩展正则)-A
: 找到匹配行以及后几行-B
:输出匹配行以及前几行
过滤掉不以#号开头和行,过滤掉空行
[root@db ~]# egrep -v "^#|^$" sshd_config
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
DenyUsers sc
SyslogFacility AUTHPRIV
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials no
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem sftp /usr/libexec/openssh/sftp-server
统计不同状态码出现次数
[root@db ~]#egrep -o "HTTP/1.1\" [1-5][0-9]{2}" access.log-20250828 |sort|uniq -c
21 HTTP/1.1" 200
38 HTTP/1.1" 304
4 HTTP/1.1" 404
2 HTTP/1.1" 500
过滤出文档中的ip地址,ip正则表达式? ipv4 [0-255].[0-255].[0-255].[0-255]
egrep "((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
过滤出/var/log/message中含有error的行,以及error信息的后5行
grep -i -A 5 'error' /var/log/messages
二、sed
sed – 编辑文本,过滤文本 非交互式 方便在脚本中使用
sed命令格式:
直接处理文本
sed [选项] 'command' file
前一个命令的输出也可以作为sed的输入
cat file| sed [选项] 'command'
选项
-i
直接修改文件内容-n
只打印模式匹配的行-f
将sed的命令写入一个文件内,从脚本文件中读取内容并执行-r
支持正则表达式-e
执行多个编辑命令时
sed常用指令 [address]command
p
– print 打印输出模块行!
取反 表示后面的命令对选定的行没有作用n
next 读取下一个输入的行,用下一个命令处理新的行i\
insert 在当前行的上面插入文本a\
append 在当前行的后面插入文本d
delete 删除行r
r filename 从filename读取 ,将filename的内容放在匹配的行之后w
w filename 将匹配到的内容写入文本N
追加下一行到当前模块的后面s
替换操作
多条命令进行组合
- 使用 分号 ;
- 使用-e
- 还可以使用 |
address – 确定的范围
1、空地址 – 全文处理
2、单地址 – 7p
3、步进地址 1~2, 2~2
4、地址对
N,M
从第N行到第M行N,+M
从第n行开始向下匹配M行N,/pattern/
从第n行到 pattern匹配的行 pattern支持正则表达式 使用//包裹起来/pattern1/,/pattern2/
从pattern1匹配到 pattern2 匹配的行/pattern/
匹配pattern的行$
最后一行
打印文件所有行并输出行号
[root@db ~]# sed -n “p” test.txt
1 aaa abc
2 xxx yyy
3 ppp
4 nihao
5 hello
6 sctl world
7 balabala
8 xixi
9 haha
仅打印第7行
[root@db ~]# sed -n “7p” test.txt
7 balabala
打印所有奇数行
[root@db ~]# sed -n “1~2p” test.txt
1 aaa abc
3 ppp
5 hello
7 balabala
9 haha
[root@db ~]# sed -n “p;n” test.txt
1 aaa abc
3 ppp
5 hello
7 balabala
9 haha
打印所有偶数行
[root@db ~]# sed -n “2~2p” test.txt
2 xxx yyy
4 nihao
6 sctl world
8 xixi
[root@db ~]# sed -n “n;p” test.txt
2 xxx yyy
4 nihao
6 sctl world
8 xixi
打印1到3行
[root@db ~]# sed -n “1,3p” test.txt
1 aaa abc
2 xxx yyy
3 ppp
第 1 行开始,打印当前行 + 后续 3 行
[root@db ~]# sed -n “1,+3p” test.txt
1 aaa abc
2 xxx yyy
3 ppp
4 nihao
从第 1 行开始,打印到 “首次包含字符 s 的行”
[root@db ~]# sed -n “1,/s/p” test.txt
1 aaa abc
2 xxx yyy
3 ppp
4 nihao
5 hello
6 sctl world
打印所有包含字符串 sctl 的行
[root@db ~]# sed -n “/sctl/p” test.txt
6 sctl world
在第 7 行之后插入一行内容 this is test
[root@db ~]# sed “7 a\this is test” test.txt
1 aaa abc
2 xxx yyy
3 ppp
4 nihao
5 hello
6 sctl world
7 balabala
this is test
8 xixi
9 haha
在第 7 行之前插入一行内容 this is test
[root@db ~]# sed “7 i\this is test” test.txt
1 aaa abc
2 xxx yyy
3 ppp
4 nihao
5 hello
6 sctl world
this is test
7 balabala
8 xixi
9 haha
删除第七行
[root@db ~]# sed “7d” test.txt
1 aaa abc
2 xxx yyy
3 ppp
4 nihao
5 hello
6 sctl world
8 xixi
9 haha
sed替换
[address]s/pattern/replacement/flags
-> 不修改原文件
- 支持正则表达式,支持分组向后引用
- & 表示已匹配的字符串
- flags: 替换标记
- g 表示行内全面替换
- p 表示打印行
- n n表示数字 1~512 表示替换第几个
- ng 从第几个开始替换
在第 1-5 行中,将所有字母 a(小写)替换为 *
[root@db ~]# sed '1,5s/a/\*/g' test.txt
1 *** *bc
2 xxx yyy
3 ppp
4 nih*o
5 hello
6 sctl world
7 balabala
8 xixi
9 haha
在第 1-5 行中,将所有 a、b、c、d(小写,连续字母范围)替换为 *
[root@db ~]# sed '1,5s/[a-d]/\*/g' test.txt
1 *** ***
2 xxx yyy
3 ppp
4 nih*o
5 hello
6 sctl world
7 balabala
8 xixi
9 haha
直接修改要用 -i 选项
sed -i 's/old/new/g'
test.txt
使用 -i 前,建议先执行不加 -i 的命令预览效果,确认替换正确
三、awk
awk是一种编程语言,对文本、数据进行处理
过滤、统计、截取
内部编程和c语言有相同之处
语法形式:
awk [选项] 'awk命令脚本' file
awk [选项] -f scriptfile file
常用的命令选项:
-F
指定分隔符 ,默认空白符-v
var=value 将外部变量传递给awk-f
scripfile 从脚本文件中读取awk命令
awk命令脚本基本结构体,由三大块组成
'BEGIN{语句块} /模式/{动作} END{ 语句块}'
BEGIN、模式通用语句块、END 都是可选部分
- 第一部分:执行BEGIN语句块中的内容,在处理文本之前,读取行之前被执行。一般用在初始化变量,打印一些表头
- 第二部分:pattern语句块 通用语句块,读取文本的每一行交给这个语句块处理
- 第三部分:读取完所有的行之后执行, 统计、分析结果这些信息在END中完成
以a1.txt为例
[root@docker ~]# cat a1.txt
1 a1 b1 3
2 a2 b1 4
3 a3 b2 2
4 a1 b2 5
5 a2 b3 4
6 a3 b3 3
[root@docker ~]# awk 'BEGIN{print "start....";print "start2....."}{print $2}END{print "end......"}' a1.txt
start....
start2.....
a1
a2
a3
a1
a2
a3
end......
[root@docker ~]# awk 'BEGIN{print "start....";print "start2....."}{print $2}' a1.txt
start....
start2.....
a1
a2
a3
a1
a2
a3
[root@docker ~]# awk '{print $2}' a1.txt
a1
a2
a3
a1
a2
a3
[root@docker ~]# awk 'BEGIN{print "start....";print "start2....."}' a1.txt
start....
start2.....
[root@docker ~]# awk 'BEGIN{print 3/2}' a1.txt
1.5
[root@docker ~]# awk '{print 3/2}' a1.txt
1.5
1.5
1.5
1.5
1.5
1.5
[root@docker ~]# echo|awk '{print 3/2}'
1.5
[root@docker ~]# echo|awk '{print 3>2}'
[root@docker ~]# echo|awk '{print 3<2}'
0
问题:根据 awk 的逻辑判断规则,数值比较结果会以 0(假)或 1(真) 输出,比较结果为假输出了0,但是比较结果为真并没有输出1?
通用语句块 – 模式和动作
模式 – 过滤:
/正则表达式/
可以使用正则表达式匹配
关系表达式(&& || !)
、运算符表达式(> < ==)
模式匹配表达式
: 用运算符~ !~
(表示匹配或不匹配)动作:
动作是由一个或多个命令、表达式组成,命令之间用分号隔开- 变量或数组的赋值
- 输出
- 使用内置函数
- 控制流语句
模式 – 正则表达式
[root@docker ~]# awk -F":" '/ss/{print}' /etc/passwd
dbus:x:81:81:System message bus:/:/sbin/nologin
sssd:x:998:998:User for sssd:/:/sbin/nologin
tss:x:59:59:Account used for TPM access:/:/usr/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/usr/share/empty.sshd:/usr/sbin/nologin
[root@docker ~]# awk -F":" '/^ss/{print}' /etc/passwd
sssd:x:998:998:User for sssd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/usr/share/empty.sshd:/usr/sbin/nologin
关系表达式、运算符表达
[root@docker ~]# awk -F":" '$3>2000{print $1}' /etc/passwd
[root@docker ~]# awk -F":" '$3==2001{print $1}' /etc/passwd
[root@docker ~]# awk -F":" '$3>1010 && $3 < 2000{print $1}' /etc/passwd
[root@docker ~]# awk -F":" '$3>1010 && $3 < 2000{print}' /etc/passwd
算术运算符 + - * / % ++ –
其他运算符:
- ?: 类似于javascript的三元运算
- in
[root@docker ~]# echo |awk 'BEGIN{a="bbb";print a=="bbb"?"ok":"error"}'
ok
模式匹配表达式
[root@docker ~]# awk -F":" '$1 ~ /ss{print}' /etc/passwd
[root@docker ~]# awk -F":" '$1 ~ /^ss/{print}' /etc/passwd
# 找到家目录不在/home 目录下的所有用户信息
[root@docker ~]# awk -F":" '$6 !~ /^\/home\//{print}' passwd
# 找到登录shell为/bin/bash并且uid大于500的用户信息
[root@docker ~]# awk -F ':' '$7=="/bin/bash" && $3>500 {print $0}' /etc/passwd
动作 输出
内部变量:
$0
表示整行文本$1
表示分隔出来的第一列文本$n
表示分隔出来的第n列文本NF
每行$0的字段数 (最后一列可表示为$NF)NR
当前处理的行号FS
当前的输入分隔符,默认是空白字符(空格和tab)field separatorOFS
当前的输出分隔符,默认是空格字符(空格)output field separator
[root@docker ~]# awk '{print "最后一列为:"$NF}' a1.txt
最后一列为:end
最后一列为:4
最后一列为:2
最后一列为:end
最后一列为:4
最后一列为:3
[root@docker ~]# awk '{print "最后一列为:"$NF" "$(NF-1)}' a1.txt
最后一列为:end 3
最后一列为:4 b1
最后一列为:2 b2
最后一列为:end 5
最后一列为:4 b3
最后一列为:3 b3
[root@docker ~]# awk 'NR>3{print "最后一列为:"$NF" "$(NF-1)}' a1.txt
最后一列为:end 5
最后一列为:4 b3
最后一列为:3 b3
[root@docker ~]# awk 'BEGIN{FS=" ";OFS="**"}{print $1,$2}' a1.txt
1**a1
2**a2
3**a3
4**a1
5**a2
6**a3
[root@docker ~]# awk 'BEGIN{FS=" "}{print $1,$2}' a1.txt
1 a1
2 a2
3 a3
4 a1
5 a2
6 a3
流程控制
[root@docker ~]# echo |awk 'BEGIN{
> num=100;
> if(num>90) {
> print "大于90"
> } else {}
> }'
大于90
[root@docker ~]# echo |awk 'BEGIN{num=100;if(num>90){print "大于90"}else{print "小于等于90"}}'
大于90
参数传递,awk接受外部变量
1、使用双引号
2、使用 -v
选项
3、将变量的接受放后面
[root@docker ~]# a=100
[root@docker ~]# b=200
# 1.双引号
[root@docker ~]# echo|awk "{print $a+$b}"
300
[root@docker ~]# echo|awk '{print $a+$b}'
0
# 2.-v
# 注意没有空格
[root@docker ~]# echo|awk -v var1=$a var2=$b '{print var1+var2}'
awk: fatal: cannot open file `{print var1+var2}' for reading: No such file or directory
[root@docker ~]# echo|awk -v var1=$a var2=$b'{print var1+var2}'
300
# 3.变量放后面
# 注意空格
[root@docker ~]# echo|awk '{print var1+var2}'var1=$a var2=$b
200
[root@docker ~]# echo|awk '{print var1+var2}' var1=$a var2=$b
300
统计
求取a1.txt文件中,第四列的平均值、最大值、最小值
[root@docker ~]# awk 'NR==1{max=$4} {max=($4>max)?$4:max} END{print max}' a1.txt
5
[root@docker ~]# awk 'NR==1{min=$4} {min=($4<min)?$4:min} END{print min}' a1.txt
2
[root@docker ~]# awk 'BEGIN{sum=0;count=0}{sum+=$4;count+=1}END{print sum/count}' a1.txt
3.5
数组的使用
分析、统计
分类聚合
类似python中的字典
arr[1]=“aa”
arr[2]=“bb”
arr[‘a’] = 1 #k-v存储 key-value
arr[‘b’] = 2
读取数组
{ for (i in arr){print arr[i]}}
{ for (i=1;i<=len;i++) {print arr[i]}}
# 没有相应的索引,也可以取值为空
[root@docker ~]# echo |awk 'BEGIN{t1["a"]=1;t2["b"]=2}{if(t1["a"]==1){print "ok"}}'
ok
[root@docker ~]# echo |awk 'BEGIN{t1["a"]=1;t2["b"]=2}{if(t1["a"]==1){print t1["c"]}}'
[root@docker ~]# echo |awk 'BEGIN{info["name"]="sc"; info["age"]=7}{print info["name"]}'
sc
[root@docker ~]# echo |awk 'BEGIN{info["name"]="sc"; info["age"]=18}{print info["age"]}'
18
[root@docker ~]# echo |awk 'BEGIN{info["name"]="sc"; info["age"]=7}{print info["sex"]}'
# 判断属组中有没有这个key -- in
[root@docker ~]# echo | awk 'BEGIN{info["name"]="sc";info["age"]=18}{if("name" in info){print "yes"}}'
yes
分组统计
[root@docker ~]# cat stu.txt
工号 姓名 性别 部门 工资
1 张三 女 SA 8000
2 李四 男 PE 7000
3 王二 女 SA 9000
4 小李 男 PE 8000
5 小张 女 PE 9000
6 小谢 男 SA 9000
7 小米 男 SA 8000
5 小陈 女 PE 7000
[root@docker ~]# awk 'NR>1{ stuff[$3]+=$4 } END{ for(i in stuff) {print i"-->"stuff[i] }}' stu.txt
男-->0
女-->0
[root@docker ~]# awk 'NR>1{ stuff[$3]+=$5 } END{ for(i in stuff) {print i"-->"stuff[i] }}' stu.txt
男-->32000
女-->33000
# 统计每个部门,每个性别 各发出去多少工资 -- 按什么分组,就以什么作为key
[root@docker ~]# awk 'NR>1{ stuff[$3"--"$4]+=$5 } END{ for(i in stuff) {print i"-->"stuff[i] }}' stu.txt
女--SA-->17000
女--PE-->16000
男--SA-->17000
男--PE-->15000
练习
# 统计本机各个连接状态的数量
netstat -anplt|awk 'NR>2{ state[$6]++} END{ for(i in state) {print i"-->"state[i] }}'
LISTEN-->27
ESTABLISHED-->70
# 降序统计客户端 IP 的访问次数
cat access.log | awk '{print $1}'|sort|uniq -c|sort -rn
# 降序统计 Referer 中含 IP 地址的访问次数
cat access.log | awk '{print $11}'|sort|uniq -c|egrep "((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"|sort -rn
# 统计nginx访问日志的每分钟,每个路径访问次数
awk -F"[ :]" '{count[$4$5"--"$10]++}END{for(i in count){print i":"count[i]}}' access.log
常用内置函数
算术相关: int(x)
rand()
0<=n<1sqrt()
开平方
字符串相关:
length(string)
长度split(string, A,[指定的分隔符])
切割字符串substr(string, M, [N])
对string进行截取,从M的位置开始,截取N个
统计access.log日志中,每个小时,每个路径访问次数
awk '{count[substr($4,2,14)"--"$7]++}END{for(i in count){print i":"count[i]}}' scwebsite2_ssl.log