前言
玩转Linux操作系统必须会shell,会shell必须知道正则表达式及grep、sed、awk文本处理三剑客。好多年前有个大佬告诉我,不知道正则不要说自己会Linux,为此我专门学习这部分内容,这篇文章是对我学习内容的整理,也是我经常查阅的一篇笔记,也希望他可以帮助到大家。共同进步,瑞思拜~
一、正则表达式
基本正则表达式元字符:
元字符 功能 示例
^ 行首定位符 ^love grep '^root' /etc/passwd
$ 行尾定位符 love$ grep 'root$' /etc/passwd
. 匹配单个字符 l..e grep '^r..t' /etc/passwd
- 匹配0个或多个前导字符 ab*love grep 'ro*t' /etc/passwd
.* 任意多个字符 grep 'r.*ot' /etc/passwd
[] 匹配指定范围内的一个字符 [lL]ove grep '[rx]oot' /etc/passwd
[ - ] 匹配指定范围内的一个字符 [a-z0-9]ove grep '[a-z0-9]oot' /etc/passwd
[^] 匹配不在指定组内的字符 [^a-z0-9]ove grep '[^a-z0-9]oot' /etc/passwd
^[^] 括号外尖号是指匹配,匹配括号内的任意一个字符 括号内尖号是指取反,匹配不在括号内的任意字符
\ 用来转义元字符 love\. 匹配love.
\< 词首定位符 \<love 定位单词首
\> 词尾定位符 love\> 定位单词尾
\(..\) 匹配稍后使用的字符的标签 :% s/172.16.130.1/172.16.130.5/
:% s/\(172.16.130.\)1/\15/
:% s/\(172.\)\(16.\)\(130.\)1/\1\2\35/
:3,9 s/\(.*\)/
x\{m\} 字符x重复出现m次 lo\{5\}ve
x\{m,\} 字符x重复出现m次以上 lo\{5,\}ve
x\{m,n\} 字符x重复出现m到n次 lo\{5,10\}ve
扩展正则表达式元字符(需要egrep或者转义)
元字符 功能 示例
+ 匹配一个或多个前导字符 [a-z]+ove
? 匹配零个或一个前导字符 lo?ve
a|b 匹配a或b love|hate
() 组字符 loveable|rs love(able|rs) ov+ (ov)+
(..)(..)\1\2 标签匹配字符 (love)able\1er
x{m} 字符x重复m次 lo{5}ve
x{m,} 字符x重复至少m次 lo{5,}ve
x{m,n} 字符x重复m到n次 lo{5,10}ve
二、文本处理三剑客
grep
grep使用的元字符:
grep: 使用基本元字符集 ^, $, ., *, [], [^], \< \>,\(\),\{\}, \+, \|
egrep(grep -E): 使用扩展元字符集 ?, +, { }, |, ( )
grep也可以使用扩展集中的元字符,仅需要对这些元字符前置一个反斜线(转义)
\w 所有字母与数字,称为字符[a-zA-Z0-9] 'l[a-zA-Z0-9]*ve' 'l\w*ve'
\W 所有字母与数字之外的字符,称为非字符 'love[^a-zA-Z0-9]+' 'love\W+'
\b 词边界 '\<love\>' '\blove\b'
grep不支持\d、\D、\s、\S,\d匹配数字,\D匹配非数字,\s匹配空白,\S匹配非空白
grep常用选项:
-i --ignore-case
-v --invert-match
-n --line-number
-e --regexp=PATTERN
grep -e "a" -e "b" file
-f --file=FILE
grep -f a b
-c --count
grep -c "1" a
-o --only-matching
ifconfig |grep -E -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
sed
sed使用的元字符
使用基本元字符集 ^, $, ., *, [], [^], \< \>,\(\),\{\}
使用扩展元字符集 ?, +, { }, |, ( )
sed选项
sed -r 打印结果,没有真正执行
sed -i 真正执行
sed -n 静默输出(只打印处理过的行)
sed常用用法
sed -n '10,20p' 只显示匹配到的行
sed -r 's/root/alice/gi' /etc/passwd 忽略大小写且全局替换root>alice
sed -r 's#root#alice#gi' /etc/passwd 替换 不需要定义分隔符 直接使用
sed -r '\#root#d' /etc/passwd 查找 要定义分隔符
sed -r '/^[ \t]*#/d' passwd 删除``开头的行
sed -r '/^[ \t]*$/d' passwd 删除空行
sed -r '/^[ \t]*#/d; /^[ \t]*$/d' passwd 删除注释行和空行 ;号隔开
sed -r '3,$ s/^#*/#/' passwd 将行首零个或多个
sed -r '30,50 s/^[ \t]*#*/#/' passwd 将行首带有空格的零个
sed ':label;N;s/\n/ /;b label' 回车符转行为空格
sed定址
sed -r 'd' passwd 全部删除
sed -r '3d' /etc/passwd 删除第3行
sed -r '1,3d' /etc/passwd 删除1-3行
sed -r '/root/d' /etc/passwd 删除带root的行
sed -r 's/root/alice/g' /etc/passwd 全局替换root为alice
sed -r '/^adm/,20d' /etc/passwd 从adm开头的行删除到第20行
sed -r '/^adm/,+20d' /etc/passwd 删除adm开头的行再删除其行后20行
sed常用参数
d 删除行
s 用一个字符串替换另一个
s 替换标志
g 全局替换
i 忽略大小写
sed -r 's/(.*)/#\1/' passwd
sed -r 's/(.*)/#&/' passwd &代表在查找串中匹配到的内容
sed -r 's/^/#/' passwd 全部注释
sed -r 's/(.)(.)(.*)/\1oooo\2\3/' passwd 在第二个字母前添加内容 oooo
sed -r 's/(192)(.)/\1\2xx\2/' a.file
192.xx.168.1.1 10.19.200.200
sed -n "s/192/xx/p" a 替换并打印
xx.168.1.1 10.19.200.200
r 读取文件
sed -r '2r 1.txt' a.txt 把a.txt的前2行放到1.txt最前面
w 写入文件
sed -r '3,$w 1.txt' a.txt 把a.txt的3行到最后一行保存到1.txt
aic 插入
sed -r '2a 1111111111111' 1.txt 在第二行后面追加111111111111
sed -r '2i 1111111111111' 1.txt 在第二行前面插入一行1111111111111
sed -r '2c 1111111111111' 1.txt 第二行替换成1111111111
sed -r '/^192/a abc123' a 在以192开头的行后面追加abc123
192.168.1.1 10.19.200.200
abc123
192.168.1.1 10.19.200.200
abc123
sed空间操作
n 读取下一行到模式空间,如果没有下一行则执行 q 退出
N 追加下一行内容到模式空间,并以换行符\n分割
q 退出
p 打印模式空间所有内容
P 打印模式空间第一行
seq 6 |sed -n 'n;p' 打印偶数行
seq 6 |sed 'N;q' 打印1\n2
d 删除模式空间所有内容
D 删除模式空间第一行,以`\n`分割,放弃之后的命令,但是对剩余模式空间重新执行sed
seq 6 |sed -n 'N;P' 打印奇数行
seq 6 |sed 'N;D' 打印最后一行
读取1,执行N,模式空间为1\n2,执行D,删除1剩余2,执行N,模式空间为2\n3,执行D,删除2剩余3,依此类推,得出5,执行N,条件失败退出。
h/H 模式空间**覆盖/追加**到暂存空间
g/G 暂存空间**覆盖/追加**到模式空间
sed -r '1h;$G' passwd 把第1行复制到最后一行
sed -r '1{h;d};$G' passwd 把第1行剪切到最后一行
sed -r '1h; 2,$g' passwd 把其它行全部替换成第1行
sed -r '1h; 2,3H; $G' passwd 把前3行复制到最后三行
sed -r '1!G;h;$!d' 1 倒序!!!!!
读取第一行 1 时,跳过 G 命令,执行 h 命令将模式空间 1 复制到保持空间,执行 d 命令删除模式空
间的 1。
读取第二行 2 时,模式空间是 2,执行 G 命令,将保持空间 1 追加到模式空间,此时模式空间是2\n1,执行 h 命令将 2\n1 覆盖到保持空间,d 删除模式空间。
以此类推,读到第 5 行时,模式空间是 5,执行 G 命令,将保持空间的 4\n3\n2\n1 追加模式空间,
然后复制到模式空间,5\n4\n3\n2\n1,不执行 d,模式空间保留,输出。
x 暂存空间和模式空间替换
sed -r '1h;4x;$G' passwd 把第4行替换成第1行并把第4行追加到最后一行
sed ':t;N;s/\n/,/;b t' 将换行符换成逗号
sed命令区分制表符和空格
sed -n l tab_space.txt
this is tab\tfinish.$
this is several space finish.$
awk
awk内部变量:
$0: 保存当前记录的内容 awk -F: '{print $0}' passwd
NF: 显示每一行的字段数 awk -F: '{print NR,$0,NF}' passwd
NR: 显示总共行号 awk -F: '{print NR,$0}' passwd passwd1
FNR: 分别显示每个文件行号 awk -F: '{print FNR,$0}' passwd passwd1
FS: 输入字段分隔符 默认空格 awk -F"[ :\t]" '{print $1,$2,$3}' passwd
OFS: 输出字段分隔符 awk 'BEGIN{FS=":";OFS="--"} {print $1,$2,$3}' passwd
RS: 输入记录分隔符 默认换行 awk 'BEGIN{RS="/"} {print $0}' passwd
ORS: 输出记录分隔符 awk -F: 'BEGIN{ORS=" "} {print $0}' passwd
格式化输出:
awk -F: '{print "username is: " $1 "\t uid is: " $3}' passwd
tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="} {print $0}'
3gpp-cbsp 48049/tcp
isnetserv 48128/tcp
awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' passwd
%s 字符类型
%d 数值类型
%f 浮点类型 %.2f 保留两位小数点
占15字符
- 表示左对齐,默认是右对齐
printf默认不会在行尾自动换行,加\n
awk模式:
-
正则表达式 (1) 匹配一整行 awk '/^root/' passwd
awk '!/^root/' passwd
(2) 匹配字段 awk -F":" '$1 ~/root/' passwd
awk -F":" '$1 !~/bin/' passwd
-
比较表达式 awk -F":" '$3==7' passwd
awk -F":" '$NF=="/sbin/nologin"' passwd
awk -F":" '$NF ~"/nologin"' passwd
-
条件表达式 awk -F":" '{if($3>5){print $1,$3} else{print $1}}' passwd
-
算术运算:+ - * / %(模) (幂23) awk -F":" '{if($3*1>5){print $1,$3}}' passwd
-
逻辑操作符 && 逻辑与 a&&b
|| 逻辑或 a||b
! 逻辑非 !a
awk -F":" '$1 ~/^ro/ && $1 ~/ooot$/' passwd
awk -F":" '$1 ~/^ro/ || $1 ~/^bin/' passwd
awk -F":" '!($1 ~/^ro/ || $1 ~/^bin/)' passwd
awk -F":" '!/^#|^$/' passwd
-
布尔值判断 seq 6 |awk 'i=!i'
seq 6 |awk '!(i=!i)'
i值未被定义所以为假,!i就为真,所以i为真,打印1
第二次,i已经为真,!i为假,所以i又为假,不打印2 ,依次循环
-
三目运算 awk 'BEGIN{print 1==1?"yes":"no"}'
yes
seq 4 |awk '{printf NR%2!=0?$0" ":$0" \n"}'
1 2
3 4
-
控制输出 awk '/^root/{next}{print $0}' file
seq 5 |awk 'NR!=1{print $0}'
tail -n5 /etc/services |awk '{print $2 > "a.txt"}'
tail -n5 /etc/services |awk '{print $2 |"grep tcp"}'
awk 'NR%2{printf $0",";next}{print $0}'
awk脚本:
-
条件判断 awk -F: '{if($3>0 && $3<1000){i++}} END{print i}' passwd
awk -F: '{if($3==0){i++} else{j++}} END{print "管理员个数: "i; print "系统用户数: "j}' passwd
awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员: "i; print "普通用户: "k; print "系统用户: "j}' passwd
-
循环 awk -F: '{i=1;while(i<=5) {print $1;i++}}' passwd
awk -F":" '{i=1; while(i<=NF){print $i; i++}}' passwd
awk -F":" '/^root/{ for(i=1;i<=NF;i++) {print $i} }' passwd
-
数组 awk -F: '{username[i++]=$1} END{print username[0]}' passwd
按索引遍历: awk -F: '{username[$1]++} END{for(i in username) {print i,username[i]} }' passwd
awk -F":" '{shells[$NF]++} END{for(i in shells){print i,shells[i]}}' passwd
ss -antp |grep :22 |awk -F":" '!/LISTEN/{ip[$2]++} END {for (i in ip) {print i,ip[i]}}' |sort -rn -k3 |head |awk '{print $2,$3}'
awk '/22\/Mar\/2018/{ips[$1]++} END{for(i in ips){print i,ips[i]}}' access.log |awk '$2>100' |sort -k2 -rn|head
awk '/22\/Mar\/2018/{ips[$1]++} END{for(i in ips){if(ips[i]>100){print i,ips[i]}}}' access.log |sort -k2 -rn|head
cat /var/log/access_liang.log |awk '$7~/liang.php/ {print $1}' |sort |uniq -c
awk '{sum += $1} END {print sum}'
awk '{line[NR]=$0}END{for(i=NR;i>=1;i--){print line[i]}}' 1
awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' 1
统计访问 IP 次数: awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log
统计访问访问大于 100 次的IP: awk '{a[$1]++}END{for(v in a){if(a[v]>100)print v,a[v]}}' access.log
统计访问 IP 次数并排序取前10: awk '{a[$1]++}END{for(v in a)print v,a[v] |"sort -k2 -nr |head -10"}' access.log
统计时间段访问最多的IP: awk '$4>="[02/Jan/2018:00:00:00" && $4<="[02/Jan/2018:00:03:00"{a[$1]++}END{for(v in a)print v,a[v]}' access.log
统计上一分钟访问量: date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M)
awk -vdate=$date '$4~date{c++}END{print c}' access.log
统计访问最多的 10 个页面: awk '{a[$7]++}END{for(v in a)print v,a[v] |"sort -k1 -nr|head -n10"}' access.log
统计每个 URL 数量和返回内容总大小: awk '{a[$7]++;size[$7]+=$10}END{for(v in a)print a[v],v,size[v]}' access.log
统计每个 IP 访问状态码数量: awk '{a[$1" "$9]++}END{for(v in a)print v,a[v]}' access.log
统计访问 IP 是 404 状态次数: awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log
找出 b 文件在 a 文件相同记录: awk 'NR==FNR{a[$0]=1}NR!=FNR{if($0 in a) print $0}' a b
找出 b 文件在 a 文件不同记录: awk 'FNR==NR{a[$0]=1;next}!a[$0]' a b
-
引用外部变量 使用-v参数可以将外部值传给awk中使用的变量
awk -v user=root -F: '$1 == user' passwd
-
案例 例1 cat a.file
姓名 费用 数量
zhangsan 8000 1
zhangsan 5000 1
lisi 1000 1
lisi 2000 1
wangwu 1500 1
zhaoliu 6000 1
zhaoliu 2000 1
zhaoliu 3000 1
awk 'NR>1{name[$1]++;number[$1]+=$3;money[$1]+=$2}END{for(i in name)print i,number[i],money[i]}' a.file
zhaoliu 3 11000
zhangsan 2 13000
wangwu 1 1500
lisi 2 3000
例2 求每个人的平均成绩
cat a.txt
one 23 23 45 22 22
two 23 23 45 22 22 28
three 23 23 45 22 22 82 23
four 23 23 45 22 22 23 45 32 23
cat a.awk
BEGIN{
print "姓名","平均成绩"
}
{
for(i=2;i<=NF;i++){
sum=sum+$i
}
avg=sum/(NF-1)
print $1,avg
sum=0
}
awk -f a.awk a.txt
姓名 平均成绩
one 27
two 27.1667
three 34.2857
four 28.6667
例3 按列统计,列转行
cat a.file
姓名:liang
性别:male
电话:18623432212
姓名:mobai
性别:male
电话:18223432122
姓名:right
性别:female
电话:18123432212
cat a.file |awk -F":" '{print $2}' |sed "/^$/d" |awk 'BEGIN{print "姓名\t性别\t电话"} {printf NR%3!=0?$0"\t":$0"\n"}'
姓名 性别 电话
liang male 18623432212
mobai male 18223432122
right female 18123432212
例4 获取每行的最大值
cat c.txt
20 30 40
55 44 33
23 76 54
13 19 9
cat c.awk
{
max=$1
for (i=2;i<=NF;i++)
{
if ($i>max)
{
max=$i
}
}
print max
}
awk -f c.awk c.txt
40
55
76
19
按行从小到大排序
cat c1.awk
{
num=0
if($1>$2)
{
num=$1
$1=$2
$2=num
}
if($1>$3)
{
num=$1
$1=$3
$3=num
}
if($2>$3)
{
num=$2
$2=$3
$3=num
}
print $1,$2,$3
}
awk -f c1.awk c.txt
20 30 40
33 44 55
23 54 76
9 13 19
|