利用shell腳本實現計划任務功能


開發背景介紹:

有一台DBSERVER,跑的是MySQL5.5。准備通過crontab執行計划任務定時備份數據庫。安裝crontab時竟然報告與MySQL沖突,在網上找了一下,倒是有位仁兄有遇到過,並提供了解決方案(http://blog.csdn.net/faye0412/article/details/7895366)。但是方法比較折騰,SERVER又是運行在線上環境,不敢亂動。於是就用shell腳本實現了一個簡單的計划任務功能。


設計思路:

設想是任務封裝到函數中,並加上必要的初始化聲明,包括起始時間、運行周期等。每個任務單獨一個sh文件,存放在統一的目錄中。由主程序讀取並按計划執行各任務。腳本以終端無關的形式在后台執行,啟動命令:nohup mytask.sh & 。結束運行的命令:kill -15 `cat mytask.pid`。腳本在centos6 ubuntu12測試通過。


實現功能:

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文件
      for i in `ls $FUNCDIR/*.sh`
      do
      ... ... ... ... ... ...
#確保每個任務腳本都包含了有效的初始化語句
      RunArg=
      . $i
      if [ "${RunArg:-'none'}" = "none" ]; then
        continue
      fi
      ... ... ... ... ... ...
#處理任務初始執行時間,將初始執行時間全部統一為標准總秒數(+%s)
      startTime=${startRun%[+|-]*}
      startSec=`date -d "$startTime" +%s`
      fixTime=${startRun:${#startTime}:$[ ${#startRun} - ${#startTime} ]}
      case ${fixTime:$[ ${#fixTime} - 1]} in
        s|[0-9])
        startSec=$[ $startSec + ${fixTime%s} ]
        ;;
        m)
        startSec=$[ $startSec + ${fixTime%m} * 60 ]
        ;;
        h)
        startSec=$[ $startSec + ${fixTime%h} * $ONEHOUR ]
        ;;
        d)
        startSec=$[ $startSec + ${fixTime%d} * $ONEDAY ]
        ;;
        w)
        startSec=$[ $startSec + ${fixTime%w} * $ONEWEEK ]
        ;;
        M)
        ty=`date -d $startTime +%y`
        tm=$[ `date -d $startTime +%m` + ${fixTime%M} ]
        td=$[ `date -d $startTime +%d` - 1 ]
        tt=`date -d $startTime +%T`
        if (( $tm > 12 )); then
        tm=$[ $tm % 12 ]
        ty=$[ $ty + $tm / 12 ]
        fi
        startSec=$[ `date -d "$ty-$tm-1 $tt" +%s` + $td * $ONEDAY ]
        ;;
        y)
        ty=$[ `date -d $startTime +%y` + ${fixTime%y} ]
        td=$[ `date -d $startTime +%j` - 1 ]
        tt=`date -d $startTime +%T`
        startSec=$[ `date -d "$ty-1-1 $tt" +%s` + $td * $ONEDAY ]
        ;;
      esac
#計算任務執行間隔時間,將除單位為年和月以外的簡隔時間統一為秒。由於以年和月為單位的間隔時間要根據實際運行時間而定,所以不能預先計算。
      tp=s
      case ${atime:$[ ${#atime} - 1]} in
      s)
      addTime=${atime%s}
      ;;
      m)
      addTime=$[ ${atime%m} * 60 ]
      ;;
      h)
      addTime=$[ ${atime%h} * $ONEHOUR ]
      ;;
      d)
      addTime=$[ ${atime%d} * $ONEDAY ]
      ;;
      w)
      addTime=$[ ${atime%w} * $ONEWEEK ]
      ;;
      M)
      addTime=${atime%M}
      tp=M
      ;;
      y)
      addTime=${atime%y}
      tp=y
      ;;
      ... ... ... ... ... ...
      esac
#將初始化后的任務參數存入數組,供后續程序調用
#任務參數以#分隔,分別為任務函數名、開始時間(標准總秒數)、運行間隔時間、間隔時間單位。
#間隔時間單位為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
#當前時間超過任務計划運行時間小於運行閥值時啟動任務
#為避免因某個任務執行時間過長超出此任務間隔時間而導致重入,
#每個任務在執行時都會創建鎖文件,並在任務執行完后刪除。
#為了保證多任務的並發性,每個任務都會以后台運行方式執行。
      if ! [ -e $flagfile ] && (( $tdiff < $MISSTIMES )) ; then
        {
        touch $flagfile;
        echo "$fn start at "`date`\(`date +%s`\) >>$LOGFILE;
        result=`$fn`;
        echo "$fn finished at "`date`\(`date +%s`\) >>$LOGFILE;
        rm -f $flagfile;
        } &
      else
        echo "$fn has skipped" >>$LOGFILE
      fi
#根據間隔時間單位計算下一次任務執行的時間
      case $tp in
#秒
      s)
      addSec=$addTime
      ;;
#月
      M)
      ty=`date +%y`
      tm=$[ `date +%m` + $addTime ]
      td=$[ `date +%d` - 1 ]
      if (( $tm > 12 )); then
        tm=$[ $tm % 12 ]
        ty=$[ $ty + $tm / 12 ]
      fi
      addSec=$[ `date -d "$ty-$tm-1 $nowTime" +%s` + $td * $ONEDAY ]
      ;;
#年
      y)
      ty=$[ `date +%y` +$addTime ]
      td=$[ `date +%d` - 1 ]
      addSec=$[ `date -d "$ty-1-1 $nowTime" +%s` + $td * $ONEDAY ]
      ;;
#將只執行一次的任務從任務數組中清除
      *)
      aRunList=(`echo ${aRunList[@]} |sed "s/$fn\(#[^#]*\)\{2\}#[^ ]*//g"`)
      IntervalTime=0;
      continue
      ;;
      esac
      tntarg=$[ $tntarg + ( $tdiff / $addSec ) * $addSec + $addSec ]
      eval ${ntarg}=$tntarg
      tdiff=$[ $nowSec - $tntarg ]
      fi
      if (( $tdiff > $IntervalTime )) ; then
        IntervalTime=$tdiff;
      fi
      done
      ... ... ... ... ... …

 

 

遺留問題:每個任務腳本中聲明的函數名必須唯一不能重復,否則會導致任務函數覆蓋,目前沒有很好的解決。

因為是第一次編寫稍復雜的腳本,代碼結構和水平還有待提高,希望能起到拋磚引玉的作用.

 完整源碼點此下載

 

 題外話:ubuntu下的LibreOffice實在是用不好,排版有些亂,有興趣的朋友可以直接下載源碼看。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM