Linux Shell编程 | 何不通过一段详细注释的Shell脚本,掌握linux命令?(一键部署jdk)

人生苦短,务必性感。

前言

        相信很多人都不想从头开始看一个个枯燥的linux命令,学了那么多命令,实际经常使用的命令并不多,何不通过一段实战的shell脚本,学习起来更有意思,可以从中直接感受下哪些命令才会高频使用,针对性去学习,了解哪些坑我们遇到的时候怎么去避过。

       当我们遇到不懂得命令,或者需要用到某些命令时,我们再回过头去书本上或者网上找对应的知识点学习,这样效率会更高。 

        完整的 shell 脚本已经放在了文章最后,每行都加了详细的注释,赶紧复制下来学起来吧!


 建议先了解下jdk的安装过程,帮助理解


[运行说明]
脚本名称:jdk.sh
脚本和jdk包放置路径:按照jdk.sh中填写的路径放置即可
运行系统:centos 7 运行通过

演示运行结果:


 # 脚本第一行是 #!/bin/bash ,其中 #! 是一个特殊标记,说明这是一个可执行的脚本,#! 后面跟是脚本的解释器程序路径。除了第一行,其他以 # 开头都表示注释。

#!/bin/bash

更加详细了解shell执行细节的可以看下我的文章:《Linux Shell编程 | 你真的了解Shell脚本执行方式吗?》


Shell 里使用的两种注释方式:

:<<EOF

  • 单行注释用   # 需要注释的内容 
  • 多行注释用   :<<EOF 

                                    需要注释的内容1 
                                    需要注释的内容2
                                    ...
                                EOF

EOF


        脚本开始,我们需要使用一个相对定位的方法,以达到灵活访问各个目录,这里使用的是dirname $0 定位方式。

 需要了解更多dirname $0细节的可以看下我的文章《Linux Shell编程 | cd `dirname $0`;pwd》

代码:  

# 相对位置定位方法:SH_DIR=$(cd `dirname $0`;pwd),dirname $0 取得jdk.sh脚本的父目录,pwd命令显示当前目录的绝对路径
# 注意:``是命令替换符,键盘ESC键下面,在字符1左边,在TAB键上面的那个键,例子:A=`ls -l` ,把ls -l的结果赋给A
# $()与``作用一样,SH_DIR=$(cd `dirname $0`;pwd)也可写为SH_DIR=`cd $(dirname $0); pwd `
SH_DIR=$(cd `dirname $0`;pwd)

# 进入目录,命令格式: cd [dirName]
cd ${SH_DIR}
# 进入上上层目录(一个../表示上一层目录,两个../表示上上层目录)
cd ../../


  我们在写简单脚本时,可以一个sh搞定,后面如果大家写的脚本功能比较多时,我们可以把一些公共的配置参数单独放到一个文件中conf.properties,后续脚本中直接source就可以所有配置了

# 读取配置(本篇文章不涉及,仅仅说明一下,但在文末完整代码里已经把这行代码添加上了,小伙伴正好可以借此学习机会自行尝试把一些参数放到conf.properties中看下运行结果)
source ./conf.properties 


:<<EOF 

=== usage 是一个函数 ===

  • 实现的功能是:当我们执行脚本,输入了不支持的命令行参数时,会提示当前支持的参数为 install 和 uninstall
  • 后面在case in分支中有调用此函数

EOF

代码:  

usage() {
    echo "Usage: jdk.sh [install|uninstall]"
    exit 1
}

:<<EOF
===是否卸载 OpenJDK ===

  • 有些centos系统已经内置了openJDK版本,如果想安装其他jdk版本,最好先把openJDK卸载了,以免影响使用
  • 这里给出的解决方式是:检测到系统已安装openJDK版本,会给出提示,需要手动输入 y/n 来决定要不要卸载

EOF

代码: 

# rpm -qa | grep java表示查询java包是否安装,并把查找的结果赋给openjdk
# 管道符使用"丨"代表,是用来连接多条命令的;如"命令1丨命令2",把前面命令1的输出,作为后面命令2的输入
openjdk_rpm=`rpm -qa | grep java`
# if else是Shell的一个分支结构
# 如果查询jdk安装包结果不为空,说明已经安装jdk环境,则
if [[ ${openjdk_rpm} != '' ]];then
    # 给出提示,这里read -p表示读取命令前,在命令行中直接指定一个提示,提示语为:Do you want to uninstall openJDK (y/n)
    # read命令用于从标准输入读取数值,读取的值为remove_flag
    read -p "Do you want to uninstall openJDK (y/n): " remove_flag
    # 如果remove_flag = y,这里即表示键盘输入y,则
    if [[ ${remove_flag} = "y" ]];then
        # 卸载jdk,这里利用for循环卸载所有jdk相关包,并把记录写到jdk.log里,但是不在命令行显示
        for jdk in ${openjdk_rpm}
        do
        	echo $jdk >> ${SH_DIR}/jdk.log 2>&1
        	# rpm -e --nodeps 这条命令是删除某个软件包,并且忽略软件包的依赖关系
        	rpm -e --nodeps $jdk >> ${SH_DIR}/jdk.log 2>&1
        done
        printf "%-25s \t %-10s \t %5s\n" "OpenJDK" "uninstall" "100% complete。" | tee -a ${SH_DIR}/jdk.log
    #如果键盘输入的不是y,则不卸载openJDK,打印提示语OpenJDK needn't to be uninstall,并把提示语记录到jdk.log文件中,但也在命令行显示
    else
        echo "OpenJDK needn't to be uninstall" | tee -a ${SH_DIR}/jdk.log
        # exit 2 表示这个程序退出后,它的返回值是2,可以理解为这个程序到这里就死了,它的遗言是2
        exit 2
    fi
# 注意:所有的if结束必须有fi,这是规定
fi

        不知道大家注意到了没,上面有的记录写到文件jdk.log中,同时还会在命令行显示这些记录,有的记录只会写到文件jdk.log中,并不会在命令行显示
# 这就涉及到了知识点 tee -a jdk.log 和 >>jdk.log 2>&1的区别,感兴趣的看下我的文章《Linux Shell编程 | tee命令各种使用场景详解》,特别详细举例说明了各种使用场景。



:<<EOF

  • === install 是一个函数 ===
  • 实现的功能是:安装JDK

EOF

sh jdk.sh install 安装jdk:

 如果已经安装JDK,再次运行此函数,会提示:

 

 代码:

install(){
	# 函数开头用rm -rf 强制删除了jdk.log,目的是每次安装前把之前老的记录清除掉,保证每次安装仅记录最新的一次安装日志
	rm -rf ${SH_DIR}/jdk.log
	# echo用来打印字符串的,加上参数 -e 还可以为字符添加样式、颜色、背景颜色;格式:\033[;;m 需要设置样式的字符串 \033[0m
	echo -e "***********************************\033[7;0;41m JDK安装 \033[0m*************************************" | tee -a ${SH_DIR}/jdk.log
	# 通过命令java -version查看jdk版本信息,并把查到的信息赋值给java_version
	# -n :只打印模式匹配的行,sed -n '1p'表示只打印第一行
	# awk是一个强大的文本分析工具,值得大家单独花时间好好学一下,gsub(A,B)表示利用B替换A,即gsub(/"/,"")表示用空替换",再取print $3(print $3表示第三列)
	java_version=`java -version 2>&1 | sed -n '1p'|awk '{gsub(/"/,"");print $3}' 2>&1`
	# =~表示正则匹配,如果JDK版本信息包含1.8.0_231,则
	if [[ $java_version =~ "1.8.0_231" ]]; then
		# 打印已安装的JDK版本,并把记录写到jdk.log
		printf "%-25s \t %-10s \t %5s\n" "JDK" "已经安装版本" "$java_version" | tee -a ${SH_DIR}/jdk.log
		# 退出码0
		exit 0
	fi
	cd ${SH_DIR}
  cd ../../../
	# if -f 如果文件存在且是可读的则为真,这里! -f 表示如果文件jdk-8u231-linux-x64.tar.gz不存在,则
	if [ ! -f "./tools/jdk-8u231-linux-x64.tar.gz" ]; then
		# 打印jdk-8u231-linux-x64.tar.gz not exist,并写入jdk.log,且不在命令行显示
		echo "jdk-8u231-linux-x64.tar.gz not exist" >> ${SH_DIR}/jdk.log 2>&1
		# 退出码1
		exit 1
	# else表示,如果文件jdk-8u231-linux-x64.tar.gz存在,则
	else
	  print4barC 0 1
		# if -x 如果文件存在且是可执行的则为真。这里! -x表示如果/usr/local/java不存在,则
		if [ ! -x /usr/local/java ]; then
			echo "mkdir /usr/local/java/" >> ${SH_DIR}/jdk.log 2>&1
			# 创建/usr/local/java/
			mkdir "/usr/local/java/"
		fi
		print4barC 1 2
		# cp是拷贝命令,拷贝tool目录下jdk-8u231-linux-x64.tar.gz到/usr/local/java/目录下
		cp ./tools/jdk-8u231-linux-x64.tar.gz /usr/local/java/
		print4barC 2 4
		# 进入/usr/local/java/目录
		cd /usr/local/java/
		echo "unzip jdk-8u231-linux-x64.tar.gz" >> ${SH_DIR}/jdk.log 2>&1
		# 解压jdk-8u231-linux-x64.tar.gz文件到当前目录
		# tar命令用来解压tar包,一般解压时跟的参数是zxvf,想更详细了解相关知识点的可以关注下,我正好准备从原理写一篇详细的关于linux打包和解压包的文章
		tar -zxvf jdk-8u231-linux-x64.tar.gz >> ${SH_DIR}/jdk.log 2>&1
		print4barC 4 6
		# 删除该压缩包,因为上面我们已经得到解压后的包了;也可以不删除,个人习惯喜欢删掉不用的数据,避免冗余
		rm jdk-8u231-linux-x64.tar.gz
		print4barC 6 7
		# 如果不存在/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback,则
		if [ ! -x /usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback ]; then
			echo "mkdir /usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/" >> ${SH_DIR}/jdk.log 2>&1
			# 创建目录/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback
			mkdir "/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/"
		fi
		print4barC 7 8
		cd ${SH_DIR}
    cd ../../../
		# 如果存在/deploy/src/simsun.ttc,则
		if [ -f ./deploy/src/simsun.ttc ]; then
			# 拷贝simsun.ttc到/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/
			cp ./deploy/src/simsun.ttc /usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/ >> ${SH_DIR}/jdk.log 2>&1
			# 为了避免某些程序找不到字体。用mkfontscale和mkfontdir,创建fonts.scale和fonts.dir两个文件达到索引字体的目的
			mkfontscale >> ${SH_DIR}/jdk.log 2>&1
			mkfontdir >> ${SH_DIR}/jdk.log 2>&1
		fi
		print4barC 8 10

		# 日期,date "+%Y%m%d_%H%M%S",输出的结果类似20220108_144228
		t=`date "+%Y%m%d_%H%M%S"`
		echo "back /etc/profile to /etc/back/profile.install.back.$t"  >>  ${SH_DIR}/jdk.log 2>&1
		if [ ! -x /etc/back/ ]; then
			echo "mkdir /etc/back/"  >>  ${SH_DIR}/jdk.log 2>&1
			mkdir "/etc/back/"
		fi
		print4barC 10 11
		cp /etc/profile /etc/back/profile.install.back.$t  >>  ${SH_DIR}/jdk.log
		cp /etc/profile /etc/profile.tmp  >>  ${SH_DIR}/jdk.log 2>&1
		cp /etc/bashrc /etc/bashrc.tmp  >>  ${SH_DIR}/jdk.log 2>&1
		print4bar 12

    # 添加到环境环境,类似windows添加环境变量后,在任意路径都可以使用
    # linux里使用 echo "需要添加的路径" 添加环境变量
		echo "export JAVA_HOME=/usr/local/java/jdk1.8.0_231/" >> /etc/profile.tmp
		print4bar 13
	  echo "export JRE_HOME=/usr/local/java/jdk1.8.0_231/jre" >> /etc/profile.tmp
	  print4bar 14
	  echo "export CLASS_PATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar:\$JRE_HOME/lib" >> /etc/profile.tmp
	  print4bar 15
	  echo "export PATH=\$PATH:\$JAVA_HOME/bin:\$JRE_HOME/bin" >> /etc/profile.tmp
	  print4bar 16
    # /etc/bashrc 中也要添加 java的相关环境变量,否则非交互式命令模式下,直接在sh文件中执行source /etc/profile 并不会让环境变量生效
    # 脚本source /etc/profile经常不生效的问题,相信很多老手都不知道,这个值得注意下
    echo "export JAVA_HOME=/usr/local/java/jdk1.8.0_231/" >> /etc/bashrc.tmp
    print4bar 17
    echo "export JRE_HOME=/usr/local/java/jdk1.8.0_231/jre" >> /etc/bashrc.tmp
    print4bar 18
    echo "export CLASS_PATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar:\$JRE_HOME/lib" >> /etc/bashrc.tmp
    print4bar 19
    echo "export PATH=\$PATH:\$JAVA_HOME/bin:\$JRE_HOME/bin" >> /etc/bashrc.tmp
    print4bar 20

		# mv命令用来为文件或目录改名、或将文件或目录移入其它位置;这里用来移动文件,-f表示遇到同名文件不会询问直接覆盖
		mv -f /etc/profile.tmp /etc/profile
	    echo "source /etc/profile" >> ${SH_DIR}/jdk.log 2>&1
	    source /etc/profile
	  print4barC 20 22
	  mv -f /etc/bashrc.tmp /etc/bashrc
	    echo "source /etc/bashrc" >> ${SH_DIR}/jdk.log 2>&1
	    source /etc/bashrc
	    source ~/.bashrc
	  print4barC 22 25
		printf "%-25s \t %-10s \t %5s\n" "JDK" "install" "100% complete。" | tee -a ${SH_DIR}/jdk.log
		exit 0
	fi
}

:<<EOF

  • === uninstall 是一个函数 ===
  • 实现的功能是:卸载JDK
  • #卸载注意事项:保证安装目录下相关文件卸载赶紧,以防万一卸载时可以备份一下相关数据

EOF

sh jdk.sh uninstall 即可卸载jdk:

代码:  

uninstall(){
	basedir="/usr/local"

	j=`whereis java`
	java=$(echo ${j} | grep "jdk")
	#echo "$java"
	if [ "$java" != "" ]; then
		sudo rm -rf /usr/local/java/
	fi
	t=`date "+%Y%m%d_%H%M%S"`
	echo "back /etc/profile to /etc/back/profile.uninstall.back.$t" >> ${SH_DIR}/jdk.log 2>&1
	if [ ! -x /etc/back/ ]; then
		mkdir "/etc/back/"
	fi
	cp /etc/profile /etc/back/profile.uninstall.back.$t
	cp /etc/profile /etc/profile.tmp
	cp /etc/bashrc /etc/bashrc.tmp
	sed -i '/JAVA_HOME/d' /etc/profile.tmp
	sed -i '/JRE_HOME/d' /etc/profile.tmp
	# delete bashrc java env variables
	sed -i '/JAVA_HOME/d' /etc/bashrc.tmp
	sed -i '/JRE_HOME/d' /etc/bashrc.tmp
	# replace
	mv -f /etc/profile.tmp /etc/profile
	mv -f /etc/bashrc.tmp /etc/bashrc
	#echo "source /etc/profile"
	source /etc/profile
	source /etc/bashrc

	printf "%-25s \t %-10s \t %5s\n" "JDK" "uninstall" "100% complete。" | tee -a ${SH_DIR}/jdk.log
}

脚本中,在/etc/profile中添加环境变量后,使用source /etc/profile编译后能在当前终端生效


:<<EOF

  • === 这是函数 ===
  • 实现的功能是:安装进度显示

EOF   

 代码: 

print4barC() {
  i=$1
  while [ $i -le $2 ]
  do
    print4bar $i
    sleep 0.1
    let i++
  done
}

print4bar() {
  ch=('|' '\' '-' '/')
  let index=$1%4
  printf "%-25s \t %-10s \t %d%% \t [%c] \r" "JDK" "install" $(($1*4)) ${ch[$index]}
}

        解释下 print4bar()中的printf "%-25s \t %-10s \t %d%% \t [%c] \r" "JDK" "install" $(($1*4)) ${ch[$index]}

  • 一般%s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出;
  • 上面的%-25s表示一个左对齐、宽度为25个字符格式,不足25个字符,右侧补充相应数量的空格符,这里指的是JDK往右共占25字符;
  • %-10s表示一个左对齐、宽度为10个字符字符串格式,这里指的是install往右共占10字符;
  • ch[$index]是取得ch=('|' '\' '-' '/')中的字符,再通过sleep等待,最终实现的效果:若是安装还未到100%,一直动态展示从字符| 到 / 动态旋转的动画

 

    最后,我们使用shell 中的case in语句完成install、uninstall等分支选择 。

:<<EOF

  • === case in 分支结构 === 
  • 实现的功能是:当表达式有匹配的模式时,会执行对应的函数 

      Shell支持两种分支结构(选择结构),分别是 if else 语句和 case in 语句
      case语句适用于需要进行多重分支的应用情况

                          语法格式:
                                case expression in
                                pattern1)
                                    statement1
                                    ;;
                                pattern2)
                                    statement2
                                    ;;
                                ……
                                *)
                                    statementn
                                    esac

      case结构特点:以case开头,以esac结尾,模式必须以 ) 结束 
      case、in 和 esac 都是 Shell 关键字,expression 表示表达式,pattern 表示匹配模式
      语句中的*) 可以没有,但一般都会加上,用来做一些提示工作,这样当 expression 没有匹配到任何一个模式时,会执行 *) 后面语句。

EOF 

代码:  


case "$1" in 			# 比如,我们执行脚本 sh jdk.sh start ,$1就表示运行该脚本时的第一个命令行参数 start
  "install")      # 如果 $1 为 start,则执行 install函数
    install
    ;;            # 直到遇到 ;; 停止,不再继续执行下去
  "uninstall")    # 如果,我们执行脚本 sh jdk.sh install,则遇到上面的 ;; 不会停止,继续往下找到 uninstall ,再遇到下一个 ;;停止执行
    uninstall
    ;;
  *)              #  *) 表示默认模式,当使用前面的 $1 没有匹配结果时,,将执行 *) 后的命令序列 usage函数
    usage         # usage函数写的是一个提示功能,告诉用户我这个脚本执行时支持的参数为install 和 uninstall
    ;;
esac

附录(完整的脚本):

#!/bin/bash

# 脚本第一行是#!/bin/bash ,其中#!是一个特殊标记,说明这是一个可执行的脚本,#!后面跟是脚本的解释器程序路径。除了第一行,其他以#开头都表示注释
# 想更详细了解相关知识点的可以看下我的文章《Linux Shell编程 | 你真的了解Shell脚本执行方式吗?》

:<<EOF
		单行注释用 # 需要注释的内容 
		多行注释用 :<<EOF 
									需要注释的内容1 
									需要注释的内容2
									...
							EOF
EOF

# 相对位置定位方法:SH_DIR=$(cd `dirname $0`;pwd),dirname $0 取得jdk.sh脚本的父目录,pwd命令显示当前目录的绝对路径
# 注意:``是命令替换符,键盘ESC键下面,在字符1左边,在TAB键上面的那个键,例子:A=`ls -l` ,把ls -l的结果赋给A
# $()与``作用一样,SH_DIR=$(cd `dirname $0`;pwd)也可写为SH_DIR=`cd $(dirname $0); pwd `
# 想更详细了解相关知识点的可以看下我的文章《Linux Shell编程 | cd `dirname $0`;pwd》
SH_DIR=$(cd `dirname $0`;pwd)

# 进入目录,命令格式: cd [dirName]
cd ${SH_DIR}
# 进入上上层目录(一个../表示上一层目录,两个../表示上上层目录)
cd ../../

# 读取配置,conf.properties里面写的是该脚本需要用到的一些参数
source ./conf.properties

## === 这是一个函数 ===
# 实现的功能是:当我们执行脚本,输入了不支持的命令行参数时,会提示当前支持的参数为 install 和 uninstall
# 后面在case in分支中有调用此函数
usage() {
    echo "Usage: jdk.sh [install|uninstall]"
    exit 1
}


:<<EOF
		===是否卸载 OpenJDK ===
		有些centos系统已经内置了openJDK版本,如果想安装其他jdk版本,最好先把openJDK卸载了,以免影响使用
		这里给出的解决方式是:检测到系统已安装openJDK版本,会给出提示,需要手动输入 y/n 来决定要不要卸载
EOF
# rpm -qa | grep java表示查询java包是否安装,并把查找的结果赋给openjdk
# 管道符使用"丨"代表,是用来连接多条命令的;如"命令1丨命令2",把前面命令1的输出,作为后面命令2的输入
openjdk_rpm=`rpm -qa | grep java`
# if else是Shell的一个分支结构
# 如果查询jdk安装包结果不为空,说明已经安装jdk环境,则
if [[ ${openjdk_rpm} != '' ]];then
    # 给出提示,这里read -p表示读取命令前,在命令行中直接指定一个提示,提示语为:Do you want to uninstall openJDK (y/n)
    # read命令用于从标准输入读取数值,读取的值为remove_flag
    read -p "Do you want to uninstall openJDK (y/n): " remove_flag
    # 如果remove_flag = y,这里即表示键盘输入y,则
    if [[ ${remove_flag} = "y" ]];then
        # 卸载jdk,这里利用for循环卸载所有jdk相关包,并把记录写到jdk.log里,但是不在命令行显示
        for jdk in ${openjdk_rpm}
        do
        	echo $jdk >> ${SH_DIR}/jdk.log 2>&1
        	# rpm -e --nodeps 这条命令是删除某个软件包,并且忽略软件包的依赖关系
        	rpm -e --nodeps $jdk >> ${SH_DIR}/jdk.log 2>&1
        done
        printf "%-25s \t %-10s \t %5s\n" "OpenJDK" "uninstall" "100% complete。" | tee -a ${SH_DIR}/jdk.log
    #如果键盘输入的不是y,则不卸载openJDK,打印提示语OpenJDK needn't to be uninstall,并把提示语记录到jdk.log文件中,但也在命令行显示
    else
        echo "OpenJDK needn't to be uninstall" | tee -a ${SH_DIR}/jdk.log
        # exit 2 表示这个程序退出后,它的返回值是2,可以理解为这个程序到这里就死了,它的遗言是2
        exit 2
    fi
# 注意:所有的if结束必须有fi,这是规定
fi

# 不知道大家注意到了没,上面有的记录写到文件jdk.log中,同时还会在命令行显示这些记录,有的记录只会写到文件jdk.log中,并不会在命令行显示
# 这就涉及到了知识点 tee -a jdk.log 和 >>jdk.log 2>&1的区别,感兴趣的看下我的文章《Linux Shell编程 | tee命令各种使用场景详解》,特备详细举例说明了各种使用场景

:<<EOF
=== 这是一个函数 ===
实现的功能是:安装JDK
EOF
install(){
	# 函数开头用rm -rf 强制删除了jdk.log,目的是每次安装前把之前老的记录清除掉,保证每次安装仅记录最新的一次安装日志
	rm -rf ${SH_DIR}/jdk.log
	# echo用来打印字符串的,加上参数 -e 还可以为字符添加样式、颜色、背景颜色;格式:\033[;;m 需要设置样式的字符串 \033[0m
	echo -e "***********************************\033[7;0;41m JDK安装 \033[0m*************************************" | tee -a ${SH_DIR}/jdk.log
	# 通过命令java -version查看jdk版本信息,并把查到的信息赋值给java_version
	# -n :只打印模式匹配的行,sed -n '1p'表示只打印第一行
	# awk是一个强大的文本分析工具,值得大家单独花时间好好学一下,gsub(A,B)表示利用B替换A,即gsub(/"/,"")表示用空替换",再取print $3(print $3表示第三列)
	java_version=`java -version 2>&1 | sed -n '1p'|awk '{gsub(/"/,"");print $3}' 2>&1`
	# =~表示正则匹配,如果JDK版本信息包含1.8.0_231,则
	if [[ $java_version =~ "1.8.0_231" ]]; then
		# 打印已安装的JDK版本,并把记录写到jdk.log
		printf "%-25s \t %-10s \t %5s\n" "JDK" "已经安装版本" "$java_version" | tee -a ${SH_DIR}/jdk.log
		# 退出码0
		exit 0
	fi
	cd ${SH_DIR}
  cd ../../../
	# if -f 如果文件存在且是可读的则为真,这里! -f 表示如果文件jdk-8u231-linux-x64.tar.gz不存在,则
	if [ ! -f "./tools/jdk-8u231-linux-x64.tar.gz" ]; then
		# 打印jdk-8u231-linux-x64.tar.gz not exist,并写入jdk.log,且不在命令行显示
		echo "jdk-8u231-linux-x64.tar.gz not exist" >> ${SH_DIR}/jdk.log 2>&1
		# 退出码1
		exit 1
	# else表示,如果文件jdk-8u231-linux-x64.tar.gz存在,则
	else
	  print4barC 0 1
		# if -x 如果文件存在且是可执行的则为真。这里! -x表示如果/usr/local/java不存在,则
		if [ ! -x /usr/local/java ]; then
			echo "mkdir /usr/local/java/" >> ${SH_DIR}/jdk.log 2>&1
			# 创建/usr/local/java/
			mkdir "/usr/local/java/"
		fi
		print4barC 1 2
		# cp是拷贝命令,拷贝tool目录下jdk-8u231-linux-x64.tar.gz到/usr/local/java/目录下
		cp ./tools/jdk-8u231-linux-x64.tar.gz /usr/local/java/
		print4barC 2 4
		# 进入/usr/local/java/目录
		cd /usr/local/java/
		echo "unzip jdk-8u231-linux-x64.tar.gz" >> ${SH_DIR}/jdk.log 2>&1
		# 解压jdk-8u231-linux-x64.tar.gz文件到当前目录
		# tar命令用来解压tar包,一般解压时跟的参数是zxvf,想更详细了解相关知识点的可以关注下,我正好准备从原理写一篇详细的关于linux打包和解压包的文章
		tar -zxvf jdk-8u231-linux-x64.tar.gz >> ${SH_DIR}/jdk.log 2>&1
		print4barC 4 6
		# 删除该压缩包,因为上面我们已经得到解压后的包了;也可以不删除,个人习惯喜欢删掉不用的数据,避免冗余
		rm jdk-8u231-linux-x64.tar.gz
		print4barC 6 7
		# 如果不存在/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback,则
		if [ ! -x /usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback ]; then
			echo "mkdir /usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/" >> ${SH_DIR}/jdk.log 2>&1
			# 创建目录/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback
			mkdir "/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/"
		fi
		print4barC 7 8
		cd ${SH_DIR}
    cd ../../../
		# 如果存在/deploy/src/simsun.ttc,则
		if [ -f ./deploy/src/simsun.ttc ]; then
			# 拷贝simsun.ttc到/usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/
			cp ./deploy/src/simsun.ttc /usr/local/java/jdk1.8.0_231/jre/lib/fonts/fallback/ >> ${SH_DIR}/jdk.log 2>&1
			# 为了避免某些程序找不到字体。用mkfontscale和mkfontdir,创建fonts.scale和fonts.dir两个文件达到索引字体的目的
			mkfontscale >> ${SH_DIR}/jdk.log 2>&1
			mkfontdir >> ${SH_DIR}/jdk.log 2>&1
		fi
		print4barC 8 10

		# 日期,date "+%Y%m%d_%H%M%S",输出的结果类似20220108_144228
		t=`date "+%Y%m%d_%H%M%S"`
		echo "back /etc/profile to /etc/back/profile.install.back.$t"  >>  ${SH_DIR}/jdk.log 2>&1
		if [ ! -x /etc/back/ ]; then
			echo "mkdir /etc/back/"  >>  ${SH_DIR}/jdk.log 2>&1
			mkdir "/etc/back/"
		fi
		print4barC 10 11
		cp /etc/profile /etc/back/profile.install.back.$t  >>  ${SH_DIR}/jdk.log
		cp /etc/profile /etc/profile.tmp  >>  ${SH_DIR}/jdk.log 2>&1
		cp /etc/bashrc /etc/bashrc.tmp  >>  ${SH_DIR}/jdk.log 2>&1
		print4bar 12

    # 添加到环境环境,类似windows添加环境变量后,在任意路径都可以使用
    # linux里使用 echo "需要添加的路径" 添加环境变量
		echo "export JAVA_HOME=/usr/local/java/jdk1.8.0_231/" >> /etc/profile.tmp
		print4bar 13
	  echo "export JRE_HOME=/usr/local/java/jdk1.8.0_231/jre" >> /etc/profile.tmp
	  print4bar 14
	  echo "export CLASS_PATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar:\$JRE_HOME/lib" >> /etc/profile.tmp
	  print4bar 15
	  echo "export PATH=\$PATH:\$JAVA_HOME/bin:\$JRE_HOME/bin" >> /etc/profile.tmp
	  print4bar 16
    # /etc/bashrc 中也要添加 java的相关环境变量,否则非交互式命令模式下,直接在sh文件中执行source /etc/profile 并不会让环境变量生效
    # 脚本source /etc/profile经常不生效的问题,相信很多老手都不知道,这个值得注意下
    echo "export JAVA_HOME=/usr/local/java/jdk1.8.0_231/" >> /etc/bashrc.tmp
    print4bar 17
    echo "export JRE_HOME=/usr/local/java/jdk1.8.0_231/jre" >> /etc/bashrc.tmp
    print4bar 18
    echo "export CLASS_PATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar:\$JRE_HOME/lib" >> /etc/bashrc.tmp
    print4bar 19
    echo "export PATH=\$PATH:\$JAVA_HOME/bin:\$JRE_HOME/bin" >> /etc/bashrc.tmp
    print4bar 20

		# mv命令用来为文件或目录改名、或将文件或目录移入其它位置;这里用来移动文件,-f表示遇到同名文件不会询问直接覆盖
		mv -f /etc/profile.tmp /etc/profile
	    echo "source /etc/profile" >> ${SH_DIR}/jdk.log 2>&1
	    source /etc/profile
	  print4barC 20 22
	  mv -f /etc/bashrc.tmp /etc/bashrc
	    echo "source /etc/bashrc" >> ${SH_DIR}/jdk.log 2>&1
	    source /etc/bashrc
	    source ~/.bashrc
	  print4barC 22 25
		printf "%-25s \t %-10s \t %5s\n" "JDK" "install" "100% complete。" | tee -a ${SH_DIR}/jdk.log
		exit 0
	fi
}

# === 这是一个函数 ===
# 实现的功能是:卸载JDK
# 卸载注意事项:保证安装目录下相关文件卸载赶紧,以防万一卸载时可以备份一下相关数据
uninstall(){
	basedir="/usr/local"

	j=`whereis java`
	java=$(echo ${j} | grep "jdk")
	#echo "$java"
	if [ "$java" != "" ]; then
		sudo rm -rf /usr/local/java/
	fi
	t=`date "+%Y%m%d_%H%M%S"`
	echo "back /etc/profile to /etc/back/profile.uninstall.back.$t" >> ${SH_DIR}/jdk.log 2>&1
	if [ ! -x /etc/back/ ]; then
		mkdir "/etc/back/"
	fi
	cp /etc/profile /etc/back/profile.uninstall.back.$t
	cp /etc/profile /etc/profile.tmp
	cp /etc/bashrc /etc/bashrc.tmp
	sed -i '/JAVA_HOME/d' /etc/profile.tmp
	sed -i '/JRE_HOME/d' /etc/profile.tmp
	# delete bashrc java env variables
	sed -i '/JAVA_HOME/d' /etc/bashrc.tmp
	sed -i '/JRE_HOME/d' /etc/bashrc.tmp
	# replace
	mv -f /etc/profile.tmp /etc/profile
	mv -f /etc/bashrc.tmp /etc/bashrc
	#echo "source /etc/profile"
	source /etc/profile
	source /etc/bashrc

	printf "%-25s \t %-10s \t %5s\n" "JDK" "uninstall" "100% complete。" | tee -a ${SH_DIR}/jdk.log
}

print4barC() {
  i=$1
  # -le表示:小于等于
  # 如果$1 小于或者等于 $2,执行
  while [ $i -le $2 ]
  do
    print4bar $i
    # 等待0.1秒
    sleep 0.1
    # 每次$1 + 1,直到$1 增加到4=$2结束
    let i++
  done
}

# === 实现进度展示功能 ===
print4bar() {
  ch=('|' '\' '-' '/')
  let index=$1%4
  # %s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出
  # 下面的%-25s表示一个左对齐、宽度为25个字符字符串格式,不足25个字符,右侧补充相应数量的空格符,这里指的是JDK往右共占25字符
  # %-10s表示一个左对齐、宽度为10个字符字符串格式,这里指的是install往右共占10字符
  printf "%-25s \t %-10s \t %d%% \t [%c] \r" "JDK" "install" $(($1*4)) ${ch[$index]}
}


:<<EOF

  	Shell支持两种分支结构(选择结构),分别是 if else 语句和 case in 语句
  	case语句适用于需要进行多重分支的应用情况

					  	语法格式:
						    	case expression in
						    	pattern1)
						        	statement1
						        	;;
						    	pattern2)
						        	statement2
						        	;;
						    	……
						    	*)
						        	statementn
									esac

  	case结构特点:以case开头,以esac结尾,模式必须以 ) 结束 
  	case、in 和 esac 都是 Shell 关键字,expression 表示表达式,pattern 表示匹配模式
  	语句中的*) 可以没有,但一般都会加上,用来做一些提示工作,这样当 expression 没有匹配到任何一个模式时,会执行 *) 后面语句
     
EOF

# === case in 是shell的一个分支结构 ===
# 实现的功能是:当表达式有匹配的模式时,会执行对应的函数

case "$1" in 			# 比如,我们执行脚本 sh jdk.sh start ,$1就表示运行该脚本时的第一个命令行参数 start
  "install")      # 如果 $1 为 start,则执行 install函数
    install
    ;;            # 直到遇到 ;; 停止,不再继续执行下去
  "uninstall")    # 如果,我们执行脚本 sh jdk.sh install,则遇到上面的 ;; 不会停止,继续往下找到 uninstall ,再遇到下一个 ;;停止执行
    uninstall
    ;;
  *)              #  *) 表示默认模式,当使用前面的 $1 没有匹配结果时,,将执行 *) 后的命令序列 usage函数
    usage         # usage函数写的是一个提示功能,告诉用户我这个脚本执行时支持的参数为install 和 uninstall
    ;;
esac

 看完脚本,大家可以尝试一下实现下面功能,完成了说明linux命令基本入门了,能够胜任大部分简单工作场景了~

1、卸载jdk时,命令行显示的卸载风格

2、引用配置文件conf.properties

3、尝试写一个一键安装、卸载mysql

 后续会继续更新 shell 的各种脚本,尝试覆盖90%以上的使用场景。

快乐|微笑|情绪|好|做得好|哈哈|开口笑|笑脸|脸|大笑|喜笑颜开
点赞、收藏,记得回家的路~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我自人间漫浪

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值