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:\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
查看腳本幫助信息:
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
