前言

      当需要脚本来管理独立程序的启动、停止、重启、状态查询等功能时,小编为您提供一个模板,便于您参考。

      

脚本说明

      run.sh  #用于管理程序执行的脚本

      run.conf #程序执行 配置项

 

脚本展示

     注:关键代码部分,已做解释。

      1.run.sh

#!/bin/sh
### ====================================================================== ###
##                                                                          ##
##  Jar Stand-alone Programme Bootstrap Script                              ##
##                                                                          ##
### ====================================== ================================ ###

# 2019-09-21 script by vincent


Usage(){
    echo Usage:
    echo "$0 start|start-console|stop|restart|restart-console|status"
}

# get shell path by $0(script name)
SHELLPATH=$(cd "`dirname "$0"`" >/dev/null;pwd)
# get script's name by $0
SHELLNAME=$(basename "$0")
# shell's parent dictory
AcpSrvHome=$(cd "`dirname "$SHELLPATH"`" >/dev/null;pwd)
# locate the start class
APPNAME=com.vincent.test.BoostServer
NAME=BoostServer
# load the extra file
CONF="$SHELLPATH/run.conf"

# Load run.conf
if [ -r "$CONF" ];then
    . "$CONF"
else
    echo "config file:$CONF error!"
    exit 1
fi

#check the authority
if [ -n "$run_as" -a "$run_as" != "root" -a "`id -u`" = "0" ];then
    su - $run_as -c "$SHELLPATH/$SHELLNAME $*"
    exit $?
fi

# Set default parameter
[ -z "$VERBOSE" ]   && VERBOSE=no
[ -z "$log_home" ]  && log_home="$AcpSrvHome/logs"
[ -z "$JAVA" ]      && JAVA=$JAVA_HOME/bin/java
[ -z "$PIDFILE" ]   && PIDFILE="$SHELLPATH/run.pid"
[ -z "$LOGFILE" ]   && LOGFILE="$log_home/$NAME.log"
[ -z "$restart_sleep" ] && restart_sleep=30
PID=

# method check environment
checkEnv(){
    local chkErr
    chkErr=0
    # Check env and parameters
    [ -z "$JAVA_HOME" -o ! -x "$JAVA" ] && echo "JAVA_HOME error,$JAVA no exist,or no permission!" && chkErr=2

    touch "$PIDFILE" > /dev/null
    [ "$?" != "0" -o ! -r "$PIDFILE" ] && echo "PIDFILE:$PIDFILE read/write error!" && chkErr=2

    touch "$LOGFILE" > /dev/null
    [ "$?" != "0" ] && echo "$LOGFILE not have write permission!" && chkErr=2

    return $chkErr
}

getrunPSinfo(){
    ps aux|grep "java .*\s$APPNAME" 2>/dev/null
}
getrunPID(){
    ps aux|grep "java .*\s$APPNAME" 2>/dev/null|awk '{print $2}'
}
getfilePID(){
    if [ -f "$PIDFILE" ];then
        cat "$PIDFILE"|tail -1
    fi
}

chkPID(){
    local fpid pspid
    fpid=$(getfilePID)
    psnum=$(getrunPID|wc -l)
    #TODO:发现存在多个进程时,如何处理?
    pspid=$(getrunPID|tail -1)
    if [ -n "$pspid" ];then
        [ "$fpid" != "$pspid" ] && echo -n "$pspid" > "$PIDFILE"
        echo $pspid
    else
        echo -n '' > "$PIDFILE"
    fi
}

start(){
    local type
    type=$1
    PID=$(chkPID)
    if [ -n "$PID" ];then # already running!
        echo "$NAME already running!(pid:$PID)" 
        return 0
    fi

    # Set CLASSPATH
    CLASSPATH=$AcpSrvHome
    export CLASSPATH=$CLASSPATH

    if [ "$type" = "console" ];then
        $JAVA $JAVA_OPTS $APPNAME
    else
        # start execute and append log‘s file to LOGFILE
        nohup $JAVA $JAVA_OPTS $APPNAME >> "$LOGFILE" 2>&1 &
        # start execute the jar
        # nohup $JAVA -jar $JAVA_OPTS jarname >> "$LOGFILE" 2>&1 &

        PID=$!
        sleep 1
        ckPID=$(chkPID)
        if [ "$PID" = "$ckPID" ];then
            echo $PID > "$PIDFILE"
            echo "$NAME started(pid:$ckPID)." 
        else
            tail -n 10 "$LOGFILE"
            echo "$NAME start failed!" 
        fi
    fi
}
stop(){
    local cnt retry ckpid killsign
    # return values:
    # 0 = not running
    # 1 = stoped
    # 2 = stop failed
    PID=$(chkPID)
    if [ -z "$PID" ];then
        echo "$NAME not running!" 
        return 0
    fi
    cnt=1
    retry=10
    killsign=
    while [ $cnt -le $retry ];do
        ckpid=$(chkPID)
        if [ -n "$ckpid" ];then
            [ $cnt -gt 6 ] && killsign="-9"
            echo "$cnt:kill $killsign $PID"
            kill $killsign $PID
            sleep 1
        else
            echo "$NAME stoped!" 
            break
        fi
        cnt=$(expr $cnt + 1)
    done
    ckpid=$(chkPID)
    if [ -n "$ckpid" ];then
        echo "$NAME stop failed!($ckpid)" && return 2
    else
        [ "$killsign" = "-9" ] && return 1  # kill -9 stop
        return 0  # kill stoped
    fi
}

# execute with param 
case "$1" in
start)
    checkEnv || exit 2
    start
    ;;
start-console)
    checkEnv || exit 2
    start console
    ;;
stop)
    stop
    ;;
restart|restart-console)
    checkEnv || exit 2
    stop
    retv=$?
    [ "$1" = "restart-console" ] && type=console
    case "$retv" in
    0)
        start $type
        ;;
    1)
        cnt=1
        echo -n "Waiting zookeeper timeout"
        #you can ignore,my project registered on the zk.
        while [ $cnt -lt $restart_sleep ];do
            echo -n .
            sleep 1
            cnt=$(expr $cnt + 1)
        done
        #################################################
        echo 
        start $type
        ;;
    *)
        echo "stop failed!"
        ;;
    esac
    ;;
status)
    if [ "$VERBOSE" != "no" ];then
        #env
        echo NAME=$NAME
        echo SHELLNAME=$SHELLPATH/$SHELLNAME
        echo AcpSrvHome=$AcpSrvHome
        echo PIDFILE=$PIDFILE
        echo LOGFILE=$LOGFILE
        echo
    fi
    PID=$(chkPID)
    if [ -z "$PID" ];then
        echo "$NAME is not running."
        exit 1
    else
        [ "$VERBOSE" = "yes" ] && getrunPSinfo
        echo 
        echo "$NAME is running.(pid:$PID)"
    fi
    ;;
chkEnv)
    checkEnv || exit 2
    ;;
version)
    echo "$NAME bootstrap script."
    ;;
*)
    #remind adding the param to execute shell
    Usage
    exit 1
    ;;
esac
exit 0

    2.run.conf

#脚本执行用户
run_as=admin
# 日志存放路径
log_home=/usr/local/vincentlog 
JMX_REMOTE_PORT=9109
DATE_STR=$(date '+%Y%m%d_%H%M%S')
# 重启动等待时间(秒),避免zookeeper被注销问题
restart_sleep=30        
VERBOSE=yes

# to append the java options
JAVA_OPTS=
# Djava.ext.dirs
# JAVA_OPTS="$JAVA_OPTS -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:$AcpSrvHome/lib"

# GC LOG
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintCommandLineFlags -XX:HeapDumpPath=$log_home"
JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:$log_home/${NAME}.gc-${DATE_STR}.log"

# JMX REMOTE
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=$JMX_REMOTE_PORT -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

# Memorys
JAVA_OPTS="$JAVA_OPTS -Xms768m -Xmx768m -server -XX:MaxNewSize=224m -XX:NewSize=224m -XX:SurvivorRatio=5 -XX:MaxTenuringThreshold=60"


#debug
#JAVA_OPTS="$JAVA_OPTS -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8881"

 

脚本拆解

1.$(cd `dirname $0`;pwd)

linux获取指定文件的目录
-----------------------------------
vim /1/dirname.sh
CAD=$(cd `dirname $0`;pwd)
cd $CAD

说明:
dirname $0 :取得当前执行脚本文件的父目录
cd `dirname $0` :进入这个目录
pwd :显示当前目录(cd执行后的)
-----------------------------------
变体:
SHELLPATH=$(cd "`dirname "$0"`" >/dev/null;pwd)
AcpSrvHome=$(cd "`dirname "$SHELLPATH"`" >/dev/null;pwd)
获取当前文件路径的父路径
-----------------------------------

2.$(basename "$0") 和 $0

`basename $0`只显示当前脚本或命令的名字

$0显示会包括当前脚本或命令的路径

3.exit $? 

 

4.文件运算符

#检测文件是否是目录
if [ -d $file ]
#检测文件是否是普通文件
if [ -f $file ]
#检测文件是否可读
if [ -r $file ]
#检测文件是否可写
if [ -w $file ]
#检测文件是否可执行
if [ -x $file ]  
#检测文件是否为空
if [ -s $file ] 
#检测文件(包括目录)是否存在
if [ -e $file ] 

#实例
#1.如果文件可读
if [ -r $file ] 
then
   echo "文件可读"
else
   echo "文件不可读"
fi

5.加载文件

CONF="$SHELLPATH/run.conf"

# Load run.conf
if [ -r "$CONF" ];then
    #加载可读的run.conf
    . "$CONF"
else
    echo "config file:$CONF error!"
    exit 1
fi

6.字符串监测

-n 检测字符串长度是否为0,不为0返回 true。	
-z 检测字符串长度是否为0,为0返回 true。
$  检测字符串是否为空,不为空返回 true。
=  检测两个字符串是否相等,相等返回 true。
!= 检测两个字符串是否相等,不相等返回 true。

7.逻辑运算符

-a 与运算,两个表达式都为 true 才返回 true。
-o 或运算,有一个表达式为 true 则返回 true。
!  非运算,表达式为 true 则返回 false,否则返回 true。

8.id -u (显示当前用户的uid)

##实例 -- root标识
whoami(显示当前用户的用户名)

if [ `whoami` = "root" ];then
 echo "root用户!"
else
 echo "非root用户!"
fi

id -u (显示当前用户的uid)

if [ `id -u` -eq 0 ];then
 echo "root用户!"
else
 echo "非root用户!"
fi

9.$* 和 $@

$* 和 $@ 都表示传递给函数或脚本的所有参数:
当 $* 和 $@ 不被双引号" "包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。
当它们被双引号" "包含时,就会有区别了:
  "$*"会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
  "$@"仍然将每个参数都看作一份数据,彼此之间是独立的。

10.[ -z "$" ] &&

实例:
[[ -z ${pInputUnit} ]] && pInputUnit=$m

含义:如果变量 pInputUnit 的值为空,那么把变量m的值赋给 pInputUnit

11.[ -z "" ] && echo 0 || echo 1 

参考:
https://www.bbsmax.com/A/kmzLxZ9EzG/

12.> /dev/null  输出重定向

文件描述符:

类型

文件描述符

默认情况

对应文件句柄位置

标准输入(standard input)

0

从键盘获得输入

/proc/slef/fd/0

标准输出(standard output)

1

输出到屏幕(即控制台)

/proc/slef/fd/1

错误输出(error output)

2

输出到屏幕(即控制台)

/proc/slef/fd/2

实例:
执行下面的 who 命令,它将命令的完整的输出重定向在用户文件中(users):
$ who > users
执行后,并没有在终端输出信息,这是因为输出已被从默认的标准输出设备(终端)重定向到指定的文件。
$ echo "菜鸟教程:www.runoob.com" > users
$ cat users
菜鸟教程:www.runoob.com

13. 2>&1

这条命令用到了重定向绑定,采用&可以将两个输出绑定在一起。这条命令的作用是错误输出将和标准输出同用一个文件描述符,说人话就是错误输出将会和标准输出输出到同一个地方。

linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令,所以>/dev/null 2>&1的作用就是让标准输出重定向到/dev/null中(丢弃标准输出),然后错误输出由于重用了标准输出的描述符,所以错误输出也被定向到了/dev/null中,错误输出同样也被丢弃了。执行了这条命令之后,该条shell命令将不会输出任何信息到控制台,也不会有任何信息输出到文件中。

14.touch

Linux touch命令用于修改文件或者目录的时间属性,包括存取时间和更改时间。若文件不存在,系统会建立一个新的文件。

ls -l 可以显示档案的时间记录。

$ ls -l testfile  #查看文件的时间属性  
#原来文件的修改时间为16:09  
-rw-r--r-- 1 hdd hdd 55 2011-08-22 16:09 testfile  

$ touch testfile                #修改文件时间属性为当前系统时间  
$ ls -l testfile                #查看文件的时间属性  
#修改后文件的时间属性为当前系统时间  
-rw-r--r-- 1 hdd hdd 55 2011-08-22 19:53 testfile

15.nohup command &

# nohup java -jar xxxx.jar &
为了不让一些执行信息输出到前台(控制台),我们还会加上刚才提到的>/dev/null 2>&1命令来丢弃所有的输出:
# nohup java -jar xxxx.jar >/dev/null 2>&1 &

16.ps aux|grep XXX  (进程监控)

ps aux输出格式:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

17.awk '{print $2}'

实例:
$2:表示第二个字段
print $2 : 打印第二个字段
awk '{print $2}'  $fileName :   一行一行的读取指定的文件, 以空格作为分隔符,打印第二个字段
比如有这样一个文件
a1  b1  c1  d1
a2  b2  c2  d2
执行的结果是,输出
b1
b2

18.wc

wc [-clw][--help][--version][文件...]

-l或--lines 只显示行数。

 

小结

       1.此脚本可用于管理jar、class程序 的启动、停止、重启等行为,使用时需匹配参数

sh run.sh start|start-console|stop|restart|restart-console|status

       2.restart() 方法中,执行stop()后,sleep 30s是因我程序中有数据注册到了zk,等待失效。此部分可以删除。