我们都知道,在Linux中,可以通过cal命令来查看日历。Cal怎么用呢,我们可以man一下,可以看到如下结果:
我们可以看到cal命令的基本用法如下:cal [-smjy13] [[[day] month] year]
当然如果不加指定参数的话,会显示当前的月份情况。
其常用参数如下:
-1 Display single month output. (This is the default.)
-3 Display prev/current/next month output.
-s Display Sunday as the first day of the week.
-m Display Monday as the first day of the week.
-j Display Julian dates (days one-based, numbered from January 1).
-y Display a calendar for the current year.
-V Display version information and exit.
我对其逐个简单解释一下:
-1 会把当前单月显示输出(这个也是默认值)
-3 会显示3个月,分别是上一个月,本月,和下个月
-s 会把星期天作为每周的第一天显示
-m 会把星期一作为每周的第一天显示
-j 会显示【天文学】儒略日(从公历1月1日开始的时间,今天是一年里的第多少天)
-y 显示当前一整年的日历
-V 会显示当前的版本信息
( 每个参数的使用方法,已经进行了说明,这里就不贴图了。)
看了这些命令和解释,其实很简单吧。简单试验一下,相信大家肯定就知道怎么用了。后面我补充一下这些参数配合使用的效果,比如:
1.cal -jy 可以将本年度的每一天从年初到年为表上序号,从1~365计数的形式显示,如下:
2.cal -3j 可以将本月与上一月、下一月的顺序计数儒略日时间,如下:
3.cal 5 2013 可以显示2013年5月份的情况
4.当然这里还有一个实际有用的小技巧:比如你想知道前年某一天(eg.2011-11-14)和今天相隔多少天,那么我们可以这么做:
1)通过 cal -j 14 11 2011 可以得到2011-11-14在当年的天文学日
2)通过cal看下今天的时间
3)然后很简单就可以计算出来了,另外计算可以通过bc,此略。
5.说到这里,我们也可以再尝试一下:cal 9 1752 ,可以发现结果如下图:
我们可以发现,竟然9月2号过去就是9月14号。有人说这个是不是Linux cal的bug,其实不是的,那么中间的时间去哪里了呢?这里引出一个故事,大家看了就明白了:
1752年9月,大英帝国极其所属美洲殖民地的恺撒历法被格里高利教皇历法所取代。由于恺撒历法比格里高利历法迟11天,因此9月2日当天改历法后,次日须为9月14日。现行的公历是格利戈里历法,这个历法的是1582年教皇格利戈里根据恺撒大帝引进的算法改进的。它采用的是闰年制也就是现行的制度,不过有一个需要注意的地方就是,这个历法并不是连续的,中间缺少了11天。1752年9月2日之后的那一天并不是1752年9月3日,而是1752年9月14日。也就是说,从1752年9月3日到1752年9月13日的11天并不存在。抹掉这11天是由英国议会做出的决定。所以要计算某年每个月的天数的,除了要考虑是否是闰年以外,还要考虑1752年的9月。 (这段话出自:互动百科)
呵呵,看来Linux还有很多奇妙的事情等着挖掘。
---2013年5月6日17:25:19
利用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测试通过。
程序主要结构及说明:
一、任务脚本编写规范
每个任务脚本都必须包含初始化语句和任务函数这两部分,函数名要保证唯一性。
初始化语句格式如下:
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
... ... ... ... ... …
遗留问题:每个任务脚本中声明的函数名必须唯一不能重复,否则会导致任务函数覆盖,目前没有很好的解决。
因为是第一次编写稍复杂的脚本,代码结构和水平还有待提高,希望能起到抛砖引玉的作用.