避免定时任务脚本的">常见问题
很多脚本在实际使用的时候往往是以定时任务的方式运行,而非手工运行。但是实现同样功能的脚本在这两种运行方式下可能遇到的问题不尽相同。
以定时任务方式运行的脚本往往会遇到以下几个问题。
路径问题:当前目录往往不是脚本文件所在目录。因此,脚本在引用其使用的外部文件,如配置文件和其它脚本文件时,无法方便得使用相对路径。 命令找不到问题:脚本中使用到的一些外部命令,在手工执行脚本的时候可以正常调用。但是在定时任务下运行则可能出现脚本解析器找不到相关命令的问题。 脚本重复运行问题:一次脚本的执行未结束,而下一次脚本的运行已经开始。导致系统中有多个进程在同时运行
同一个脚本。
下面分享定时任务脚本开发中上述几个常见问题的处理方法。
路径问题
定时任务下当前路径往往不是脚本文件所在目录。因此我们需要用绝对路径来引用。即先获取脚本所在目录,然后以该目录为基础采用绝对路径的方式去引用脚本所需的外部文件。方法如下面代码所示。
清单 1. 获取脚本文件所在路径
#!/usr/bin/kshecho "Current path is: `pwd`"scriptPath=`dirname $0` #获取脚本所在路径echo "The script is located at: $scriptPath"cat "$scriptPath/readme" #使用绝对路径引用外部文件
将清单 1 中的脚本置于目录/opt/demo/scripts/auto-task 下,并在 cron 中添加该脚本。定时任务运行输出如下。
Current path is: /home/viscent
The script is located at: /opt/demo/scripts/auto-task
命令找不到问题
定时任务下运行的脚本可能出现脚本解析器找不到相关命令的问题。比如 Oracle 数据库中的 sqlplus 命令,脚本在调用该命令时若没有特殊处理则在定时任务下执行会使脚本解析器无法找到这个命令,出现如下所示的错误提示:
sqlplus: command not found
这是因为脚本在定时任务下执行时脚本是由非登录式 Shell 来执行的,并且执行脚本的父 Shell 并非 Oracle 用户的 Shell。因此,此时 Oracle 用户的.profile 文件并没有被调用。故解决的方法是在脚本的开头添加以下代码:
清单 2. 解决找不到外部命令问题
source /home/oracle/.profile
也就说,对于外部命令找不到的问题,可以通过在脚本的开头加一个 source 用户的.profile 文件的语句来解决。
脚本重复运行问题
定时任务脚本的另外一个常见问题是脚本重复运行的问题。比如,一个脚本被设置为每 5 分钟运行一次。若某一次该脚本的运行无法在 5 分钟内结束的话,定时任务服务仍然会新启一个进程来执行该脚本。这时就出现了运行同一个脚本的多个进程。而这可能导致脚本功能紊乱。并且浪费了系统资源。 避免脚本重复运行的方法通常有两种。一是在脚本执行时先检查系统是否存在运行该脚本的其它进程。若存在,则终止当前脚本的运行。二是,脚本运行时检查系统中是否存在其它进程运行该脚本。若存在,则结束那个进程(此方法有一定风险,慎用!)。这两种方法均需要在脚本的开头检查系统是否已经存在运行当前脚本的进程,若存在这样的进程则获取该进程的 PID。示例代码如下清单 3 所示。
清单 3. 防止脚本重复运行方法 1
#!/usr/bin/kshmain(){selfPID="$$"scriptFile="$0"typeset existingPidexistingPid=`getExistingPIDs $selfPID "$scriptFile"`if [ ! -z "$existingPid" ]; then echo "The script already running, exiting..." exit -1fidoItsTask}#获取除本身进程以外其它运行当前脚本的进程的 PIDgetExistingPIDs(){selfPID="$1"scriptFile="$2"ps -ef | grep "/usr/bin/ksh ${scriptFile}" | grep -v "grep" | awk "{ if(\$2!=$selfPID) print \$2 }"}doItsTask(){echo "Task is now being executed..."
sleep 20 #睡眠 20s,以模拟脚本在执行需要
长时间完成的任务}main $*
清单 4. 防止脚本重复运行方法 2
#!/usr/bin/kshmain(){selfPID="$$"scriptFile="$0"typeset existingPidexistingPid=`getExistingPIDs $selfPID "$scriptFile"`if [ ! -z "$existingPid" ]; then echo "The script already running, killing it..." kill -9 "$existingPid" #此方法有一定风险,慎用!fidoItsTask}#获取除本身进程以外其它运行当前脚本的进程的 PIDgetExistingPIDs(){selfPID="$1"scriptFile="$2"ps -ef | grep "/usr/bin/ksh ${scriptFile}" | grep -v "grep" | awk "{ if(\$2!=$selfPID) print \$2 }"}doItsTask(){echo "Task is now being executed..."sleep 20 #睡眠 20s,以模拟脚本在执行需要长时间完成的任务}main $*