(转)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
*************** 当你发现自己的才华撑不起野心时,就请安静下来学习吧!***************
posted @ 2021-11-10 15:16  liujiacai  阅读(319)  评论(0编辑  收藏  举报