文本处理三剑客
# Linux符号
Linux中存在一些特殊符号用于实现特定的功能。
# 通配符号
通配符号*
用于根据文件名称进行匹配。
例如:
# 查看名称以.txt结尾的文件信息。
ls -l *.txt
2
# 引号符号
# '':将单引号内的信息全部转义为普通字符。
echo '$UID'
> $UID
# "":将双引号内的信息,特殊信息进行解析。
echo "Root UID: $UID"
> Root UID: 0
# ``或$():将引号中的命令先执行,将执行结果交给引号外面的命令。
echo "I am `echo 123`"
> I am 123
# $()还可以用来整数运算
echo $((1+1))
# 没有引号:和双引号功能类似,不同在于可以直接通配符信息、且有空格可能会被当成两部分。
echo 123
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 转义符号
\
是转义符号。
用于将有特殊意义符号转义为普通信息进行识别。
# \$会将$转义为普通字符"$"
echo "I am \$"
> I am $
2
3
或则将没有意义的信息转义为有特殊意义的信息。
\t 制表。
\n 换行。
\ 也可转义空格。
# 逻辑符号
逻辑符号可以用在命令执行中,用于实现更复杂的命令逻辑。
# &&:与/并且,前一个命令执行成功后面的命令才会执行。
# 例如:只有test.txt存在时,才会写入123到test.txt
touch ./test.txt && echo "123" > ./test.txt
# ||:或/或者,前一个命令执行失败,后面的命令才会执行。
# 例如:只有test.txt不存在时,才会创建test.txt
ls ./test.txt || touch ./test.txt
2
3
4
5
6
7
# 正则符号
正则符号用于根据符号进行内容匹配。
基础正则符号:
^
尖号符号,匹配开头。
$
美元符号,匹配结尾。
.
点字符,代表任意一个字符。
*
星字符,匹配前一个字符至少出现零次或多次连续的信息。
[]
中括号,匹配括号中所有字符的任意一个字符信息。
[x-y]
匹配范围内的任何字符。[^]
不匹配[]内的任何字符。
扩展正则符号:
+
加号,匹配前一个字符至少出现一次或多次连续的信息。
|
或符号,用于匹配多个信息。
?
问号,匹配前一个字符出现零次或一次的信息。
()
小括号,非捕获分组,而是使信息作为整体进行匹配。需要以此格式:
(?:xxx|xxx|xxx...)
{}
花括号,可以指定前一个字符连续匹配的次数。
x{n,m}
指定前一个字符连续匹配的最小次数和最大次数。
x{n,}
指定前一个字符连续匹配的最小次数。
x{,m}
指定前一个字符连续匹配的最大次数。
x{n}
指定前一个字符正好连续匹配的次数。
其他正则符号:
~
匹配包含,判断某内容是否包含某内容,包含则匹配。
- 如
"test1231" ~ /te/
是匹配成功的。
!~
匹配不包含。
注意:
默认grep、sed命令不能直接识别扩展正则。
grep使用扩展正则需要加上-E参数,或直接使用egrep命令。
sed使用扩展正则需要加上-r参数。
# grep命令
grep命令主要用于筛选文件内容,过滤出想要找出的信息,常用于查找日志等场景。
# 命令语法
grep 参数 "正则表达式" 文件
-c
统计匹配的信息个数。
-v
显示不匹配的内容,即取反和排除。
-E
使支持扩展正则,或直接使用egrep命令。
-o
显示匹配过程。
-B n
显示匹配内容行和上面n行。
-A n
显示匹配内容行和下面n行。
-C n 或 -n
显示匹配内容行和上下n行。
-i
忽略大小写筛选。
# 例子
#筛选以test开头的行。
grep "^test" ./test
# 筛选以test结尾的行。
grep "test$" ./test
# 排除空行,-v表示排除。
grep -v "^$" ./test
# 以指定信息开头和结尾,此处为匹配a开头和b结尾的行
grep "^a.*b$" ./test
# 指定具体信息进行贪婪匹配,找到P开头后匹配到最后一个me停下。
grep "^P.*me" ./test
# 指定具体信息进行非贪婪匹配,找到P开头后匹配到第一个me停下。
grep "^P.*me" ./test
# 匹配不同字符开头的信息,匹配P或h开头的信息。
grep "^[Ph]" ./test
# 匹配所有以英文字符开头的信息。
grep "^[a-Z]" ./test
# 排除有指定字符的信息,排除包含a/b/c的信息。
grep "[^abc]" ./test
# 排除包含指定字符开头的信息,排除a/b/c开头的信息。
grep "^[^abc]" ./test
# 匹配所有数字字符,[0-9]+表示匹配一个或连续的数字,也就是匹配所有数字。
grep "[0-9]+" ./test
# 匹配多个信息,同时匹配one和two。
grep -r "one|two" ./test
# 筛选IP
ip a s eth0 | grep "inet " | egrep "([0-9]{1,3}\.?){4}" -o
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
# sed命令
# 命令介绍
sed命令是一个字符流编辑工具(行编辑工具),擅长对行进行修改,它会按照每行中的字符进行处理操作,对文件内容具有增删改查的能力。
# 执行过程
- sed命令先将一行内容读取到内存中(模式空间)。
- 因为默认是在内存中处理,所以如果要实际修改文件需要加
-i
参数。 - 然后判断这一行是否满足条件,如果满足条件则处理后再读取下一行,不满足则直接读取下一行。
- sed默认会输出读取的内容到屏幕,可用-n参数取消默认输出。
# 命令语法
sed 参数 '条件/处理指令' 文件
参数:
-n
取消默认输出,只输出处理的内容。
-r
识别扩展正则。
-i
真实写入文件(将内存中的信息覆盖到磁盘中)。
-i.bak
修改时自动备份,-i后面应当只有备份后缀,参数应当放前面。
-e
识别sed命令的多个操作指令。
指令:
p
:输出内容(print)。
!
取反。
!p
:输出取反的匹配内容
s
:替换内容(substitute),例如:s#[欲替换内容]#[目标内容]#g
其中g代表全局替换。
i
:插入内容(insert),在指定内容前面插入新内容。
a
:追加内容(append),在指定内容后面插入新内容。
注意事项:
修改文件内容时,不要同时使用
n
和i
参数,会将文件内容全部变成输出的内容。
# 使用例子
添加内容(增)
# 在指定行上面添加内容
sed -i '1i test' ./test
# 在指定行下面添加内容
sed -i '1a test' ./test
# 在最后一行添加内容
sed -i '$a test1\ntest2' ./test
# 在有匹配内容所在行的上下添加内容
sed -e '/test/i one' -e '/test/a two' ./test
2
3
4
5
6
7
8
9
10
11
删除内容(删)
# 删除指定行
sed -i '3d' test
# 删除连续多行
sed -i '1,3d' ./test
# 删除有指定内容的行
sed -i '/test/d' ./test
# 指定行号删除多行
sed -i '1d;3d' ./test
# 指定匹配内容删除多行
sed -i '/test1/d ; /test2/d' ./test
# 删除空行
sed -i '/^$/d' ./test
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
修改内容(改)
# 如果内容有#可以使用/,或者使用反斜杠将内容中的#转义。
sed -i 's#原有内容#目标内容#g' ./test
sed -i 's/原有内容/目标内容/g' ./test
# 将指定行的整行内容进行替换
sed -i '2c lalalalala' ./test
2
3
4
5
6
查询内容(查)
# 输出指定行内容
sed -n '3p' ./test
# 输出指定连续多行内容
sed -n '1,3p' ./test
# 输出匹配内容的行
sed -n '/test/p' ./test
# 多个匹配条件进行输出
sed -n '/test1/p ; /test2/p' ./test
# 输出两个匹配内容及其之间的所有行。
sed -n '/test1/,/test2/p' ./test
2
3
4
5
6
7
8
9
10
11
12
13
14
实际案例
# 取出IP地址。
ip a show eth0 | sed -n '3p' | sed -r 's#.*net (.*) brd.*#\1#g'
# 或
ip a show eth0 | sed -rn '3s#.*net (.*) brd.*#\1#gp'
# 实际修改时进行备份。
sed -i.bak '3p' ./test
# 批量创建用户,()将前项内容交给后项处理。
# 1.先将echo输出的信息用xargs转化成多行。
# 2.然后用sed将每行内容替换成useradd + 每行内容,其中()代表整行内容,而\1代表引用第一个括号内的内容。
# 3.最后将替换好的内容交由bash解释器执行。
echo user{01..05} | xargs -n1 | sed -r "s#(.*)#useradd \1#g" | bash
# 或
seq -w 10 |sed -r "s#(.*)#useradd user\1#g" | bash
# 批量创建并改密码
echo test{06..10} | xargs -n1 | sed -r "s#(.*)#useradd \1 ; echo "123456" | passwd --stdin \1#g" | bash
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# awk命令
# 命令介绍
awk擅长对文件进行分析,模式扫描和处理文件语言,awk和grep没有和sed一样的直接对文件进行处理的方式,只是将处理结果输出到屏幕。
# 执行过程
- awk命令执行时,会先执行BEGIN{}内的动作。
- 然后将文件内容一行一行的读取覆盖到$0以换行符结束,并将内容以分隔符切割给$n。
- 如果有条件,则对满足条件的行进行处理执行动作,不满足直接读取下一行。
- 如果只有动作,没有匹配条件,则直接对该行执行动作,然后继续读取下一行。
- 全部处理完后执行END{}内的事情。
# 内置变量
NR
表示行号。
NF
表示每一行有多少列。
FS
字段分隔符变量,可以直接通过变量修改,也可以通过-F参数指定。
# 命令语法
awk [参数] '模式-动作' 文件
awk中调用变量不用加$, 如果普通字符被当成变量可以加上""将其包裹。
参数:
-v变量名="变量值"
设置变量。另外如果直接在动作中使用未定义的变量,默认会从0开始。
-F "[\t: ]+"
设置制表符为字段分隔符。
- 同等于
BEGIN{FS="[\t: ]+"}
或-vFS="[\t: ]+"
。
普通模式:
正则表达式作为模式:
'/内容/'
查找指定内容。
'$3~/内容/'
查找指定列含内容的信息。
$0!~
取反。比较匹配作为模式:
'NR==n'
匹配指定行。
'NR<n'
匹配行号小于n的行。
'NR>n'
匹配行号大于n的行。
'NR<=n'
匹配行号小于等于n的行。
'NR==n, NR==m'
匹配n和m行。逻辑操作符进行匹配:
&&
并且,例如:NR>=n && NR<=m
||
或者,例如:NR==n || NR==m
!
非。
特殊模式:
BEGIN{}
:在读取文件前执行的动作,比如加上表头、运算、指定awk变量等。
awk 'BEGIN{print "a","b"}' ./test
awk 'BEGIN{print 1+1}' ./test
awk 'BEGIN{FS="[: ]+"}' ./test
2
3
4
5
END{}
:在文件处理后执行的动作,比如提示、统计、计算等。
awk 'END{print "操作结束"}' ./test
awk '/^$/{i++}END{print i}' ./test
2
3
动作:
{}
:表示使用动作,多个动作之间用;
分隔。
,
表示空格,"," 表示使用,字符。
NF
表示倒数第一列。
$(NF-1)
表示倒数第二列。
$列数
表示第n列。
$0
表示整行所有列。
{i++}
算数运算。
gsub(/欲替换信息/,"目标信息",欲修改列)
全局替换。
if判断:
{ if (条件) {命令} }
单分支。
{ if (条件) {成立命令1} else{不成立命令2} }
双分支。
{ if (条件1) {成立命令1} else if (条件2) {命令2}... }
多分支。
for循环:
{ for (变量 in 迭代对象) {命令} }
{ for (初始变量;条件;自增减) {命令} }
C语言风格。
while循环:
{ while (条件) {循环体} }
数组定义:
数组名[索引]=值
,索引可以是数值,也可以是字符串。例如:test[1]=233
。如果值是字符串必须加双引号,例如:
test[1]="hhh"
。支持自增自减赋值,默认从0开始,例如:
test[1]++
。
数组调用:
数组名[索引]
,例如:print test[1]
。作为迭代对象时不用加索引,例如:
{for ( i in test ) print i }
。
# 使用例子
# 按照行号查询指定行。
awk 'NR==2' ./test
# 按照行号查询指定连续行。
awk 'NR==2,NR==5' ./test
# 按照行号查询指定多行。
awk 'NR==2;NR=6' ./test
# 按照指定内容查询指定行。
awk '/test/' ./test
# 按照指定内容查询指定多行。
awk '/test1/;/test2/' ./test
# 输出匹配行的第一列和第二列。
awk 'NR==2{print $1 "," $3}' ./test
# 输出匹配行的倒数第二列。
awk -F "[: ]+" '/test/{print $(NF-1)}' ./test
# 查找第三列以41开头的行。
awk '$3~/^41/' ./test
# 查找第三列以1或5结尾的行。
awk '$3~/1$|5$/' ./test
# 将第一行的a替换为1。
awk 'NR==1{gsub(/a/,"1",$1);print}' ./test.txt
# 求和运算。
seq 10 | awk '{sum=sum+$n}; END{print sum}'
# 对文本空行进行计数
awk '/^$/{i++}; END{print i}' ./test
# 单分支判断。
awk -F: '{if ($3==0) {print $1} }' /etc/passwd
# 双分支判断。
awk -F: '{if ($3>0 && $3<1000) {print "虚拟用户"$1} else {print "可登录用户"$1} }' /etc/passwd
# 多分支判断。
awk -F: '{if ($3>0 && $3<1000) {print "虚拟用户"$1} else if($3==0) {print "超级用户"$1} else{print "普通用户"$1} }' /etc/passwd
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
# 实际案例
# 统计Nginx页面的PV量,即一个页面的浏览量,用户每刷新一次就有一个PV。
## 统计当天站点总PV量。
awk '$4~/04\/Apr\/2021/{i++} END{print "2021年4月4日PV量为"i}' access.log
## 统计当天站点12:00-16:00的PV量。
awk -F: '$1~/04\/Apr\/2021/ {if ($2 >=12 && $2 < 16) {i++} } END{print "2021年4月4日12-16点的总PV量为:"i}' access.log
awk '$4>="[04/Apr/2021:12:00:00" && $4<"[04/Apr/2021:16:00:00" {print $0}' access.log |wc -l
## 统计4月4日访问次数最多的前10个页面。
awk '$4~/04\/Apr\/2021/{pages[$7]++} END{for (i in pages) print pages[i]"\t"i}' access.log |sort -nr | head -10
# 统计Nginx日志中IP访问次数。
## 统计4月4日中访问次数最多的前十个IP。
awk '$4~/04\/Apr\/2021/{ips[$1]++} END{for (i in ips){print ips[i]"\t"i}}' access.log | sort -nr | head -10
## 统计4月4日12-16点访问次数最多的前十个IP
awk -F '[ :]+' ' $4~/04\/Apr\/2021/ && $5>=12 && $5<16 {ips[$1]++} END{for (i in ips){print ips[i]"\t"i}}' access.log | sort -nr | head -10
## 统计4月4日访问次数最多的时间段。
awk -F '[: ]+' '{print $5}' access.log | sort | uniq -c | sort -nr
## 统计4月4日访问次数大于100的IP。
awk '$4~/04\/Apr\/2021/{ips[$1]++} END{for (i in ips) { if(ips[i]>100){print ips[i]"\t"i} } }' access.log
# 统计Nginx日志状态码。
## 统计4月4日状态码为404的次数。
awk '$4~/04\/Apr\/2021/ && $9~/404/ {i++} END{print i}' access.log
## 统计4月4日12点-16点 状态码为404的次数。
awk -F '[ :]+' '$4~/04\/Apr\/2021/ && $5 >=12 && $5 < 16 && $12~/404/ {i++} END{print i}' access.log
awk '$4 >="[04/Apr/2021:12:00:00" && $4 < "[04/Apr/2021:16:00:00" && $9~/404/ {i++} END{print i}' access.log
awk '$9~/404/' access.log | egrep '04/Apr/2021:1[2-5]' | wc -l
## 统计4月4日每个IP所有状态码的次数
awk '{states[$1 "\t" $9]++} END{for (i in states) print states[i]"\t"i }' access.log |sort -k2
# 统计日志中每个URL的总大小
awk '{size[$7]+=$10} END{for (i in size)print int(size[i]/1024/1024)"MB\t"i }' access.log |sort -nr | head -10
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