51Testing软件测试论坛

标题: 利用shell脚本实现计划任务功能 [打印本页]

作者: 听海——sky    时间: 2018-6-14 14:20
标题: 利用shell脚本实现计划任务功能
开发背景介绍:
有一台DBSERVER,跑的是MySQL5.5。准备通过crontab执行计划任务定时备份数据库。安装crontab时
竟然报告与MySQL冲突,在网上找了一下,倒是有位仁兄有遇到过,并提供了解决方案。但是方法比较
折腾,SERVER又是运行在线上环境,不敢乱动。于是就用shell脚本实现了一个简单的计划任务功能。

设计思路:
设想是任务封装到函数中,并加上必要的初始化声明,包括起始时间、运行周期等。每个任务单独一个s
h文件,存放在统一的目录中。由主程序读取并按计划执行各任务。脚本以终端无关的形式在后台执行,
启动命令:nohup mytask.sh & 。结束运行的命令:kill -15 `cat mytask.pid`。脚本在centos6 及ubuntu1
2测试通过。

实现功能:
1、多任务并发执行,不会互相影响,采用锁机制避免单个任务的重叠执行。
2、每个任务以单独脚本形式保存,相互独立。
3、支持起始运行时间,如"2013/05/08"、"13:30"或“now”。并且支持给起始运行时间的修正值,比如"
now+5m"表示当前时间的5分钟后执行(另外还实现了负数修正值,比如-1h,现在觉得这个功能挺无
聊的)。
4、支持多种类型的运行周期设定,包括秒、分、时、天、周、月、年还有一次性任务。
5、会根据任务执行间隔,自动设定休眠时间,主程序占用资源极小。

程序主要结构及说明:
一、任务脚本编写规范
每个任务脚本都必须包含初始化语句和任务函数这两部分,函数名要保证唯一性。
初始化语句格式如下:
RunArg="<调用函数名>#<起始运行时间>#<运行周期>"
以#符分隔参数依次定义为:调用函数名、起始运行时间、运行周期。
    1、调用函数名,任务函数必须要在脚本中明确定义。
    2、起始运行时间分两部分。
第一部分为初始时间,格式为"yyyy/MM/dd hh:mm:ss"也可以是时间值片断,例如:"2013/03/05"、
"03/05"、“03/05 21:30”、"21:30"或"now"代表当前时间。
第二部分为修正时间,格式为"+时间单位"或“-时间单位”,意思为在初始时间的基础上做进一步的时
间修正。例如:"+5s"、"-10m"等。时间的单位区别大小写,具体定义如下:
y=年、M=月、d=日、h=时、m=分、s=秒、w=星期
    3、运行周期即为任务函数运行的间隔时间,取值与修正时间类似,只是取消了+-号,如果值为不
带单位的0则表示只运行一次。
例如:
#在凌晨零点开始执行_backdb函数,每隔1天运行一次。
RunArg='_backdb#00:00#1d'
#在当前时间的2分钟后开始执行_test1func函数,每隔5分钟运行一次。
RunArg='_test1func#now+2m#5m'
#在5月12日14点30开始执行_test2func函数,只运行一次。
RunArg='RunArg='_test5func#5/12 14:30#0'
最后给一个完整的任务脚本:
#!/bin/bash
#启动即开始执行_test4func函数,每隔1个月运行一次。
RunArg='_test4func#now#1M'
#定义任务函数_test4func
function _test4func()
{
#任务内容,此处以休眠5秒模拟任务运行时间。
sleep 5;
}

    二、主程序说明
    1、初始化
      FUNCDIR=`dirname $0`"/tasks"    #任务脚本存放目录
      LOGFILE=`dirname $0`"/mytask.log"    #运行记录文件名
      PIDFILE=`dirname $0`"/mytask.pid"    #pid存放文件名
      LOCKFILE=`dirname $0`"/mytask.lock"    #锁文件名
      ... ... ... ... ... ...
#通过检测锁文件存在,判断程序是否已经运行,防止重入
      if [ -f $LOCKFILE ]; then
        exit 0
      else
        touch $LOCKFILE
        echo "mytask start at "`date` >$LOGFILE
      fi
#捕获系统信号,处理程序锁。
      trap "rm -f $LOCKFILE;rm -f $FUNCDIR/*_lock;echo 'exit';kill -15 $$" SIGINT EXIT
      ... ... ... ... ... ...

    2、任务预处理
#循环执行指定目录下的所有sh文件
  1. <p>      for i in `ls $FUNCDIR/*.sh`</p><p>      do</p><p>      ... ... ... ... ... ...</p><p>#确保每个任务脚本都包含了有效的初始化语句</p><p>      RunArg=</p><p>      . $i</p><p>      if [ "${RunArg:-'none'}" = "none" ]; then</p><p>        continue</p><p>      fi</p><p>      ... ... ... ... ... ...</p><p>#处理任务初始执行时间,将初始执行时间全部统一为标准总秒数(+%s)</p><p>      startTime=${startRun%[+|-]*}</p><p>      startSec=`date -d "$startTime" +%s`</p><p>      fixTime=${startRun:${#startTime}:$[ ${#startRun} - ${#startTime} ]}</p><p>      case ${fixTime:$[ ${#fixTime} - 1]} in</p><p>        s|[0-9])</p><p>        startSec=$[ $startSec + ${fixTime%s} ]</p><p>        ;;</p><p>        m)</p><p>        startSec=$[ $startSec + ${fixTime%m} * 60 ]</p><p>        ;;</p><p>        h)</p><p>        startSec=$[ $startSec + ${fixTime%h} * $ONEHOUR ]</p><p>        ;;</p><p>        d)</p><p>        startSec=$[ $startSec + ${fixTime%d} * $ONEDAY ]</p><p>        ;;</p><p>        w)</p><p>        startSec=$[ $startSec + ${fixTime%w} * $ONEWEEK ]</p><p>        ;;</p><p>        M)</p><p>        ty=`date -d $startTime +%y`</p><p>        tm=$[ `date -d $startTime +%m` + ${fixTime%M} ]</p><p>        td=$[ `date -d $startTime +%d` - 1 ]</p><p>        tt=`date -d $startTime +%T`</p><p>        if (( $tm > 12 )); then</p><p>        tm=$[ $tm % 12 ]</p><p>        ty=$[ $ty + $tm / 12 ]</p><p>        fi</p><p>        startSec=$[ `date -d "$ty-$tm-1 $tt" +%s` + $td * $ONEDAY ]</p><p>        ;;</p><p>        y)</p><p>        ty=$[ `date -d $startTime +%y` + ${fixTime%y} ]</p><p>        td=$[ `date -d $startTime +%j` - 1 ]</p><p>        tt=`date -d $startTime +%T`</p><p>        startSec=$[ `date -d "$ty-1-1 $tt" +%s` + $td * $ONEDAY ]</p><p>        ;;</p><p>      esac</p><p>#计算任务执行间隔时间,将除单位为年和月以外的简隔时间统一为秒。由于以年和月为</p><p>单位的间隔时间要根据实际运行时间而定,所以不能预先计算。</p><p>      tp=s</p><p>      case ${atime:$[ ${#atime} - 1]} in</p><p>      s)</p><p>      addTime=${atime%s}</p><p>      ;;</p><p>      m)</p><p>      addTime=$[ ${atime%m} * 60 ]</p><p>      ;;</p><p>      h)</p><p>      addTime=$[ ${atime%h} * $ONEHOUR ]</p><p>      ;;</p><p>      d)</p><p>      addTime=$[ ${atime%d} * $ONEDAY ]</p><p>      ;;</p><p>      w)</p><p>      addTime=$[ ${atime%w} * $ONEWEEK ]</p><p>      ;;</p><p>      M)</p><p>      addTime=${atime%M}</p><p>      tp=M</p><p>      ;;</p><p>      y)</p><p>      addTime=${atime%y}</p><p>      tp=y</p><p>      ;;</p><p>      ... ... ... ... ... ...</p><p>      esac</p>
复制代码

#将初始化后的任务参数存入数组,供后续程序调用
#任务参数以#分隔,分别为任务函数名、开始时间(标准总秒数)、运行间隔时间、间隔时间单位。
#间隔时间单位为s、M、y,即秒、月、年。
      aRunList=(${aRunList[@]} "$fn#$startSec#$addTime#$tp")
      fi
      done

    3、任务执行
#循环读取任务数组,并根据任务参数适时启动计划任务
      ... ... ... ... ... ...
      IntervalTime=$INIT; #主程序休眠时长
      ... ... ... ... ... ...
      for i in ${aRunList[@]}
      do
      ... ... ... ... ... …
#以动态变量的形式存放各任务的下一次运行时间
      ntarg="${fn}_ntime"
      flagfile="${FUNCDIR}/${fn}_lock"
      eval ${ntarg}=\${${ntarg}:=$startSec}
      eval tntarg=\$${ntarg}
      tdiff=$[ $nowSec - $tntarg ]
      if (( $tdiff >= 0 )); then
#当前时间超过任务计划运行时间小于运行阀值时启动任务
#为避免因某个任务执行时间过长超出此任务间隔时间而导致重入,
#每个任务在执行时都会创建锁文件,并在任务执行完后删除。
#为了保证多任务的并发性,每个任务都会以后台运行方式执行。
  1. <p>      if ! [ -e $flagfile ] && (( $tdiff < $MISSTIMES )) ; then</p><p>        {</p><p>        touch $flagfile;</p><p>        echo "$fn start at "`date`\(`date +%s`\) >>$LOGFILE;</p><p>        result=`$fn`;</p><p>        echo "$fn finished at "`date`\(`date +%s`\) >>$LOGFILE;</p><p>        rm -f $flagfile;</p><p>        } &</p><p>      else</p><p>        echo "$fn has skipped" >>$LOGFILE</p><p>      fi</p><p>#根据间隔时间单位计算下一次任务执行的时间</p><p>      case $tp in</p><p>#秒</p><p>      s)</p><p>      addSec=$addTime</p><p>      ;;</p><p>#月</p><p>      M)</p><p>      ty=`date +%y`</p><p>      tm=$[ `date +%m` + $addTime ]</p><p>      td=$[ `date +%d` - 1 ]</p><p>      if (( $tm > 12 )); then</p><p>        tm=$[ $tm % 12 ]</p><p>        ty=$[ $ty + $tm / 12 ]</p><p>      fi</p><p>      addSec=$[ `date -d "$ty-$tm-1 $nowTime" +%s` + $td * $ONEDAY ]</p><p>      ;;</p><p>#年</p><p>      y)</p><p>      ty=$[ `date +%y` +$addTime ]</p><p>      td=$[ `date +%d` - 1 ]</p><p>      addSec=$[ `date -d "$ty-1-1 $nowTime" +%s` + $td * $ONEDAY ]</p><p>      ;;</p><p>#将只执行一次的任务从任务数组中清除</p><p>      *)</p><p>      aRunList=(`echo ${aRunList[@]} |sed "s/$fn\(#[^#]*\)\{2\}#[^ ]*//g"`)</p><p>      IntervalTime=0;</p><p>      continue</p><p>      ;;</p><p>      esac</p><p>      tntarg=$[ $tntarg + ( $tdiff / $addSec ) * $addSec + $addSec ]</p><p>      eval ${ntarg}=$tntarg</p><p>      tdiff=$[ $nowSec - $tntarg ]</p><p>      fi</p><p>      if (( $tdiff > $IntervalTime )) ; then</p><p>        IntervalTime=$tdiff;</p><p>      fi</p><p>      done</p><p>      ... ... ... ... ... …</p><p></p>
复制代码




遗留问题:每个任务脚本中声明的函数名必须唯一不能重复,否则会导致任务函数覆盖,目前没有很
好的解决。
因为是第一次编写稍复杂的脚本,代码结构和水平还有待提高,希望能起到抛砖引玉的作用.


作者: qqq911    时间: 2018-6-26 11:21
感谢分享·~
作者: Miss_love    时间: 2020-12-29 10:56
感谢分享




欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2