Linux系统中有日志切割的工具logrotate,它可以按照我们的预期按时间或者大小来切割和归档老的日志,它还可以压缩切割后的日志,也可以定义老日志保留的时间。

本案例的需求是用shell脚本来实现类似logrotate的功能,具体要求如下:

1)要处理的日志路径:/data/logs/1.log

2)每天0点0分切割日志

3)老日志保留一周

4)归档后的日志名字为:1.log.1,1.log.2,... ,1.log.7

5)假设日志归档后,新日志可以自动生成


知识点一:logrotate

先声明,本案例脚本并不会引用该部分知识点,在这里仅做简单介绍,下面通过一段配置解释其含义。

/var/log/messages {    //日志的路径
       rotate  5       //保留5份归档后的日志
       weekly          //一周切割一次
       postrotate      //定义切割日之后,要执行的指令
           /usr/bin/killall -HUP syslogd  //重载syslogd服务,这样会生成新的/var/log/messages文件
       endscript       //表示执行shell命令到此结束    
}

  "/var/log/httpd/access.log" /var/log/httpd/error.log  {  //同时写多个日志的路径
       rotate  5
       mail [email protected]  //把归档后的日志发送给该邮箱
       size  100k       //当日志文件达到100k时开始做切割
       sharedscripts    //对于第一行定义的所有日志来说,以下的shell命令(/usr/bin/killall -HUP httpd)只运行一次,而不是每切割一个日志就执行一次。
       postrotate
            /usr/bin/killall -HUP httpd
       endscript     
  }
  
    /var/log/news/* {  //支持*通配
    monthly    //一个月切割一次日志
    rotate  2  //保留2个归档日志
    olddir /var/log/news/old  //将切割后的日志保存到这个目录下面
    missingok  //如果日志文件丢失,不报错
    postrotate
       kill -HUP 'cat /var/run/inn.pid'
    endscript
    nocompress  //归档后的日志不压缩,与其相对的参数是compress   
    }


知识点二:shell中的exit、break和continue

1)exit

在shell脚本中使用exit,则直接退出当前脚本,exit后续的所有指令都不再执行,示例脚本exit.sh:

#!/bin/bash
for i in `seq 1 5`
do
        echo $i
        if [ $i -eq 3 ]
        then
            exit 1
        fi
        echo $i
done
echo aaaaa

执行脚本,结果如下:

# sh exit.sh 
1
1
2
2
3

exit后面可以跟一个数字,表示该脚本的返回值,执行完脚本后,执行echo $?可以打印脚本的返回值。

# echo $?
1


2)break

在shell脚本的循环中,可以使用break跳出循环体,示例脚本break.sh:

#!/bin/bash
for i in `seq 1 5`
do
        echo $i
        if [ $i -eq 3 ]
        then
            break
        fi
        echo $i
done
echo aaaaa

执行脚本,结果如下:

# sh break.sh 
1
1
2
2
3
aaaaa

说明:当$i为3时,遇到break指令,此时会跳出整个循环,从而执行echo  aaaaa指令。


3)continue

和break类似,continue的作用是结束本次循环,继续下一次循环,而不会结束整个循环体,示例脚本continue.sh:

#!/bin/bash
for i in `seq 1 5`
do
        echo $i
        if [ $i -eq 3 ]
        then
            continue
        fi
        echo $i
done
echo aaaaa

执行脚本,结果如下:

# sh continue.sh 
1
1
2
2
3
4
4
5
5
aaaaa

说明:当$i=3时,遇到continue指令,此时结束本次循环,即continue后面的指令echo $i不再执行,继续后续循环,即i=4。


本案例参考脚本

#!/bin/bash
#日志切割归档,按天切割,1.log变1.log.1, 1.log.1变1.log.2, ...
#作者:
#日期:

logdir=/data/logs/

#定义函数,如果一个文件存在,则删除
function e_df()
{
    if [ -f $1 ]
    then
        rm -f $1
    fi
}

cd $logdir

#从7到2,依次遍历循环
for i in `seq 7 -1 2`
do
    #$i2比$i小1
    i2=$[$i-1]
    
    #首先判断1.log.7是否存在,若存在则删除
    e_df  1.log.$i

    #当1.log.6存在,则把1.log.6改名为1.log.7,依次类推
    if [ -f  1.log.$i2 ]
    then
        mv  1.log.$i2 1.log.$i
    fi
done

#由于1.log后面无后缀,所以不能走上面的for循环,只能另外拿出来处理
e_df 1.log.1
mv  1.log 1.log.1

##说明:这个脚本写完后,放到任务计划里,每天0点0分执行。


需求扩展

现在的需求是,日志按大小来切割,比如当日志大于等于100M时,则需要处理,处理后的日志需要压缩,并且延迟一天,即1.log.1不用压缩,其他需要压缩。

#!/bin/bash
#日志切割归档,按日志大小切割(100M),1.log变1.log.1, 1.log.1变1.log.2, ...
#作者:
#日期:

logdir=/data/logs/

#技术1.log大小
size=`du -sk $logdir/1.log |awk '{print $1}`

#如果1.log小于100M,则退出脚本
if [ $size -lt 10240 ]
then
    exit 0
fi

#定义函数,如果一个文件存在,则删除
function e_df()
{
    if [ -f $1 ]
    then
        rm -f $1
    fi
}

cd $logdir

#如果1.log.1存在,则先把它压缩为1.log.1.gz,这样下面的for循环才不会出错
if [ -f 1.log.1 ]
then
    gzip 1.log.1
fi
#由于1.log.1已经被压缩为1.log.gz,所以可以直接将1.log改名为1.log.1
mv  1.log 1.log.1

#从7到2,倒序循环
for i in `seq 7 -1 2`
do
    #$i2比$i小1
    i2=$[$i-1]
    
    #首先判断1.log.7.gz是否存在,若存在则删除
    e_df  1.log.$i.gz

    #当1.log.6.gz存在,则把1.log.6.gz改名为1.log.7.gz,依次类推
    if [ -f  1.log.$i2.gz ]
    then
        mv  1.log.$i2.gz 1.log.$i.gz
    fi
done

##说明:由于我们需要按照日志大小切割,所以这个脚本写完后,需要每分钟执行一次。