shell脚本学习——正则表达式、sed、awk

一、正则表达式
正则表达式用于查找匹配指定的字符
支持正则表达式的程序:locate、find、vim、grep、sed、awk
元字符:具有特殊意义的专用字符,如. * ?
前导字符:位于元字符前面的字符,如abc*
1、第一类正则表达式

普通常用的元字符
.      匹配除换行符以外的任意单个字符
*      前导字符出现0次或连续多次
.*     任意长度字符
^      行首
$      行尾
^$     空行
[]     匹配括号里任意单个字符或一组单个字符
[^]    匹配不包含括号里任意单个字符或一组单个字符
^[]    匹配以括号里任意单个字符或一组单个字符开头
^[^]   匹配不以括号里任意单个字符或一组单个字符开头

其他常用元字符
\<          匹配单词的开头
\>          匹配单词的结尾
\< \>       精确匹配单词
\{n\}       匹配前导字符连续出现n次
\{n,\}      匹配前导字符至少出现n次
\{n,m\}     匹配前导字符出现n~m次
\( \)       保存被匹配的字符
\d          匹配数字(grep -P)        [0-9]
\w          匹配字母数字下划线(grep -P) [a-zA-Z0-9_]
\s          匹配空格、制表符、换页符(grep -P) [\t\r\n]

扩展类正则常用元字符
grep -E  或  egrep
sed -r
+      匹配一个或多个前导字符
?      匹配0个或一个前导字符
|()     组字符(看成整体)
{n}    前导字符重复n次
{n,}   前导字符重复至少n次
{n,m}  前导字符重复n~m次

2、第二类正则表达式

[:alnum:]      字母、数字字符    [[:alnum:]]+
[:alpha:]      字母字符(大小写)  [[:alpha:]]{4}
[:blank:]      空格与制表符  [[:blank:]]*
[:digit:]      数字    [[:dight:]]?
[:lower:]      小写字母    [[:lower:]]{4,}
[:upper:]      大写字母   [[:upper:]]+
[:punct:]      标点符号    [[:punct:]]
[:space:]      换行符、回车等所有空白   [[:space:]]+

二、sed
1、命令行格式

sed [options] '处理动作' 文件名
-e   进行多项编辑
-n   取消默认输出
-r   使用扩展正则表达式
-i   原地编辑(修改源文件)
-f   指定sed脚本文件名

常见处理动作
'p'     打印
'i'     在指定行之前插入内容
'a'     在指定行之后插入内容
'c'     替换指定行所有内容
'd'     删除指定行

对文件进行增删改查操作
语法:sed 选项 ‘定位+命令’ 需要处理的文件
打印文件内容
sed '' a.txt
sed -n '2p' a.txt
sed -n '1,5p' a.txt
sed -n '$p' a.txt
增加文件内容
sed '2ihello world' a.txt
sed '3ihello\nworld' a.txt
sed '2,4a999' a.txt
修改文件内容
sed '$chello world' a.txt
sed '/^adm/chello world' a.txt
sed '1,5chello world' a.txt  将1~5行替换为一行hello world
删除文件内容
sed '1,4d' a.txt
sed -r '/([0-9]{1,3}\.){3}[0-9]{1,3}/d' a.txt

对文件进行搜索替换操作

语法:sed 选项 ‘s/搜索的内容/替换的内容/动作’ 需要处理的文件
s表示搜索;/表示分隔符,可自定义;动作有打印p和全局替换g
sed -n 's/root/ROOT/p' 1.txt
sed -n 's/root/ROOT/gp' 1.txt
sed -n 's/^#//gp' 1.txt  去掉#号
sed -n 's@/sbin/nologin@hello@gp' 1.txt
sed -n '10s@/sbin/nologin@hello@gp' 1.txt 替换第10行
sed -n '1,5s/^/#/gp' 1.txt
sed -n 's#\(10.1.1.\)1#\1254#gp' 1.txt

其他命令

r     从另外文件中读取内容
w     内容另存为
&     与\(\)相同
=     打印行号
!     对所选行以外的其他行应用命令,放到行数之后
q     退出
sed '3r /etc/hosts' 1.txt   在第三行读取其他文件内容
sed '1,5w 11.txt' 1.txt   将前五行另存为其他文件
sed -n 's/^sync/#&/gp' 1.txt  在sync行添加注释
sed -ne '/root/p' -ne '/root/=' 1.txt  先打印行,后面打印行号
sed -n '/root/=;/root/p' 1.txt
sed -n '1,5!p' 1.txt   不包括1~5行
sed '/shutdown/q' 1.txt  读取到shutdown行退出

其他选项

-e      多项编辑
-r      扩展正则
-i      修改原文件
grep -v '^#' vsftpd.conf |grep -v '^$' 过滤配置文件中注释行和空行
sed -e '/^#/d' -e '/^$/d' vsftpd.conf
sed -e '/^#/d;/^$/d' vsftpd.conf
sed -r '/^#|^$/d' vsftpd.conf
sed -i 's/root/ROOT/g' 1.txt

sed结合正则使用

sed 选项 ‘sed命令或者正则表达式或者地址定位’ 文件名
/key/      sed -n '/root/p' 1.txt  查询包含关键字的行
/key1/,/key2/   sed -n '/^adm/,/^mysql/p' 1.txt     匹配包含两个关键字之间的行
/key/,x      sed -n '/^ftp/,7p' 1.txt  匹配包含关键字的行到第x行
x,/key/
x,y!           不包含x到y行
/key/!    sed -n '/bash$/!p' 1.txt   不包含关键字的行

2、脚本格式

sed -f scripts.sh file
./sed.sh file

#!/bin/sed -f
1,5d
s/root/ROOT/g
3i777
5i888
a999
p

注意:脚本文件是一个sed的命令行清单;
     在每行末尾不能有空格、制表符等其他文本
     一行有多个命令用分号分隔
     不可用引号
     #号开头的行为注释

三、awk
1、语法结构:

awk 选项 '命令部分' 文件名    (引用shell变量需用双引号)
-F   定义字段分隔符,默认分隔符为空格
-v   定义变量并赋值
命令部分说明:
正则表达式,地址定位
'/root/{awk语句}'        sed中'/root/p'
'NR==1,NR==5{awk语句}'         '1,5p'
'/^root/,/^ftp/{awk语句}'      '/^root/,/^ftp/p'
{awk语句1;awk语句2;...}
'{print $0;print $1}'         'p'
'NR==5{print $0}'             '5p'
#awk语句间用分号间隔
BEGIN...END...
'BEGIN{awk语句};{处理中};END{awk语句}'
'BEGIN{awk语句};{处理中}'
'{处理中};END{awk语句}'

2、脚本模式

#!/bin/awk -f
#以下awk引号内的命令清单不要用引号,多个命令使用分号隔开
BEGIN{FS=":"}
NR==1,NR==3{print $1"\t"$NF}
...

脚本执行
方法一:
awk 选项 -f awk脚本文件   需处理的文件
awk -f awk.sh filename
awk -f awk.sh -i filename
方法二:
./awk脚本文件(或绝对路径) 需处理的文件
./awk.sh filename

3、awk内部相关变量

$0       当前处理行的所有记录
$1,$2,$3...$n   每行以间隔符号分隔的不同字段  awk -F:'{print $1,$3}'
NF       当前记录的字段数(列数)  awk -F: '{print NF}'
$NF      最后一列    $(NF-1)表示倒数第二列                 
FNR/NR    行号
FS       定义间隔符     'BEGIN{FS=":"};{print $1,$3}'
OFS     定义输出字段分隔符,默认空格  'BEGIN{OFS="\t"};{print $1,$3}'
RS      输入记录分隔符,默认换行  'BEGIN{RS="\t"};{print $0}'
ORS     输出记录分隔符,默认换行  'BEGIN{PRS="\n\n"};{print $1,$3}'
FILENAME      文件名

4、awk使用进阶

格式化输出print和printf
print函数    相当于echo
date |awk '{print "Month: "$2 "\nYear: "$NF}'
printf函数   相当于echo -n
awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' /etc/passwd
awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd

awk变量定义
awk -v NUM=3 -F: '{print NUM}' 1.txt
awk -v NUM=3 'BEGIN{print NUM}'
#awk中调用定义的变量不需要加$

awk中的BEGIN...END
BEGIN表示程序开始前执行
END表示所有文件处理完后执行
'BEGIN{开始处理前};{处理中}:END{处理结束后}'
例:打印最后一列和倒数第二列
awk -F: 'BEGIN{print "Login_shell\t\tLogin_home\n*************"};{print $NF"\t\t"$(NF-1)};END{print "*************"}' 1.txt
2、打印/etc/passwd里的用户名、家目录、登录shell
awk -F: 'BEGIN{OFS="\t\t";print "u_name\t\th_dir\t\tshell\n****************"};{printf "%-20s %-20s %-20s\n",$1,$(NF-1),$NF};END{print "******************"}' /etc/passwd

awk与正则运用
==        等于
!=        不等于
>         大于
<         小于
>=        大于等于
<=        小于等于
~         匹配
!~        不匹配
!         逻辑非
&&        逻辑与
||        逻辑或
awk -F: 'NR==1,/^lp/{print $0}' passwd
awk -F: '/^root/ || /^lp/{print $0}' passwd
awk 'NR>=30 && NR<=39 && $0 ~ /bash$/{print $0}' passwd
打印ip地址
ifconfig eth0 |grep Bcast|awk -F: '{print $2}' |awk '{print $1}'
ifconfig eth0 |grep Bcast|awk -F'[: ]+' '{print $4}'
ifconfig eth0 |awk -F"[: ]+" '/inet addr/{print $4}'

5、awk脚本编程
流程控制语句

if结构
格式:awk 选项 '正则,地址定位{awk语句}' 文件名
  {if(表达式) (语句1;语句2;...)}
awk -F: '{if($3==0) {print $1"是管理员"}}' passwd
awk 'BEGIN{if($(id -u)==0) {print "admin"}}'

if...else结构
格式:{if(表达式) {语句;语句;...} else {语句;语句;...}}
awk -F: '{if($3>=500 && $3 !=65534) {print $1"是普通用户"} else {print $1"不是普通用户"}}' passwd
awk 'BEGIN{if($(id -u)>=500 && $(id -u) !=65534) {print "是普通用户"} else {print "不是普通用户"}}'

if...else if...else结构
格式:{if(表达式1) {语句;语句;...} else if(表达式2) {语句;语句;...} else if(表达式3) {语句;语句;...} else {语句;语句;...}}
awk -F: '{if($3==0) {print $1"is admin"} else if($3>=500 && $3<=60000) {print $1"is normal user"} else {print $1"is system user"}}' passwd
awk -F: '{if($3==0) {i++} else if($3>=1 && $3<=499 || $3==65534) {j++} else {k++}};END{print "管理员个数为:"i"\n系统用户个数为:"j"\n普通用户个数为:"k}' passwd

循环语句

for循环
awk 'BEGIN{for(i=1;i<=5;i++) {print i}}'
awk 'BEGIN{sum=0;for(i=1;i<=10;i+=2) sum=sum+i;{print sum}}'
for ((i=1;i<=10;i+=2));do echo $i;done|awk '{sum+=$0};END{print sum}'

while循环
awk 'BEGIN{i=1;while(i<=5) {print i;i++}}'
awk 'BEGIN{sum=0;i=1;while(i<=10) {sum=sum+i;i+=2};{print sum}}'

嵌套循环
awk 'BEGIN{for(y=1;y<=5;y++) {for(x=1;x<=y;x++) {printf x};print}}'
1
12
123
1234
12345
99乘法口诀
awk 'BEGIN{for(y=1;y<=9;y++) {for(x=1;x<=y;x++) {k=y*x;printf (x"*"y"="k"\t")};print ("\n")}}'

awk算数运算

+ - * / %() ^(幂2^3)
awk 'BEGIN{print 1+1}'
awk 'BEGIN{print 2**3}'
awk 'BEGIN{print 2/3}'

6、awk练习——统计

统计系统中的shell
awk -F: '{shells[$NF]++};END{for (i in shells) {print i,shells[i]}}' /etc/passwd

统计网站访问状态
ss -antp|grep 80|awk '{states[$1]++};END{for (i in states) {print i,states[i]}}'
ss -an|grep :80|awk '{states[$2]++};END{for (i in states) {print i,states[i]}}'|sort -k2 -rn

统计访问网站的每个IP数量
netstat -ant|grep :80|awk -F: '{ip_count[$8]++};END{for (i in ip_count) {print i,ip_count[i]}}'|sort
ss -an|grep :80|awk -F: '!/LISTEN/{ip_count[$(NF-1)]++};END{for (i in ip_count) {print i,ip_count[i]}}'|sort -k2 -rn|head

统计网站日志PV量
统计Apache/Nginx日志中的一天PV量
grep '24/May/2020' mysqladmin.cc-access_log |wc -l
统计Apache/Nginx日志中的一天不同IP访问量
grep '24/May/2020' mysqladmin.cc-access_log|awk '{ips[$1]++};END{for (i in ips) {print i,ips[i]}}'|sort -k2 -rn|head
grep '24/May/2020' access_log|awk '{ips[$1]++};END{for (i in ips) {print i,ips[i]}}'|awk '$2>100'|sort -k2 -rn
*****专业术语解释*****
网站浏览量(PV)指页面浏览次数,用户每打开一个页面记录1次PV;
访问次数(VV)从访客进入网站到最后关闭所有页面离开为1次访问,连续30分钟没有新开或刷新网页,或关闭浏览器,为本次访问结束;
独立访客(UV)1天内相同访客多次访问网站为1个UV;
独立IP(IP)1天内使用不同IP地址的用户访问网站的数量,同一IP访问多个网页,独立IP均为1。

7、实战练习(日志转储)
要求:需要将每台web服务器上的apache访问日志保留最近3天,3天以前的日志转储到专门的日志服务器,如何实现每台服务器上只保留3天以内的日志?
1、每台web服务器日志对应日志服务器相应的目录;
2、每台web服务器保留3天的访问日志,3天以前的日志在每天凌晨5:00转储到日志服务器
3、脚本转储失败,运维人员需要通过跳板机的菜单选择手动清理日志

1)apache日志每天进行轮转:
vim /usr/local/apache2/conf/extar/http-vhosts.conf
...
ErrorLog "|/usr/local/apache2/bin/rotatelogs -l /usr/local/apache2/logs/error_log-%Y%m%d 86400"
CustomLog "|/usr/local/apache2/bin/rotatelogs -l /usr/local/apache2/logs/access_log-%Y%m%d 86400" common
...

说明:
1.rotatelogs程序是apache自带的日志切割工具,-l参数表示使用本地系统时间为标准切割,不是GMT时区时间
2./usr/local/apache2/logs/access_log-%Y%m%d 86400用来指定日志文件位置和名称,其中86400用来指定分割时间默认单位为s,24小时制

2)log-server上搭建rsync,ip 10.1.1.2
cat /etc/rsyncd.conf
[web1]
path = /web1/logs
uid = root
gid = root
read only = false

[web2]
path = /web2/logs
uid = root
gid = root
read only = false

echo rsync --daemon >> /etc/rc.local

3)web服务器上定义清理脚本
#!/bin/bash
#clean log
clean_log(){
remote_log_server=10.1.1.2
server=$1
log_dir=/usr/local/apache2/logs
log_tmp_dir=/tmp/log
host=`ifconfig eth0|sed -n '2p'|awk -F'[ :]+' '{print $4}'`

[!-d $log_tmp_dir] && mkdir -p $log_tmp_dir

cd $log_dir
find ./ -daystart -mtime +3 -exec tar -uf $log_tmp_dir/`echo $host`_$(date +%F).tar{}\;
find ./ -daystart -mtime +3 -delete

cd $log_tmp_dir
rsync -a ./ $remote_log_server::$server && find ./ -daystart -mtime +1 -delete
}

4)jumper-server
#!/bin/bash
#jumper-server
#菜单打印
trap '' 1 2 3
menu1(){
   cat <<-END
   请选择对web1的操作动作:
   1.clean_apache_log
   2.reload_apache_service
   3.test_apache_service
   4.remote login
   END
}
menu2(){
   cat <<-END
   请选择需要操作的主机:
   1.DB1-Master
   2.DB2-Slave
   3.Web1
   4.Web2
   5.exit
   END
}

push_pubkey(){
ip=$1
#判断公钥文件是否存在,没有就生成公钥
[!-f ~/.ssh/id_rsa.pub] && ssh-keygen -P "" -f ~/.ssh/id_rsa &>/dev/null
#安装expect程序,与交互程序对话
sudo rpm -q expect &>/dev/null
test $? -ne 0 && sudo yum -y install expect
#将跳板机上yunwei用户的公钥推送到指定服务器上
/usr/bin/expect <<-EOF
spawn ssh-copy-id -i root@$ip
expect{
"yes/no" {send "yes\r";exp_continue}
"password:" {send "123456\r"}
}
expect eof
EOF
}
while true
do
menu2
#让用户选择相应操作
read -p "输入需要操作的主机:" host
case $host in
     1)
     ssh root@10.1.1.2
     ;;
     2)
     ssh root@10.1.1.3
     ;;
     3)
     clear
     menu1
     read -p "请输入需要操作的动作:" action
     case $action in
          1)
          ssh root@10.1.1.1 clean_log web1
          test $? -eq 0 && ech "日志清理完毕..."
          ;;
          2)
          service apache reload
          ;;
          3)
          wget http://10.1.1.1 &>/dev/null
          test $? -eq 0 && echo "web服务正常运行..."|| echo "web服务无法访问,请检查..."
          ;;
          4)
          ssh root@10.1.1.1
          ;;
          "")
          :
          ;;
     esac
     ;;
     5)
     exit
     ;;
     *)
     clear
     echo "输入错误,请重新输入..."
     ;;
esac
done 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值