Linux-文本三剑客(grep、sed、awk)

发布于:2025-09-15 ⋅ 阅读:(15) ⋅ 点赞:(0)


前言

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 separator
  • OFS 当前的输出分隔符,默认是空格字符(空格)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<1
  • sqrt() 开平方

字符串相关:

  • 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