• (转)Shell脚本中的while getopts用法小结


    原文:https://www.cnblogs.com/kevingrace/p/11753294.html

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

    命令格式:

    1
    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来处理即可,具体如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [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 示例一

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    [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

     执行脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    [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进行位移,抹去选项参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [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

    执行脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    [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,依次类推。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    示例一:
    [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示例三

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    [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示例四

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    [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),生产环境中可以使用该脚本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    [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:\n-a:项目编码(必选)\n-b:模块名称(可选,默认为空)\n-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

    查看脚本帮助信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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服务默认启动)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    [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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    需求:开发脚本,实现某些自动化操作。
     
    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
    *************** 当你发现自己的才华撑不起野心时,就请安静下来学习吧!***************
  • 相关阅读:
    Python数据结构-链表
    面试题4:替换空格
    面试题3:二维数组中的查找
    2019.12.17基佬出的一道题
    2019.12.17霍格沃兹测试学院一道题
    python_ck01(虚拟环境管理)
    api_DZFPKJ & api_DZFPCX(get_AES_url代码优化)
    cwyth(自动核销代码)
    api_DZFPKJ & api_DZFPCX
    字符串返回数组并排序(算法题)
  • 原文地址:https://www.cnblogs.com/liujiacai/p/15533679.html
Copyright © 2020-2023  润新知