学习Shell的时候,顺带找了几个大厂的shell启动脚本来练练手,发现了很多彩蛋,学习到不少技巧~~~~
1:源码分析
#!/bin/sh
#
# RedHat system statup script for Jenkins
# Based on SUSE system statup script for Jenkins
# Copyright (C) 2007 Pascal Bleser
#
# #此处是一堆版权申明#
#
###############################################################################
#
# chkconfig: 35 99 01
# description: Jenkins Automation Server
#
###############################################################################
### BEGIN INIT INFO
# Provides: jenkins
# Required-Start: $local_fs $remote_fs $network $time $named
# Should-Start: $time sendmail
# Required-Stop: $local_fs $remote_fs $network $time $named
# Should-Stop: $time sendmail
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Short-Description: Jenkins Automation Server
# Description: Jenkins Automation Server
### END INIT INFO# Check for missing binaries (stale symlinks should not happen)
##先定义了jenkins默认的安装包的位置(采取yum等方式安装会下载war包到这个位置)
JENKINS_WAR="/usr/lib/jenkins/jenkins.war"
##判断jenkins的安装包是否存在,如果不存在并且输入命令的第一个参数是stop,将直接退出程序
test -r "$JENKINS_WAR" || { echo "$JENKINS_WAR not installed";
if [ "$1" = "stop" ]; then exit 0;
else exit 5; fi; }##判断配置文件是否存在
JENKINS_CONFIG=/etc/sysconfig/jenkins
test -e "$JENKINS_CONFIG" || { echo "$JENKINS_CONFIG not existing";
if [ "$1" = "stop" ]; then exit 0;
else exit 6; fi; }##判断配置文件是否有权限读取
test -r "$JENKINS_CONFIG" || { echo "$JENKINS_CONFIG not readable. Perhaps you forgot 'sudo'?";
if [ "$1" = "stop" ]; then exit 0;
else exit 6; fi; }##定义jenkins运行时的pid(防止jenkins被启动多份,因为jenkins在运行时可以指定多个端口,但是其他的目录并没有额外指定,这会维护软件设计的初衷,所以除非有特殊设置,否则只允许启动一份实例)
JENKINS_PID_FILE="/var/run/jenkins.pid"
#
JENKINS_LOCKFILE="/var/lock/subsys/jenkins"
#加载系统的functions库,functions库的详见本博客的functions库
. /etc/init.d/functions# 利用 -f 判断:文件是否存在且为普通文件则为真,. file 可以把文件里的配置加载到该shell里
[ -f "$JENKINS_CONFIG" ] && . "$JENKINS_CONFIG"# 根据配置文件来设置环境变量(这是一种不错的设置思路,软件开发过程中可以借鉴)
##判断JENKINS_HOME的位置是否是合法的
[ -n "$JENKINS_HOME" ] || { echo "JENKINS_HOME not configured in $JENKINS_CONFIG";
if [ "$1" = "stop" ]; then exit 0;
else exit 6; fi; }## -d 用于文件存在且为目录则为真
[ -d "$JENKINS_HOME" ] || { echo "JENKINS_HOME directory does not exist: $JENKINS_HOME";
if [ "$1" = "stop" ]; then exit 0;
else exit 1; fi; }# Search usable Java as /usr/bin/java might not point to minimal version required by Jenkins.
# see http://www.nabble.com/guinea-pigs-wanted-----Hudson-RPM-for-RedHat-Linux-td25673707.html##usr/bin/java可能不是jenkins运行支持的jdk,candidate(候选)的jdk
##此处蕴含了可以把字符串当做数组来迭代!!!!!
candidates="
/etc/alternatives/java
/usr/lib/jvm/java-1.8.0/bin/java
/usr/lib/jvm/jre-1.8.0/bin/java
/usr/lib/jvm/java-1.7.0/bin/java
/usr/lib/jvm/jre-1.7.0/bin/java
/usr/lib/jvm/java-11.0/bin/java
/usr/lib/jvm/jre-11.0/bin/java
/usr/lib/jvm/java-11-openjdk-amd64
/usr/bin/java
"## -x 用于判断文件存在且可执行则为真 ,如果配置文件里制定了jdk则用配置文件里的jdk
for candidate in $candidates
do
[ -x "$JENKINS_JAVA_CMD" ] && break
JENKINS_JAVA_CMD="$candidate"
doneJAVA_CMD="$JENKINS_JAVA_CMD
#JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true" 默认开始了绘图无显示设备模式
$JENKINS_JAVA_OPTIONS -DJENKINS_HOME=$JENKINS_HOME -jar $JENKINS_WAR"
##指定了jenkins自身的运行日志的目录
PARAMS="--logfile=/var/log/jenkins/jenkins.log##项目运行时会把${JENKINS_WAR}解压放到cache目录里,jenkins其实是一个web架构项目
## 是否会每一次运行都去把这个执行解压,还是解压一次后,就不再管了,这个需要看源码
--webroot=/var/cache/jenkins/war --daemon"
## -n用于判断:字符串的长度不为零,判断是否指定JENKINS_PORT(jenkins监听的端口号)
[ -n "$JENKINS_PORT" ] && PARAMS="$PARAMS --httpPort=$JENKINS_PORT"## -n用于判断:字符串的长度不为零,判断是否指定jenkins响应指定IP的请求,默认监听所有IP (0.0.0.0),若安全性有要求,可只监听指定的IP
[ -n "$JENKINS_LISTEN_ADDRESS" ] && PARAMS="$PARAMS --httpListenAddress=$JENKINS_LISTEN_ADDRESS"## -n用于判断:字符串的长度不为零,判断是否指定HTTPS_PORT(默认是关闭的,也没有配置)
如果判断表达式为真,会把当前当前参数拼接在一起
[ -n "$JENKINS_HTTPS_PORT" ] && PARAMS="$PARAMS --httpsPort=$JENKINS_HTTPS_PORT"
## -n用于判断:字符串的长度不为零,判断是否指定HTTPS_KEYSTORE(默认是没有启用https,也没有配置,HTTPS_KEYSTORE与HTTPS应该同时出现才行)如果判断表达式为真,会把当前当前参数拼接在一起
[ -n "$JENKINS_HTTPS_KEYSTORE" ] && PARAMS="$PARAMS --httpsKeyStore=$JENKINS_HTTPS_KEYSTORE"
## -n用于判断:字符串的长度不为零,判断是否指定HTTPS_KEYSTORE_PASSWORD(默认是没有启用https,也没有配置,HTTPS_KEYSTORE_PASSWORD与HTTPS_KEYSTORE应该同时出现才行)如果判断表达式为真,会把当前当前参数拼接在一起
[ -n "$JENKINS_HTTPS_KEYSTORE_PASSWORD" ] && PARAMS="$PARAMS --httpsKeyStorePassword='$JENKINS_HTTPS_KEYSTORE_PASSWORD'"## -n用于判断:字符串的长度不为零,判断是否指定jenkins响应指定IP的请求,默认监听所有IP (0.0.0.0),若安全性有要求,可只监听指定的IP
[ -n "$JENKINS_HTTPS_LISTEN_ADDRESS" ] && PARAMS="$PARAMS --httpsListenAddress=$JENKINS_HTTPS_LISTEN_ADDRESS"## -n用于判断:字符串的长度不为零,判断是否启用了要监听Http2协议的监听自定义端口
[ -n "$JENKINS_HTTP2_PORT" ] && PARAMS="$PARAMS --http2Port=$JENKINS_HTTP2_PORT"## -n用于判断:字符串的长度不为零,判断是否指定jenkins响应指定IP的请求HTTP2,默认监听所有IP (0.0.0.0),若安全性有要求,可只监听指定的IP
[ -n "$JENKINS_HTTP2_LISTEN_ADDRESS" ] && PARAMS="$PARAMS --http2ListenAddress=$JENKINS_HTTP2_LISTEN_ADDRESS"## -n用于判断:字符串的长度不为零,判断是否指定了Jenkins的日志级别(默认=5=INFO)
[ -n "$JENKINS_DEBUG_LEVEL" ] && PARAMS="$PARAMS --debug=$JENKINS_DEBUG_LEVEL"
[ -n "$JENKINS_HANDLER_STARTUP" ] && PARAMS="$PARAMS --handlerCountStartup=$JENKINS_HANDLER_STARTUP"##JENKINS_HANDLER_MAX http连接池的MAX数量(默认是100,超过这个数量会被拒绝访问)
[ -n "$JENKINS_HANDLER_MAX" ] && PARAMS="$PARAMS --handlerCountMax=$JENKINS_HANDLER_MAX"##JENKINS_HANDLER_IDLE http连接池的IDEL数量
[ -n "$JENKINS_HANDLER_IDLE" ] && PARAMS="$PARAMS --handlerCountMaxIdle=$JENKINS_HANDLER_IDLE"##jenkins默认选择winstone作为servlet容器,如果有必要刻意选择jetty-runner.jar作为servlet容器
[ -n "$JENKINS_EXTRA_LIB_FOLDER" ] && PARAMS="$PARAMS --extraLibFolder=$JENKINS_EXTRA_LIB_FOLDER"##jenkins支持的其他的额外的参数 可以通过java -jar jenkins.war --help 查看详见本博客
[ -n "$JENKINS_ARGS" ] && PARAMS="$PARAMS $JENKINS_ARGS"##jenkins 是否支持把访问日志记录下,如果支持会选择LoggerClassName ,日志记录格式,以及日志记录的位置
if [ "$JENKINS_ENABLE_ACCESS_LOG" = "yes" ]; then
PARAMS="$PARAMS --accessLoggerClassName=winstone.accesslog.SimpleAccessLogger --simpleAccessLogger.format=combined --simpleAccessLogger.file=/var/log/jenkins/access_log"
fiRETVAL=0
case "$1" in
##开始判断命令带过来的第一个参数,如果是start,就开始以daemon模式启动
start)
echo -n "Starting Jenkins "##指定进程运行的属主+指定文件运行的pid文件(pid会被写进该文件里)
daemon --user "$JENKINS_USER" --pidfile "$JENKINS_PID_FILE" $JAVA_CMD $PARAMS > /dev/null##判断 代码有没有启动起来$?能得到命令执行后的结果,如果是0就是true
RETVAL=$?
if [ $RETVAL = 0 ]; then##success是/etc/init.d/functions 定义的空壳函数
success## just in case we fail to find it(每次启动我们都要把jenkins_pid_file替换为一个新的空文件)
echo > "$JENKINS_PID_FILE"## $$能获取当前脚本进程号(pid),据此找到该进程的sessionId(-o 后面sess是定制的返回值)
MY_SESSION_ID=`/bin/ps h -o sess -p $$`
# get PID## 通过ps -hu jenkins的运行属主 -o 定制显示输出格式sessionid,pid,cmd
##把ps得到的结果里的command字段用来和jenkins指定位置的启动包进行匹配(防止一台机器上在不同的位置上部署了多个jenkins给误杀了)
/bin/ps hww -u "$JENKINS_USER" -o sess,ppid,pid,cmd | \
while read sess ppid pid cmd; do
[ "$ppid" = 1 ] || continue
# this test doesn't work because Jenkins sets a new Session ID
# [ "$sess" = "$MY_SESSION_ID" ] || continue##判断cmd 命令里是否是指定位置的jar包,$?返回的是上一个排在最后的命令(grep)执行的结果
echo "$cmd" | grep $JENKINS_WAR > /dev/null##中括号里面可以直接用于计算数学表达式(数字只能用=和!=号判断其他符号都不支持)
[ $? = 0 ] || continue
# found a PID 如果是启动jenkins会把当程序的pid写入到一个文件
echo $pid > "$JENKINS_PID_FILE"
done
touch $JENKINS_LOCKFILE
else##failure 是/etc/init.d/functions 定义的空壳函数
failure
fi
echo
;;## 如果是shell携带的参数是stop则执行停止jenkins已启动的进程
stop)
echo -n "Shutting down Jenkins "## 当jenkins 注册为服务以后,killproc即可杀死由service jenkins start 启动的服务
##killproc 函数需要阅读/etc/init.d/functions functions里定义的killproc如果没有传递信号量只是尝试给进程发送stop信号量(需要程序里做出相应才行),并没有强制杀死进程
killproc jenkins
rm -f $JENKINS_LOCKFILE
RETVAL=$?
echo
;;
try-restart|condrestart)
if test "$1" = "condrestart"; then
echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}"
fi
$0 status
if test $? = 0; then
$0 restart
else
: # Not running is not a failure.
fi
;;
restart)
$0 stop
$0 start
;;
force-reload)
echo -n "Reload service Jenkins "
$0 try-restart
;;
reload)
$0 restart
;;
status)
status jenkins
RETVAL=$?
;;
probe)
## Optional: Probe for the necessity of a reload, print out the
## argument to this init script which is required for a reload.
## Note: probe is not (yet) part of LSB (as of 1.9)test "$JENKINS_CONFIG" -nt "$JENKINS_PID_FILE" && echo reload
;;
*)
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}"
exit 1
;;
esac
exit $RETVAL
2:分析总结
分析该文件花费了我20个小时左右:收获颇多
引用其他文件已定义好的函数 | /etc/init.d/functions |
把一个服务加入到service | 可以通过service 服务名(文件名) stop|start|restart|status 定制操作 |
利用[数字变量=某个数值]||continue 结束流程 | 对于不太想写过多if then fi的时候可以参考 |
killproc pragram杀死已启动的程序 | killproc是funtions里的函数,用信号量去杀死程序(程序里也需要配合信号量) |
3:jenkins的脚本流程
先读取某个固定配置文件里的配置参数
如果某些项没有填写,就用默认的值
最后再解析命令的操作(stop|start|restart|status|...)
然后针对这些流程做出相应的处理