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

 開發背景介紹:

有一臺 DBSERVER ,跑的是 MySQL5.5 。準備通過 crontab 執行計劃任務定時備份數據庫。安裝 crontab 時竟然報告與 MySQL 衝突,在網上找了一下,倒是有位仁兄有遇到過,並提供瞭解決方案。但是方法比較折騰,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實在是用不好,排版有些亂,有興趣的朋友可以直接下載源碼看。

本文轉載自博客園,原文鏈接:http://www.cnblogs.com/lykyl/archive/2013/05/06/3063905.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章