zoukankan      html  css  js  c++  java
  • Shell脚本中的while getopts用法小结

    getpots是Shell命令行参数解析工具,旨在从Shell Script的命令行当中解析参数。getopts被Shell程序用来分析位置参数,option包含需要被识别的选项字符,如果这里的字符后面跟着一个冒号,表明该字符选项需要一个参数,其参数需要以空格分隔。冒号和问号不能被用作选项字符。getopts每次被调用时,它会将下一个选项字符放置到变量中,OPTARG则可以拿到参数值;如果option前面加冒号,则代表忽略错误

    命令格式:

    getopts optstring name [arg...]

    命令描述:
    optstring列出了对应的Shell Script可以识别的所有参数。比如:如果 Shell Script可以识别-a,-f以及-s参数,则optstring就是afs;如果对应的参数后面还跟随一个值,则在相应的optstring后面加冒号。比如,a:fs 表示a参数后面会有一个值出现,-a value的形式。另外,getopts执行匹配到a的时候,会把value存放在一个叫OPTARG的Shell Variable当中如果 optstring是以冒号开头的,命令行当中出现了optstring当中没有的参数将不会提示错误信息

    name表示的是参数的名称,每次执行getopts,会从命令行当中获取下一个参数,然后存放到name当中。如果获取到的参数不在optstring当中列出,则name的值被设置为?。命令行当中的所有参数都有一个index,第一个参数从1开始,依次类推。 另外有一个名为OPTIND的Shell Variable存放下一个要处理的参数的index。

    示例说明:
    1)在shell脚本中,对于简单的参数,常常会使用$1,$2,...,$n来处理即可,具体如下:

    [root@bobo tmp]# cat test.sh                      
    #!/bin/bash
    
    SYSCODE=$1
    APP_NAME=$2
    MODE_NAME=$3
    
    echo "${SYSCODE}下的${APP_NAME}分布在${MODE_NAME}里面"
    
    [root@bobo tmp]# sh test.sh caiwu reops kebank_uut
    caiwu下的reops分布在kebank_uut里面

    上面的例子中参数少还可以,但是如果脚本中使用的参数非常多的情况下,那使用上面这种方式就非常不合适,这样就无法清楚地记得每个位置对应的是什么参数!这个时候我们就可以使用bash内置的getopts工具了,用于解析shell脚本中的参数!下面就来看几个例子:

    2)getopts 示例一

    [root@bobo tmp]# cat test.sh
    #!/bin/bash
    
    func() {
        echo "Usage:"
        echo "test.sh [-j S_DIR] [-m D_DIR]"
        echo "Description:"
        echo "S_DIR,the path of source."
        echo "D_DIR,the path of destination."
        exit -1
    }
    
    upload="false"
    
    while getopts 'h:j:m:u' OPT; do
        case $OPT in
            j) S_DIR="$OPTARG";;
            m) D_DIR="$OPTARG";;
            u) upload="true";;
            h) func;;
            ?) func;;
        esac
    done
    
    echo $S_DIR
    echo $D_DIR
    echo $upload

     执行脚本

    [root@bobo tmp]# sh test.sh -j /data/usw/web -m /opt/data/web 
    /data/usw/web
    /opt/data/web
    false
    
    [root@bobo tmp]# sh test.sh -j /data/usw/web -m /opt/data/web -u
    /data/usw/web
    /opt/data/web
    true
    
    [root@bobo tmp]# sh test.sh -j /data/usw/web 
    /data/usw/web
    
    false
    
    [root@bobo tmp]# sh test.sh -m /opt/data/web                  
    
    /opt/data/web
    false
    
    [root@bobo tmp]# sh test.sh -h
    test.sh: option requires an argument -- h
    Usage:
    test.sh [-j S_DIR] [-m D_DIR]
    Description:
    S_DIR,the path of source.
    D_DIR,the path of destination.
    
    [root@bobo tmp]# sh test.sh j
    
    
    false
    
    [root@bobo tmp]# sh test.sh j m
    
    
    false

    getopts后面跟的字符串就是参数列表,每个字母代表一个选项,如果字母后面跟一个:,则就表示这个选项还会有一个值,比如上面例子中对应的-j /data/usw/web 和-m /opt/data/web 。而getopts字符串中没有跟随:的字母就是开关型选项,不需要指定值,等同于true/false,只要带上了这个参数就是true。

    getopts识别出各个选项之后,就可以配合case进行操作。操作中,有两个"常量",一个是OPTARG,用来获取当前选项的值;另外一个就是OPTIND,表示当前选项在参数列表中的位移。case的最后一项是?,用来识别非法的选项,进行相应的操作,我们的脚本中输出了帮助信息。

    3)getopts示例二:当选项参数识别完成以后,就能识别剩余的参数了,我们可以使用shift进行位移,抹去选项参数。

    [root@bobo tmp]# cat test.sh
    #!/bin/bash
     
    func() {
        echo "func:"
        echo "test.sh [-j S_DIR] [-m D_DIR]"
        echo "Description:"
        echo "S_DIR, the path of source."
        echo "D_DIR, the path of destination."
        exit -1
    }
     
    upload="false"
     
    echo $OPTIND
     
    while getopts 'j:m:u' OPT; do
        case $OPT in
            j) S_DIR="$OPTARG";;
            m) D_DIR="$OPTARG";;
            u) upload="true";;
            ?) func;;
        esac
    done
     
    echo $OPTIND
    shift $(($OPTIND - 1))
    echo $1

    执行脚本:

    [root@bobo tmp]# sh test.sh -j /data/usw/web beijing
    1              #执行的是第一个"echo $OPTIND"
    3              #执行的是第二个"echo $OPTIND"
    beijing        #此时$1是"beijing"
    
    [root@bobo tmp]# sh test.sh -m /opt/data/web beijing                 
    1              #执行的是第一个"echo $OPTIND"
    3              #执行的是第二个"echo $OPTIND"
    beijing
    
    [root@bobo tmp]# sh test.sh -j /data/usw/web -m /opt/data/web beijing
    1              #执行的是第一个"echo $OPTIND"
    5              #执行的是第二个"echo $OPTIND"
    beijing
    
                      参数位置: 1        2       3       4        5     6
    [root@bobo tmp]# sh test.sh -j /data/usw/web -m /opt/data/web -u beijing
    6
    beijing

    在上面的脚本中,我们位移的长度等于case循环结束后的OPTIND - 1,OPTIND的初始值为1。当选项参数处理结束后,其指向剩余参数的第一个。getopts在处理参数时,处理带值的选项参数,OPTIND加2处理开关型变量时,OPTIND则加1

    如上执行的脚本:1)第一个脚本执行,-j的参数位置为1,由于-j后面带有参数,即处理带值选项参数,所以其OPTIND为1+2=3;2)第二个脚本执行,-m参数位置为1,由于其后带有参数,所以其OPTIND也为1+2=3;3)第三个脚本执行,-m的参数位置 (观察最后一个参数的位置) 为3,由于其后面带有参数,所以其OPTIND为3+2=5;4)第四个脚本执行,-u参数位置为5,由于其后面不带参数,即为处理开关型变量,所以其OPTIND为5+1=6。

                                                                                                                        
    shift参数的使用
    很多脚本执行的时候我们并不知道后面参数的个数,但可以使用$*来获取所有参数。但在程序处理的过程中有时需要逐个的将$1、$2、$3……$n进行处理。shift是shell中的内部命令,用于处理参数位置。每次调用shift时,它将所有位置上的参数减一。 $2变成了$1, $3变成了$2, $4变成了$3。shift命令的作用就是在执行完$1后,将$2变为$1,$3变为$2,依次类推。

    示例一:
    [root@bobo tmp]# cat test.sh 
    #!/bin/bash
     
    until [ $# -eq 0 ]
    do
        echo "第一个参数为: $1 参数个数为: $#"
        shift
    done  
    
    [root@bobo tmp]# sh test.sh 10 11 12 13 14 15
    第一个参数为: 10 参数个数为: 6
    第一个参数为: 11 参数个数为: 5
    第一个参数为: 12 参数个数为: 4
    第一个参数为: 13 参数个数为: 3
    第一个参数为: 14 参数个数为: 2
    第一个参数为: 15 参数个数为: 1
    
    示例二:
    [root@bobo tmp]# cat test.sh                 
    #!/bin/bash
    
    until [ -z "$1" ]  # Until all parameters used up
    do
      echo "$@ "
      shift
    done
    
    [root@bobo tmp]# sh test.sh 10 11 12 13 14 15
    10 11 12 13 14 15 
    11 12 13 14 15 
    12 13 14 15 
    13 14 15 
    14 15 
    15

    4)getopts示例三

    [root@bobo tmp]# cat test.sh
    #!/bin/bash
    
    echo $*
    while getopts ":a:bc:" opt
    do
        case $opt in
          a)
          echo $OPTARG
          echo $OPTIND
          ;;
          b)
          echo "b $OPTIND"
          ;;
          c)
          echo "c $OPTIND"
          ;;
          ?)
          echo "error"
          exit 1
        esac
    done
    
    echo $OPTIND
    shift $(( $OPTIND-1 ))
    echo $0
    echo $*
    
    [root@bobo tmp]# sh test.sh -a beijing -b -c shanghai
    -a beijing -b -c shanghai           #执行的是第一个"echo $*",即打印"传递给脚本的所有参数的列表"
    beijing                             #执行的是"echo $OPTARG", OPTARG表示存储相应选项的参数,这里指-a的参数"beijing"
    3                                   #-a参数位置为1,是处理带值选项参数,即-a参数的OPTIND为1+2=3
    b 4                                 #-b参数位置为3,是处理开关型变量(即后面没有跟参数),即-b参数的OPTIND为3+1=4
    c 6                        #-c参数位置为4,是处理带值选项参数,即-a参数的OPTIND为4+2=3
    6                          #执行的是"echo $OPTIND",此时打印的是脚本执行的最后一个参数(即-c)的OPTIND的index索引值。
    test.sh                    #执行的是"echo $0",即打印脚本名称。$0是脚本本身的名字;
                               #执行的是最后一个"echo $*",即打印"传递给脚本的所有参数的列表"。由于前面执行了shift $(( $OPTIND-1 )),即每执行一步,位置参数减1,所以到最后$*就为零了。
    [root@bobo tmp]# 
    

    5)getopts示例四

    [root@bobo tmp]# cat test.sh
    #!/bin/bash
    # getopts-test.sh
    
    while getopts :d:s ha
    do
      case "$ha" in
          d)
            echo "d option value is $OPTARG"
            echo "d option index is $(($OPTIND-1))"
            ;;
          s)
            echo "s option..."
            echo "s option index is $(($OPTIND-1))"
            ;;
          [?])
            print "Usage: $0 [-s] [-d value] file ..."
            exit 1
            ;;
      esac
    done
    
    执行脚本:
    [root@bobo tmp]# sh test.sh -d 100 -s 
    d option value is 100                  #打印的是对应选项的参数,即-d的参数值
    d option index is 2                    #-d参数位置为1,是处理带值选项参数,即-d参数的OPTIND为1+2=3。所以$(($OPTIND-1))为2
    s option...
    s option index is 3                    #-s参数位置为3,是处理带值选项参数,即-s参数的OPTIND为3+1=4。所以$(($OPTIND-1))为2
    
    ==================================================================================
    [root@bobo tmp]# cat test.sh
    #!/bin/bash
    while getopts :ab:c: OPTION;do              #ab参数前面的:表示忽略错误
        case $OPTION in
          a)echo "get option a"
          ;;
          b)echo "get option b and parameter is $OPTARG"
          ;;
          c)echo "get option c and parameter is $OPTARG"
          ;;
          ?)echo "get a non option $OPTARG and OPTION is $OPTION"
          ;;
        esac
    done
    
    [root@bobo tmp]# sh test.sh -a haha 
    get option a
    
    [root@bobo tmp]# sh test.sh -b hehe
    get option b and parameter is hehe
    
    [root@bobo tmp]# sh test.sh -a haha -b hehe     #由于getopts解析时ab参数在一起,-a和-b都跟参数时,-a在前面执行后,-b参数就不会执行了。
    get option a
    
    [root@bobo tmp]# sh test.sh -b haha -a hehe     #将-b参数放在前面执行,-a参数放在后面执行,两个参数就都可以执行了。
    get option b and parameter is haha
    get option a
    
    [root@bobo tmp]# sh test.sh -ab hehe       
    get option a
    get option b and parameter is hehe
    
    [root@bobo tmp]# sh test.sh -ab hehe -c heihei
    get option a
    get option b and parameter is hehe
    get option c and parameter is heihei
    
    [root@bobo tmp]# sh test.sh -ab hehe -c heihei -u liu
    get option a
    get option b and parameter is hehe
    get option c and parameter is heihei
    get a non option u and OPTION is ?
    
    ================================================================================
    稍微修改下脚本,将abc参数放在一起
    [root@bobo tmp]# cat test.sh 
    #!/bin/bash
    while getopts :abc: OPTION;do          
        case $OPTION in
          a)echo "get option a"
          ;;
          b)echo "get option b and parameter is $OPTARG"
          ;;
          c)echo "get option c and parameter is $OPTARG"
          ;;
          ?)echo "get a non option $OPTARG and OPTION is $OPTION"
          ;;
        esac
    done
    
    [root@bobo tmp]# sh test.sh -a haha
    get option a
    [root@bobo tmp]# sh test.sh -a haha -b hehe
    get option a
    [root@bobo tmp]# sh test.sh -a haha -c heihei         
    get option a
    [root@bobo tmp]# sh test.sh -a haha -b hehe -c heihei
    get option a
    [root@bobo tmp]# sh test.sh -a haha -c hehe -b heihei
    get option a
    
    [root@bobo tmp]# sh test.sh -b hehe
    get option b and parameter is 
    [root@bobo tmp]# sh test.sh -b haha -a hehe 
    get option b and parameter is 
    [root@bobo tmp]# sh test.sh -b haha -c hehe
    get option b and parameter is 
    [root@bobo tmp]# sh test.sh -b haha -a hehe -c heihei
    get option b and parameter is 
    [root@bobo tmp]# sh test.sh -b haha -c hehe -a heihei
    get option b and parameter is 
    
    [root@bobo tmp]# sh test.sh -c haha
    get option c and parameter is haha
    [root@bobo tmp]# sh test.sh -c haha -a hehe
    get option c and parameter is haha
    get option a
    [root@bobo tmp]# sh test.sh -c haha -b heihei
    get option c and parameter is haha
    get option b and parameter is 
    [root@bobo tmp]# sh test.sh -c haha -a hehe -b heihei
    get option c and parameter is haha
    get option a
    [root@bobo tmp]# sh test.sh -c haha -b hehe -c heihei 
    get option c and parameter is haha
    get option b and parameter is 
    
    [root@bobo tmp]# sh test.sh -abc hehe
    get option a
    get option b and parameter is 
    get option c and parameter is hehe
    

    6)下面看一个zookeeper集群环境一键安装脚本(用到了getopts),生产环境中可以使用该脚本。

    [root@bobo zookeeper]# cat install_zookeeper.sh
    #!/bin/bash
     
    source /etc/profile
    java -version
    if [ "$?" -ne 0 ]; then
      echo "JDK未安装,请先安装JDK"
      exit 1
    fi
    while getopts "a:b:n:l:c:f:m:h" opts
    do
            case $opts in
                    a)
                            #APP_NAME:项目编码
                            APP_NAME=$OPTARG
                            ;;
                    b)
                            #MODULE_NAME:模块名称
                            MODULE_NAME=$OPTARG
                            ;;
                    n)
                            #ZK_SRVNUM:ZOOKEEPER数量
                            ZK_SRVNUM=$OPTARG
                            ;;
                    l)
                            #ZK_IPLIST:ZOOKEEPER服务器IP地址列表
                            ZK_IPLIST=$OPTARG
                            ;;
                    c)
                            #ZKCLIENT_PORT:客户端访问 zookeeper 的端口号
                            ZKCLIENT_PORT=$OPTARG
                            ;;
                    f)
                            #ZKLEADER_PORT:ZOOKEEPER的F和L通信端口号
                            ZKLEADER_PORT=$OPTARG
                            ;;
                    m)
                            #ZKCOM_PORT:ZOOKEEPER选举端口号
                            ZKCOM_PORT=$OPTARG
                            ;;
                    h)
                            echo -e "OPTIONS:
    -a:项目编码(必选)
    -b:模块名称(可选,默认为空)
    -n:ZooKeeper服务器数量(可选,默认为3)"
                            echo -e "-l:ZooKeeper服务器IP地址列表(必选,IP地址以英文逗号分隔)"
                            echo -e "-c:Client-Port(可选,默认为2181,多个端口以英文逗号分隔,且与IP地址一一对应)"
                            echo -e "-f:ZooKeeper的F和L通信端口号(可选,默认为2888,多个端口以英文逗号分隔,且与IP地址一一对应)"
                            echo -e "-m:ZooKeeper选举端口号(可选,默认为3888,多个端口以英文逗号分隔,且与IP地址一一对应)"
                            exit 1
                            ;;
                    ?)
                            echo "missing  options,pls check!"
                            exit 1
                            ;;
            esac
    done
    #可选参数赋值
    ZK_SRVNUM=${ZK_SRVNUM:-3}
    ZKCLIENT_PORT=${ZKCLIENT_PORT:-2181}
    ZKLEADER_PORT=${ZKLEADER_PORT:-2888}
    ZKCOM_PORT=${ZKCOM_PORT:-3888}
    #定义公共变量
    #zookeep安装包存放位置
    ZKSAVDIR="/usr/local/src/zookeeper"
    #zookeeper安装包名(不带扩展名)
    ZKNAME="zookeeper-3.4.8"
    #必选参数存在性及参数合法性判断
    #if [ -z ${APP_NAME} ]||[ -z ${MODULE_NAME} ]||[ -z ${ZK_IPLIST} ];then
    if [ -z ${APP_NAME} ]||[ -z ${ZK_IPLIST} ];then
            echo "Missing options,exit"
            exit 1
    elif [ ${ZK_SRVNUM} -ne 1 ]&&[ ${ZK_SRVNUM} -ne 3 ]&&[ ${ZK_SRVNUM} -ne 5 ];then
            echo "Wrong server num,exit"
            exit 1
    fi
    IPLIST_NUM=`echo ${ZK_IPLIST}|awk -F"," '{print NF}'`
    if [ ${ZK_SRVNUM} -ne ${IPLIST_NUM} ];then
            echo "IP list and server num do not match,exit"
            exit 1
    fi
    APP_NAME=`echo ${APP_NAME} | tr '[A-Z]' '[a-z]'`
    #多个端口时判断端口数与IP地址数量是否一致
    CPORT_NUM=`echo ${ZKCLIENT_PORT}|awk -F"," '{print NF}'`
    LPORT_NUM=`echo ${ZKLEADER_PORT}|awk -F"," '{print NF}'`
    EPORT_NUM=`echo ${ZKCOM_PORT}|awk -F"," '{print NF}'`
    if [ ${CPORT_NUM} -gt 1 ];then
            if [ ${IPLIST_NUM} -ne ${CPORT_NUM} ]||[ ${IPLIST_NUM} -ne ${LPORT_NUM} ]||[ ${IPLIST_NUM} -ne ${EPORT_NUM} ];then
                    echo "IP list and Port list number do not match,exit"
                    exit 1
            fi
    #获取IP地址和端口对应关系
            rm -f /home/workapp/zkinfo.cfg
            for ((i=1;i<=${ZK_SRVNUM};i++)); do
                    eval IP_$i='`echo ${ZK_IPLIST}|awk -F, "{ print $"$i" }"`'
                    eval PORT_$i='`echo ${ZKCLIENT_PORT}|awk -F, "{ print $"$i" }"`'
                    eval LPORT_$i='`echo ${ZKLEADER_PORT}|awk -F, "{ print $"$i" }"`'
                    eval EPORT_$i='`echo ${ZKCOM_PORT}|awk -F, "{ print $"$i" }"`'
    #               eval echo "server.${i}=$IP_$i:${ZKLEADER_PORT}:${ZKCOM_PORT}">>${ZKHOME}/conf/zoo.cfg
    #               eval IPTMP=$IP_$i
                    eval PORTTMP=$PORT_$i
    #zookeeper HOME路径
                    [ -z ${MODULE_NAME} ]&&eval ZKHOME="/opt/${APP_NAME}/zookeeper_$PORT_$i"||eval ZKHOME="/opt/${APP_NAME}/zookeeper_${MODULE_NAME}_$PORT_$i"
    #zookeeper日志存储路径
                    [ -z ${MODULE_NAME} ]&&eval DATA_LOGDIR="/var/log/${APP_NAME}/zookeeper_$PORT_$i"||eval DATA_LOGDIR="/var/log/${APP_NAME}/zookeeper_${MODULE_NAME}_$PORT_$i"
    #zookeeper数据存储路径
                    DATA_DIR="${ZKHOME}/data"
    #生成参数列表
                    eval echo "$i,$IP_$i,$PORT_$i,$LPORT_$i,$EPORT_$i,${ZKHOME},${DATA_LOGDIR},${DATA_DIR}">>/home/workapp/zkinfo.cfg
            done
            cat /home/workapp/zkinfo.cfg
    else
    #zookeeper HOME路径
            [ -z ${MODULE_NAME} ]&&ZKHOME="/opt/${APP_NAME}/zookeeper"||ZKHOME="/opt/${APP_NAME}/zookeeper_${MODULE_NAME}"
            echo "ZKHOME is ${ZKHOME}"
    #zookeeper日志存储路径
            [ -z ${MODULE_NAME} ]&&DATA_LOGDIR="/var/log/${APP_NAME}/zookeeper"||DATA_LOGDIR="/var/log/${APP_NAME}/zookeeper_${MODULE_NAME}"
            echo "ZK log dir is ${DATA_LOGDIR}"
    #zookeeper数据存储路径
            DATA_DIR="${ZKHOME}/data"
            echo "ZK data dir is ${DATA_DIR}"
    fi
    #安装日志
    INSTALL_LOG="/home/workapp/zookeeperinstall.log"
    #打印变量值
    echo "APP_NAME is ${APP_NAME}"|tee -a ${INSTALL_LOG}
    echo "MODULE_NAME is ${MODULE_NAME}"|tee -a ${INSTALL_LOG}
    echo "ZK_Server_num is ${ZK_SRVNUM}"|tee -a ${INSTALL_LOG}
    echo "ZK_Server IP is ${ZK_IPLIST}"|tee -a ${INSTALL_LOG}
    echo "ZK_Client Port is ${ZKCLIENT_PORT}"|tee -a ${INSTALL_LOG}
    echo "ZK_Leader Port is $ZKLEADER_PORT"|tee -a ${INSTALL_LOG}
    echo "ZK_COM Port is ${ZKCOM_PORT}"|tee -a ${INSTALL_LOG}
    #获取本机IP地址
    HOST_IP=`ip a|grep global|awk '{print $2}'|awk -F"/" '{print $1}'`
    echo "Local IP is ${HOST_IP}"|tee -a ${INSTALL_LOG}
    #安装包MD5校验
    md5Now=`md5sum ${ZKSAVDIR}/${ZKNAME}.tar.gz|awk '{print $1}'`
    md5Save=`cat ${ZKSAVDIR}/${ZKNAME}.tar.gz.md5`
    if [ "${md5Now}" != "${md5Save}" ];then
        echo "MD5 check Failed!"|tee -a ${INSTALL_LOG}
        echo "the md5 now is ${md5Now}"|tee -a ${INSTALL_LOG}
        echo "the md5 saved is ${md5Save}"|tee -a ${INSTALL_LOG}
        exit 1
    else
        echo "MD5 check success!"|tee -a ${INSTALL_LOG}
    fi
    #安装zookeeper
    function Install_zk {
            echo "=================`date '+%Y%m%d %H:%M:%S'`Start Install ZooKeeper....==============="|tee -a ${INSTALL_LOG}
            #解压缩安装包至项目编码安装路径
            if [ ! -e /opt/${APP_NAME}/ ]; then
                    mkdir -p /opt/${APP_NAME}
            fi
            tar -xzf ${ZKSAVDIR}/${ZKNAME}.tar.gz -C /opt/${APP_NAME}/
            mv /opt/${APP_NAME}/${ZKNAME} ${ZKHOME}
            mkdir -p ${DATA_DIR}
            mkdir -p ${DATA_LOGDIR}
            cp ${ZKHOME}/conf/zoo_sample.cfg ${ZKHOME}/conf/zoo.cfg
            #客户化zoo.cfg配置
            sed -i "s/clientPort=2181/clientPort=${ZKCLIENT_PORT}/g" ${ZKHOME}/conf/zoo.cfg
            sed -i "s#dataDir=/tmp/zookeeper#dataDir=${DATA_DIR}#g" ${ZKHOME}/conf/zoo.cfg
            sed -i "/dataLogDir/s/^/#/" ${ZKHOME}/conf/zoo.cfg
            echo "dataLogDir=${DATA_LOGDIR}" >>${ZKHOME}/conf/zoo.cfg
            #修改zookeeper-env.sh,指定运行日志zookeeper.log路径
            sed -i "s#/var/log/zookeeper#${DATA_LOGDIR}#g" ${ZKHOME}/conf/zookeeper-env.sh
            #修改java.env,设置jvm参数,指定gc日志路径
            sed -i "s#/var/log/zookeeper#${DATA_LOGDIR}#g" ${ZKHOME}/conf/java.env
    #服务器数量为3个或5个为集群模式
            if [ ${ZK_SRVNUM} -eq 3 ]||[ ${ZK_SRVNUM} -eq 5 ];then
    #根据端口数量判断安装方式
                    if [ ${CPORT_NUM} -eq 1 ];then
    #拆分IP地址列表,获取本机ZK_ID
                            for ((i=1;i<=${ZK_SRVNUM};i++));do
                                    eval IP_$i='`echo ${ZK_IPLIST}|awk -F, "{ print $"$i" }"`'
    #                       eval echo $IP_$i
                                    eval IPTMP=$IP_$i
                                    eval echo "server.${i}=$IP_$i:${ZKLEADER_PORT}:${ZKCOM_PORT}">>${ZKHOME}/conf/zoo.cfg
                                    if [ "$HOST_IP" == "$IPTMP" ];then
    #当列表中的IP地址等于本机地址时,获取当前i值作为ID
                                            ZK_ID=${i}
                                    else
                                            continue
                                    fi
                            done
                    else
                                    ZK_ID=${NUM}
                                    while read ZK_INFO;do
                                             echo ${ZK_INFO}|awk -F, '{print "server."$1"="$2":"$4":"$5}'>>${ZKHOME}/conf/zoo.cfg
                                    done</home/workapp/zkinfo.cfg
                    fi
            #客户化myid
                    echo "${ZK_ID}" >${DATA_DIR}/myid
                    echo "zookeeper ID is ${ZK_ID}"|tee -a ${INSTALL_LOG}
            fi
            chown -R workapp:workapp ${ZKHOME}
            chown -R workapp:workapp ${DATA_LOGDIR}
            cat ${ZKHOME}/conf/zoo.cfg
    }
    function Check_install {
            retval=$?
            if [ $retval -eq 0 ];then
                    echo "`date '+%Y%m%d %H:%M:%S'` zookeeper install SUCCESS!|${APP_NAME} ${MODULE_NAME} ${HOST_IP} ${ZKCLIENT_PORT} ${ZK_ID}|0"|tee -a ${INSTALL_LOG}
            else
                    echo "`date '+%Y%m%d %H:%M:%S'` zookeeper install FAILED!|${APP_NAME} ${MODULE_NAME} ${HOST_IP} ${ZKCLIENT_PORT} ${ZK_ID}|1"|tee -a ${INSTALL_LOG}
            fi
    }
    function Start_check {
            su - workapp -c "sh ${ZKHOME}/bin/zkServer.sh start"
            sleep 10
            su - workapp -c "sh ${ZKHOME}/bin/zkServer.sh status"
            netstat -anp|grep ${ZKCLIENT_PORT}
    }
    #根据端口数量判断安装方式,1个端口为standalone或集群模式,正常安装;
    if [ ${CPORT_NUM} -eq 1 ];then
            Install_zk
            Check_install
            Start_check
    else
    #多个端口为伪集群模式,读取zkinfo.cfg文件
            while read ZK_INFO;do
                    NUM=`echo ${ZK_INFO}|awk -F, '{print $1}'`
                    IP=`echo ${ZK_INFO}|awk -F, '{print $2}'`
                    ZKCLIENT_PORT=`echo ${ZK_INFO}|awk -F, '{print $3}'`
                    ZKHOME=`echo ${ZK_INFO}|awk -F, '{print $6}'`
                    DATA_LOGDIR=`echo ${ZK_INFO}|awk -F, '{print $7}'`
                    DATA_DIR=`echo ${ZK_INFO}|awk -F, '{print $8}'`
                    if [ "$IP" == "$HOST_IP" ];then
                            Install_zk
                            Check_install
                            Start_check
                    else
                            continue
                    fi
            done</home/workapp/zkinfo.cfg
    fi
    rm -f /home/workapp/zkinfo.cfg

    查看脚本帮助信息:

    install_zookeeper.sh脚本用于一键安装zookeeper,支持单实例部署或者3台/5台服务器集群
    
    执行方式:
    bash install_zookeeper.sh -a [option] [-b option] -l [option] [-n option] [-c option] [-f option] [-m option]
    
    参数说明:
    通过"bash install_zookeeper.sh -h" 命令可以显示参数说明
    OPTIONS:
    -a:项目编码(必选)
    -b:模块名称(可选,默认为空)
    -n:ZooKeeper服务器数量(可选,默认为3)
    -l:ZooKeeper服务器IP地址列表(必选,格式为以英文逗号[,]分隔的IP地址,如为standalone模式,填写一个IP地址,如为伪集群模式,需填写三个IP地址且与端口号一一对应)
    -c:Client-Port(可选,默认为2181,如有多个端口,需与IP地址列表一一对应,格式为以英文逗号[,]分隔)
    -f:ZooKeeper的Follower和Leader间通信端口号(可选,默认为2888,如有多个端口,需与IP地址列表一一对应,格式为以英文逗号[,]分隔)
    -m:ZooKeeper选举端口号(可选,默认为3888,如有多个端口,需与IP地址列表一一对应,格式为以英文逗号[,]分隔)
    
    ================================================================================================
    [root@bobo zookeeper]# bash install_zookeeper.sh -h 
    java version "1.8.0_51"
    Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
    Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)
    OPTIONS:
    -a:项目编码(必选)
    -b:模块名称(可选,默认为空)
    -n:ZooKeeper服务器数量(可选,默认为3)
    -l:ZooKeeper服务器IP地址列表(必选,IP地址以英文逗号分隔)
    -c:Client-Port(可选,默认为2181,多个端口以英文逗号分隔,且与IP地址一一对应)
    -f:ZooKeeper的F和L通信端口号(可选,默认为2888,多个端口以英文逗号分隔,且与IP地址一一对应)
    -m:ZooKeeper选举端口号(可选,默认为3888,多个端口以英文逗号分隔,且与IP地址一一对应)

    举例说明(可以通过该脚本部署如下四个场景的zookeeper服务环境,安装后zookeeper服务默认启动)

    [root@bobo zookeeper]# pwd
    /usr/local/src/zookeeper
    [root@bobo zookeeper]# ll
    total 21760
    -rwxr-xr-x 1 root root    10711 Nov 13 16:45 install_zookeeper.sh
    -rw-r--r-- 1 root root 22264081 Jun 12 15:44 zookeeper-3.4.8.tar.gz
    -rw-r--r-- 1 root root       33 Nov 13 16:46 zookeeper-3.4.8.tar.gz.md5
        
    [root@bobo zookeeper]# md5sum zookeeper-3.4.8.tar.gz
    81adbad1f9f2f3c1061f19c26bff9ce4  zookeeper-3.4.8.tar.gz
      
    [root@bobo zookeeper]# cat zookeeper-3.4.8.tar.gz.md5
    81adbad1f9f2f3c1061f19c26bff9ce4
        
    该脚本执行的前提是:
    1. 脚本中已经定义了zookeep安装包存放位置和安装包名,这些要提前准备好
    #zookeep安装包存放位置
    ZKSAVDIR="/usr/local/src/zookeeper"
    #zookeeper安装包名(不带扩展名)
    ZKNAME="zookeeper-3.4.8"
       
    zookeeper的安装包要和部署脚本在同一个目录路径下(比如这里都放在脚本定义的/usr/local/src/zookeeper目录下)
    检查zookeeper的tar包的md5值,这里是zookeeper-3.4.8.tar.gz.md5
       
    2. webapp用户要存在(这个可以根据自己机器的实际情况进行修改)
       
        
    ======================================================================================================================
    举例如下:
        
    1)在172.16.60.210,172.16.60.211,172.16.60.212 三台服务器上为项目编码为test的应用安装zookeeper,端口默认。(三台机器上都执行下面命令)
    [root@bobo zookeeper]# bash install_zookeeper.sh -a test -l "172.16.60.210,172.16.60.211,172.16.60.212"
         
    2)在172.16.60.210,172.16.60.211,172.16.60.212,172.16.60.213,172.16.60.214五台服务器上为项目编码为ketest的kemodu模块安装zookeeper,Client端口为3000。(五台机器上都执行下面命令)
    [root@bobo zookeeper]# bash install_zookeeper.sh -a ketest -b kemodu -n 5 -l "172.16.60.210,172.16.60.211,172.16.60.212,172.16.60.213,172.16.60.214" -c 3000
         
    3)在172.16.60.210上为项目编码为test的应用安装zookeeper,模式为standalone,端口为22281。(172.16.60.210机器上执行下面命令)
    [root@bobo zookeeper]# bash install_zookeeper.sh -a test -n 1 -l "172.16.60.210" -c 22281
         
    4)在172.16.60.210上为项目编码为test的应用安装zookeeper伪集群,客户端口为2181,2281,2381, 通信端口为2188,2288,2388,选举端口为3181,3281,3381。(172.16.60.210机器上执行下面命令)
    [root@bobo zookeeper]# bash install_zookeeper.sh -a test -n 3 -l "172.16.60.210,172.16.60.210,172.16.60.210" -c"2181,2281,2381" -f "2188,2288,2388" -m "3181,3281,3381"
         
    =======================================================================================================================
      
    注意:
    1. 在单台机器上部署伪静态集群时,参数要写全,即-a、-n、-l、-c、-f、-m都要在命令中写上,否则会报错如下:
    "IP list and server num do not match,exit"!!
      
    2. 如果部署后发现zookeeper服务没有起来,可以查看日志,日志路径在zoo.cfg文件里配置。如下:
      
    [root@bobo conf]# cat zoo.cfg |grep dataLogDir
    dataLogDir=/var/log/test/zookeeper_2181
      
    [root@bobo conf]# cat /var/log/test/zookeeper_2181/zookeeper.out
    Unrecognized VM option 'MetaspaceSize=256m'
    Could not create the Java virtual machine.
      
    有上面日志可以看出,zookeeper一键安装后,服务没有起来的原因是:jdk版本问题
    将当前jdk版本调整到jdk1.8即可!
      
    解决办法:
    [root@bobo conf]# java -version
    java version "1.6.0_41"
    OpenJDK Runtime Environment (IcedTea6 1.13.13) (rhel-1.13.13.1.el7_3-x86_64)
    OpenJDK 64-Bit Server VM (build 23.41-b41, mixed mode)
      
    [root@bobo conf]# rpm -qa|grep jdk
    java-1.6.0-openjdk-1.6.0.41-1.13.13.1.el7_3.x86_64
    java-1.6.0-openjdk-demo-1.6.0.41-1.13.13.1.el7_3.x86_64
    java-1.6.0-openjdk-devel-1.6.0.41-1.13.13.1.el7_3.x86_64
    java-1.6.0-openjdk-javadoc-1.6.0.41-1.13.13.1.el7_3.x86_64
    java-1.6.0-openjdk-src-1.6.0.41-1.13.13.1.el7_3.x86_64
      
    [root@bobo conf]# yum -y remove java-1.6.0-openjdk*
    [root@bobo conf]# yum -y remove tzdata-java.noarch
      
    [root@bobo conf]# java -version
    -bash: /usr/bin/java: No such file or directory
      
    [root@bobo conf]# yum -y install java-1.8.0-openjdk*
      
    [root@bobo conf]# java -version
    openjdk version "1.8.0_232"
    OpenJDK Runtime Environment (build 1.8.0_232-b09)
    OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode)
      
    再次启动zookeeper服务就OK了!
    

    #########################  while getopts 脚本示例  #########################

    需求:开发脚本,实现某些自动化操作。
    
    1)脚本1
    [root@VM_16_9_centos ~]# cat test1.sh
    #!/bin/bash
    
    while getopts "n:i:p:" opts
    do
        case $opts in
            n)
                  #节点数量
                  NODE_NUM=$OPTARG
                  ;;
            i)
                  #节点ip列表
                  NODE_IP_LIST=$OPTARG
                  ;;
            p)
                  #节点端口列表
                  NODE_PORT_LIST=$OPTARG
                  ;;
            ?)    #unknown args?
                  echo "unkonw argument"
                  exit 1
                  ;;
        esac
    done
    
    #获取IP地址和端口对应关系(一对一的关系)
    #下面的NODE_IP和NODE_PORT变量定义时,前面必须使用eval命令!!
    for ((i=1;i<=${NODE_NUM};i++)); do
        eval NODE_IP='`echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`'
        eval NODE_PORT='`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`'
    
        echo "http://ke_beta.pro.com/gateway/servty?/address=${NODE_IP}:${NODE_PORT}"
    done
    
    执行脚本 (ip和port是一一对应的关系):
    [root@VM_16_9_centos ~]# sh test1.sh -n 4 -i "172.16.60.10,172.16.60.11,172.16.60.12,172.16.60.13" -p "8080,8080,8088,8099"                  
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.10:8080
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.11:8080
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.12:8088
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.13:8099
    
    [root@VM_16_9_centos ~]# sh test1.sh -n 1 -i "172.16.60.17" -p "8989"
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.17:8989
    
    ######################################################## 这里需要注意 ##############################################################
    上面脚本中的 while getopts 后面的 "n:i:p:" 字符配置里的p参数后面必须要跟上:冒号,表明该字符选项需要一个参数!否则在脚本执行中-p传入的参数则无效!
    ##################################################################################################################################
    
    比如将脚本中的 while getopts "n:i:p:" opts 改成 while getopts "n:i:p" opts,则执行脚本如下,发现 -p传入的参数无效!!
    [root@VM_16_9_centos ~]# sh test1.sh -n 4 -i "172.16.60.10,172.16.60.11,172.16.60.12,172.16.60.13" -p "8080,8080,8088,8099"
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.10:
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.11:
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.12:
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.13:
    
    2)脚本2
    可以对脚本1进行改造,将节点数量的-n参数去掉
    [root@VM_16_9_centos ~]# cat test2.sh
    #!/bin/bash
    
    while getopts "i:p:" opts
    do
        case $opts in
            i)
                  #节点ip列表
                  NODE_IP_LIST=$OPTARG
                  ;;
            p)
                  #节点端口列表
                  NODE_PORT_LIST=$OPTARG
                  ;;
            ?)    #unknown args?
                  echo "unkonw argument"
                  exit 1
                  ;;
        esac
    done
    
    #获取IP地址和端口对应关系
    #节点数量
    #下面的NODE_IP和NODE_PORT变量定义时,前面必须使用eval命令!!
    NODE_NUM=`echo ${NODE_IP_LIST}|awk -F"," '{print NF}'`
    for ((i=1;i<=${NODE_NUM};i++)); do
        eval NODE_IP='`echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`'
        eval NODE_PORT='`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`'
    
        echo "http://ke_beta.pro.com/gateway/servty?/address=${NODE_IP}:${NODE_PORT}"
    done
    
    执行脚本 (ip和port是一一对应的关系):
    [root@VM_16_9_centos ~]# sh test2.sh -i "172.16.60.10,172.16.60.11,172.16.60.12,172.16.60.13" -p "8080,8080,8088,8099"
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.10:8080
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.11:8080
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.12:8088
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.13:8099
    
    [root@VM_16_9_centos ~]# sh test2.sh -i "172.16.60.18" -p "9999"                                                       
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.18:9999
    
    3) 脚本3
    不需要像上面两个脚本实现的那样:传入ip列表和port列表,然后一一对应起来。只需要将传入的ip:port参数作为一个整体参数。
    [root@VM_16_9_centos ~]# cat test3.sh
    #!/bin/bash
    
    Parameter=$1
    #Parameter=($1) #这里$1是一个整体参数,使用($1)数组形式也可以,数组里只有一个参数。
    
    #将传入的$1参数中的逗号变为空格,变成多个小参数赋予IP_PORT参数
    for IP_PORT in $(echo "${Parameter}"|sed 's/,/ /g')
    do
      echo "http://ke_beta.pro.com/gateway/servty?/address=${IP_PORT}"
    done
    
    执行脚本 (传入的多个ip:port之间使用逗号隔开,就是一个整体参数,即$1):
    [root@VM_16_9_centos ~]# sh test3.sh 172.16.60.10:8080,172.16.60.11:8080,172.16.60.12:8088,172.16.60.13:8099
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.10:8080
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.11:8080
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.12:8088
    http://ke_beta.pro.com/gateway/servty?/address=172.16.60.13:8099
  • 相关阅读:
    什么?Spring Boot CommandLineRunner 有坑!?
    关于 websocket 跨域的一个奇怪问题…
    电商金额计算的 4 个坑,千万注意了!
    微服务模块划分原则和接口定义原则
    tcp的三次握手(连接)与四次挥手(断开)
    二叉树遍历及算法实现
    elasticsearch搜索 倒排索引
    kubernetes落地-传统web服务迁移
    Docker核心技术-容器管理
    Docker核心技术-镜像管理
  • 原文地址:https://www.cnblogs.com/kevingrace/p/11753294.html
Copyright © 2011-2022 走看看