入门详解!利用 Shell 脚本自动升级更新 Github 项目软件

已经发布该脚本的第二版,有重大更新,前往 https://github.com/hl99/MyShell 围观!

0.背景

在 GitHub 发现好玩的“宝藏”后,忍不住手痒时都会下载到 VPS 上安装测试一下,有的会保留下来进行使用,但是出于安全、稳定性等方面的考虑,因此有必要进行定期或不定期的更新,毕竟 GitHub 上的很多项目都是个人作品,可能不会像正规的公司产品进行了大量测试除 bug 功能迭代升级等,GitHub 上的个人作品主要依靠相关的爱好者“众人拾柴火焰高”!以前都是手动下载安装更新,现在得要搞点自动化了脚本了,懒人必备。一方面可以学习 Shell 脚本的编写另外也学习 GitHub 的使用,一举两得!

1.原理解析

主要是利用 Linux 系统 Shell 脚本的灵活强大的特性,进行简单的人机交互,然后利用 GitHub 的统一的资源链接格式、API 接口链接等进行自动下载更新。当然,也可以用 Python 、Perl 等语言程序来实现,这里主要为了学习使用 Shell。

利用 Shell 脚本自动获取 GitHub 上最新软件版并下载

入门详细解读 Shell 脚本的编程编写并实践利用脚本程序自动获取 GitHub 软件更新并下载

2.行动指南——GitHub

GitHub 真的一个大宝库,资源丰富,在开源软件领域使用极其广泛,它是使用 Git(是专为处理文本文件而设计的版本控制系统)进行版本控制的网站。正因为其广泛的使用,官方规定了一些固定的资源链接形式、API 接口函数等以利于其集中在各类软件中。地地道道的码农们一般是直接使用 Git 客户端,将线上的功能方便的转移到本地电脑上,我这伪码农为了轻量简洁,就没必要“杀鸡用牛刀”了。
GitHub 网站将不同的资源,分配在不同的服务器上,利于加速分流分担负载,从域名来看主要有两个:一是以” github.com “为一级域名构成的主站,如” https://api.github.com/ “;另一个是以” githubusercontent.com “为一级域名构成的资源(辅助)站,如” https://raw.githubusercontent.com/ “

2.1.解读” raw.githubusercontent.com “类链接

” https://raw.githubusercontent.com/ “类链接下一般放置的是用户上传的资源如脚本程序、issus 里面的图片等,放在亚马逊的云服务上,是 GitHub 的素材服务器 ( assets server), 避免跟主服务器(即 https://github.com/ )抢占服务器资源。如果我们需要下载GitHub 项目里面的单个文件,需要先单击选择,然后进入代码查看页面,然后再单击代码框右上角的” Raw “按钮,自动跳转到” https://raw.githubusercontent.com/ “起始的页面,这个页面显示的是“原生态”的原始文件内容了(简单来说,纯代码或者文本文件了),单个文件下载方法是“另存为…”就可以了,因此我们要在命令行下下载某个文件时,也是从该类链接里面下载。

  1. #如果我们要用 wget 下载 GitHub 上的脚本程序时,方法如下
  2. wget -N  -c -b --no-check-certificate "https://raw.githubusercontent.com/OwnerName/RespositoryName/master/xxx.sh"
  3.  
  4. # wget 命令用于命令行和 Shell 中进行下载,命令格式:wget [参数] ["资源URL地址"],常见参数如下:
  5. # " -N "参数表示不要重新下载文件除非比本地文件新," -P "参数用于指定下载文件目录," -c "参数表示断点续传," -b "参数表示后台下载(这两个参数适合大文件下载)。
  6. # " --no-check-certificate  "参数表示对于 Https 开头的网址域名不检查证书。
  7. #另外如: wget -qO-  "https://www.go2do.net/"| bash ,   " -q "参数是安静模式,无信息输出," -O "参数是把后面网址下载的文件存为指定的文件名称。
  8. #如果" -O "参数后面没有跟着一个文件名字而是" - "(如" -qO- "),则表示将下载后的内容输出到标准输出,但并不在屏幕显示,目的当然是直接传递给bash进行解析执行了。
  9.  
  10. # GitHub 的资源 URL 地址:" https://raw.githubusercontent.com/OwnerName/RepositoryName/master/xxx.sh " 中" OwnerName "指资源所有者或者说是该资源“主人”的“名字”,
  11. #" RepositoryName "指项目名称,经常简写为" repo "翻译为“仓库”实际上是可以用 Git 命令进行操作和代码归档存放的文件夹。
  12. #在 GitHub 网站上资源“主人”的名字是唯一的,也就是网站登录的用户名是唯一的(即 OwnerName ),而每个用户下可以有多个项目,因此" RepositoryName "可能有同名的,或者一个账户(即 OwnerName )下有多个项目名(即 RepositoryName )。
  13. # " master "指程序的主干(或者说是未正式发布的源码的最新可用版),现代化的软件工程项目基本都是团队合作的模式,每个团队成员需要分工合作,而且只增删改程序的有限的部分代码,
  14. #为了防止把已经完工的代码改出错乱或者区分正式版和测试版之类的,更为了记录修改过程和思想,因此广泛使用 Git 系统进行版本控制。
  15.  
  16. #简单介绍一下命令行下的  Git 系统的命令使用:如果需要对主干程序(即 master)下载和修改,用" git clone URL.git "将整个项目下载到自己的本地电脑(如果本地已有,为了更新一下用 git pull orgin master),
  17. #然后创建名称为 xxx 的分支" git branch xxx ",建议仅只在新创建的分支上进行增删改,使用" git chechout xxx "将编辑的代码对象从 master 切换到分支 xxx ,当修改的代码在分支上修改测试完成,需要先切换到" git chechout master ",
  18. #再使用" git merge xxx "合并修改到 master,使用" git add . "提交所有更改(但没有同步到 GitHub), " git commit -m "备注 or 小结等信息" "添加代码修改的说明及备注(很有必有,要写修改的思想及原因!!!)。
  19. #最后,获取使用项目账户的 SSH Key(相当于取得提交代码的授权), " git push origin master "提交代码。
  20. #如果使用 fork 命令则是在自己的 Github 账户下创建的项目副本,完成修改后,push 到自己账户下的代码仓库,然后通过" pull request "向代码原来的“主人”申请将我们自己修改的代码合并 master 进去,原代码“主人”审核批准后,就可以合并到 master 了。
  21. # " xxx.sh "指具体的需下载的文件名,以上信息根据实际情况进行替换。
  22.  
  23. #常用的 git 命令还有:状态查看:git status ,git log 查看修改记录(即 commit),git config ... 进行配置(git commit 之前必须配置 user.name、user.email等利于溯源联系 ),git init 在本地创建" repo "仓库文件夹时需使用该命令初始化
  24. # git tag 命令用于给项目软件打上版本号标签,如 v1.0、v1.1 之类的,可以用" git chechout v1.0 "依据版本标签进行切换。

2.2.解读” api.github.com “类链接

形如这样的” https://api.github.com/ “链接是调用的 GItHub 官方的 REST API 接口,提供了更强大的更丰富的操作特性。我们通过WEB 浏览器进行的各类操作都可以以调用 API 接口链接的方式去实现,所以对于自动化的程序处理带来了便利的条件。

  1. #首先!访问的链接最后不能有/,如" https://api.github.com/users/OwnerName "是可以访问到项目“主人”的信息,但" https://api.github.com/users/OwnerName/ "不行。返回的信息主要是 json 格式 !
  2. #其次!不同于一般URL访问,GIthub的API访问链接是严格的区分大小写!
  3. #获取 OwnerName 用户所有的仓库资源:https://api.github.com/users/OwnerName/repos ,会得到一个repo的JSON格式列表。
  4. #获取仓库 repo 详细信息:https://api.github.com/repos/OwnerName/RepositoryName,repo的路径就开始和个人信息不同了。
  5. #获取某个仓库 repo 中的内容列表:https://api.github.com/repos/OwnerName/RepositoryName/contents,注意这只会返回根目录的内容。
  6. #获取 repo 中子目录的内容列表:https://api.github.com/repos/OwnerName/RepositoryName/contents/目录名,如果是更深层的内容,则在链接列按照顺序逐级写上目录名称。
  7. #获取 repo 中某文件信息(不包括内容):https://api.github.com/repos/OwnerName/RepositoryName/contents/文件路径,文件路径是文件的完整路径,区分大小写。只会返回文件基本信息。
  8. #获取某文件的原始内容(Raw):1. 通过上面返回的 Json 格式的文件信息中提取 download_url 这条链接;2. 直接访问:https://raw.githubusercontent.com/OwnerName/RepositoryName/分支名(如:master)/文件路径 
  9. #获取发布的版本信息:https://api.github.com/repos/OwnerName/RepositoryName/releases ,这个很重要!!是我们检查自动升级的基础了。
  10.  
  11. #从 GitHub API 获取 最新已经发布( releases )的版本信息( tag_name )
  12. wget -qO- https://api.github.com/repos/OwnerName/RepositoryName/releases| grep "tag_name"| head -n 1| awk -F ":" '{print $2}'| sed 's/\"//g;s/,//g;s/ //g'
  13.  
  14. # " | "这个竖线是管道,管道是一种通信机制,通常用于进程间的通信(也可通过socket网络接口进行通信),它自动将前面每一个进程的输出(stdout)直接作为下一个进程的输入(stdin)。管道命令仅能处理 standard output ,对于 standard error output 会忽略。
  15. # grep 简单理解就是查找,抓取的目标是 " tag_name ",在 GitHub 文档规范里这个版本号标记。grep -v grep 排除含grep的进程行;grep -E "xxx1 | xxx2" ,或逻辑;与逻辑:使用多个 ‘grep’ 命令,如 grep  | grep xxx2 ,既满足 xxx1 又满足 xxx2。
  16. # head -n 1 指从头部开始显示文件的前N(此处 N=1 )行,类似的命令有:tail -n k和tail -n -k意义相同,都是从尾部开始的第 k 行开始显示,可简化为 tail k
  17. # awk 类似于 grep 的查找,但功能超级强大," -F "参数用于指定分割符用于将一行文本划分为几个小段,此处为" : "默认是空格," '{print $2}' "通常单引号('')里放置的是:'正则表达式 {动作命令}',
  18. #本例中正则表达式为空(即任意内容)," {print $2} "即大括号" {} "放置的是动作命令," print $2 "指输出分隔符划分的第2段的内容,分隔符前为第1段。
  19. # sed 是一个非常强大的命令行下的文本查找、替换编辑工具。sed [参数] ['动作命令1;动作命令2...'] [文本文件] 组合而成,功能非常强大,支持正则表达式等功能。
  20. # " s/\"//g; "是一个动作命令段,反斜杠"/"是用于分割的标记,其中" s "指替换命令和" g "指全局替换标识,当然也可以特定行或者特定的出现次数替换," \" "是防止转义双引号("),因此,这个动作命令段也就是将双引号(")全部替换为空即删除双引号(")。
  21. # " s/,//g; "是第二个动作命令段,就是将逗号(,)全部替换为空即删除逗号(,) ; " s/ //g "是第三个动作命令段,就是将空格全部替换为空即删除空格。
  22.  
  23. #软件下载链接:https://api.github.com/OwnerName/RepositoryName/releases/download/tag_name/xxx ,根据下载链接的格式依据实际情况进行替换一下,就可以下载我们需要的软件了
  24. # 下载链接通过 wget -qO-  https://api.github.com/repos/OwnerName/RepositoryName/releases| grep "browser_download_url" ,可以很方便的读取到,
  25. #但是" xxx "这个地方需要注意,它可能包含了各种平台如32位/64位、linux、windows、Android 等等,需要结合实际的操作系统平台进行选择

3.行动指南——Shell

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁,当我们在命令行模式见到的各种 Linux 就是一个个 Shell,它也是一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统的各种服务和功能。Shell 既是一种命令语言,又是一种程序设计语言。
Shell 通常都是指 shell 脚本,但 Shell 和 Shell script 是两个不同的概念。由于习惯的原因和简洁起见,我们这里都是指 Shell 脚本编程,不是指开发 Shell 自身。Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。

Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)

这里本教程关注的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash 。

3.1.Shell 脚本之环境变量、变量引用及文字颜色

Shell 程序的一些基本设置(配置)和前置信息的处理。

  1. #!/bin/env bash
  2. PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
  3. export PATH
  4.  
  5. #以上三行用于定义环境变量,指定使用的 Shell 解析器是 Bash , Bash 是当前用户使用的 Shell。注意" /bin/env "实际安装路径,还可能是" /usr/bin/env "等。
  6. # env 查看环境变量;还可以在" PATH "环境变量查找要执行的命令,比用" #!/bin/bash "灵活,适应性更好;
  7. # set 查看环境变量;命令显示当前 Shell的变量,包括当前用户的变量,信息量最大;
  8. # export 查看环境变量;可以声明一个临时的变量,变量在关闭shell时失效
  9. # PATH 是系统自带的常用环境变量,决定了 Shell 将到哪些目录中寻找命令或程序
  10. #在用户目录中的 .bash_profile 为用户环境变量文件【仅对该用户有效是(永久的)】," .bashrc "文件使用在非交互式 Shell 登陆时
  11. #在" /etc/profile "文件中添加的变量【对所有用户生效是(永久的)】," source "命令让修改立即生效,否则下次重进此用户时生效
  12.  
  13. #在 Windows 下编写该代码有个坑,运行时,虽然可以用,但是 Linux 报出异常:/usr/bin/env bash\r no such file or directory,因为在 Windows 下,用连续的'\r'和'\n'两个字符进行换行。'\r'为回车符,'\n'为换行符
  14. #在 Linux下,用'\n'进行换行,碰到\r后,光标会被移到行首,把前面的覆盖掉了,然后输出了:No such file or directory
  15. #解决办法:1.在 VIM 里设置: set fileformat=unix set nobomb; set fileencoding=utf8; w 命令解决问题 ;2.安装 dos2unix 如 yum install dos2unix -y ,然后 dos2unix xxx.sh
  16. #另外,在使用 Notepad++ 等进行编辑文档时,需要选择 UTF-8 无BOM 格式。微软在 UTF-8 中使用 BOM 是因为这样可以把 UTF-8 和 ASCII 等编码明确区分开,打开文本文件时它会自动识别并剔除BOM,在用户不注意的情况下同时兼容 Unicode 和 非Unicode
  17.  
  18. #=================================================
  19. #	System Required: CentOS/Debian/Ubuntu
  20. #	Description: Auto update the software from GitHub 
  21. #	Version: 1.0
  22. #	Author: Go2do 
  23. #	Blog: https://www.go2do.net/
  24. #	Encoding: Unix(LF)  UTF-8  noBOM
  25. #=================================================
  26.  
  27. sh_ver="v1.0"
  28.  
  29. Green_font_prefix="\033[32m" && Red_font_prefix="\033[31m" && Green_background_prefix="\033[42;37m" && Red_background_prefix="\033[41;37m" && Font_color_suffix="\033[0m"  #定义了一些字体颜色相关的局部变量
  30. Error="${Red_font_prefix}[错误]${Font_color_suffix}"     #定义了局部变量 "Error" ,输出时显示为红色的“【错误】”字样
  31. Tip="${Green_font_prefix}[注意]${Font_color_suffix}"     #定义了局部变量 "Tip" ,输出时显示为红色的“【注意】”字样
  32. Info="${Green_font_prefix}[信息]${Font_color_suffix}"    #定义了局部变量 "Info" ,输出时显示为绿色的“【信息】”字样
  33.  
  34. # Green_font_prefix、Red_font_prefix、Info 等是自定义 Shell 变量,这里用于定义字体背景色、文字色等。
  35. #输出特效格式控制: 如" \033[32m " 指绿色文字,反斜杠" \ "是转义字符(与 echo 命令配合),中括号" [ "是颜色定义标识符
  36. #输出特效格式控制:形如" \033[31m "、" \033[32m " 指字体颜色(范围:30-37);" \033[41;37m "指红底白字的背景色、" \033[42;37m "指绿地白字的背景色(范围:40-47);
  37. #控制选项:" \033[0m "指关闭所有属性;" \033[1m "指设置高亮度;" \033[4m "指下划线
  38.  
  39. # Info="${Green_font_prefix}..." 其中" Green_font_prefix " 是前面定义的变量,然后在此处进行引用(变量替换),使用" ${ } " 进行标识,引用变量时功能与" $Green_font_prefix "相同,但使用" ${ } "后更佳更清晰明了。
  40. #命令替换用" $() " ,如" echo $(ls) ",输出结果同直接使用" ls " 命令,等同于使用反引号(``),如" echo `ls` "。
  41. #大括号" {} " 和 小括号" () "还可用于一串命令的执行(也可不用大括号、小括号,直接用分号分割,但用了更清晰),命令之间用 分号" ; " 分隔,首尾的命令和大括号之间需要有空格,命令结尾与大括号之间有分号" ; "。
  42. #执行命令时" () " 只是对一串命令重新开一个子 Shell 进行执行," {} "对一串命令在当前 Shell 执行。
  43. #大括号" {} "还有很多特色使用方法,如使用" # "获取变量长度" ${#Green_font_prefix} ";如进行字符截取" ${Error:1:4} "等等功能。
  44.  
  45. #双引号("")与单引号(''):双引号里可以有变量,可以出现转义字符" \ ";单引号里的任何字符都会原样输出,即使单引号字符串中使用 " $ "进行变量替换也无效,单引号字串中不能出现单引号,使用转义符后也不行
  46.  
  47.  
  48. #******以下为预定义的变量,使用时需在脚本里面进行赋值或者人机交互时赋值******
  49. OwnerName=""                          #设置欲下载的 GitHub 项目的所有者 
  50. RepositoryName=""                     #设置欲下载的 GitHub 项目的仓库名称
  51. APPName=""                            #设置软件在本地主机上的安装后的运行(进程)名称
  52. APPath=""                             #设置软件在本地主机上的安装后的路径,可以用 which xxx 查看

3.2.Shell 脚本之条件判断(权限及进程PID)

利用 Linux 系统进程 ID 的特色,学习自定义函数的使用,常用基本 Shell 命令的使用,Shell 命令实际上也是一种函数,这个函数是 Shell 自带的。脚本程序中主要是利用 echo 命令输出提示信息,利用 read 命令读取输入。

  1. #自定义 Check_root() 权限判断函数,输出信息提醒脚本需要 root 或者 sudo 权限
  2. Check_root(){
  3.     [[ $EUID != 0 ]] && echo -e "${Error} 当前非ROOT权限账号,无法继续操作,请更换ROOT账号或使用 ${Green_background_prefix} sudo su ${Font_color_suffix} 命令获取临时ROOT权限。" && exit 1
  4. }
  5.  
  6. #Linux 系统中每个进程都有2个 ID,分别为用户ID(uid)和有效用户ID(euid),UID 表示进程是哪个用户创建的,而 EUID 表示进程对于文件和资源的访问权限,具有 root 权限时 euid = 0 。$EUID 是系统环境变量。
  7.  
  8. #双对中括号" [[]] "和 单对中括号" [] ",都是进行条件判断,类似于" if [$EUID -ne 0] ",其中" -ne "进行数值判断,不相等返回则 "true"。" -eq "进行数值 相等 判断。
  9. # " && "是逻辑与运算符。另外,可以用" $? "表示上一条命令返回值,如果上一条命令成功执行,返回" 0 ",否则返回" 1 ",与" if "组合起来,也能达到" && "是逻辑与运算符的效果。
  10. #还有与逻辑、或逻辑组合成的多条命令: command1 && command2 || command3   如果前一条命令执行成功则执行下一条命令,如果command1执行成功(返回0),则执行command2
  11. # exit 1 表示退出脚本,一般正常时退出是" 0 ",这里用" 1 "利于标识错误。
  12.  
  13. Check_pid(){
  14.     [[ ${APPName} ]] && PID=$(ps -ef| grep "${APPName}" | grep -v "grep" | grep -v "xxx.sh" | grep -v "init.d" |grep -v "service" |awk '{print $2}') && echo  -e "${Info} 检测到 XXX 软件的运行名称为: ${APPName} 进程 PID 为:$PID " 
  15. }
  16.  
  17. # grep "${APPName}" 是利用 grep 命令依据变量" APPName "的值进行搜索。
  18.  
  19. # echo 命令" -e "参数指" echo "命令进行转义输出(通常都是所见即所得)且转义字符要使用双引号 , echo 命令" -n "参数指表示输出时不换行
  20. #例如" echo "\n" "是输出" \n ",而" echo  -e "\n" "在命令行下显示为换行的效果。

3.3.Shell 脚本之进行系统版本及系统位数判断

主要是利用 Shell 脚本中 if 条件判断语句的嵌套,读取保护系统有关消息的文件,判断系统版本

  1. #检查系统版本及位数信息
  2. Check_sys(){
  3.     if [[ -f /etc/redhat-release ]]; then        
  4. 	release="centos"
  5.     elif cat /etc/issue | grep -q -E -i "debian"; then  
  6. 	release="debian"
  7.     elif cat /etc/issue | grep -q -E -i "ubuntu"; then
  8. 	release="ubuntu"
  9.     elif cat /etc/issue | grep -q -E -i "centos|red hat|redhat"; then
  10. 	release="centos"
  11.     elif cat /proc/version | grep -q -E -i "debian"; then
  12. 	release="debian"
  13.     elif cat /proc/version | grep -q -E -i "ubuntu"; then
  14. 	release="ubuntu"
  15.     elif cat /proc/version | grep -q -E -i "centos|red hat|redhat"; then
  16. 	release="centos"
  17.     fi
  18.  
  19.     bit=`uname -m`   
  20.  
  21.     echo && echo -e "${Info} 您的系统为${Green_font_prefix}[ $release ]${Font_color_suffix};处理器架构为${Green_font_prefix}[ $bit ]${Font_color_suffix}" && echo
  22. }
  23.  
  24. # grep 常见参数: -q 安静模式,不输出任何消息,找到匹配内容返回0,未找到返回非0;-v 用于在进程查找中排除特定的进程; -i 参数是忽略大小写匹配;
  25. # grep 常见参数: -e 只能传递一个检索内容; -E 可以传递多个内容,支持正则表达式(同 egrep) ,使用 | 来分割多个pattern,以此实现或操作;
  26. # grep '^A B$' 是指, 匹配整个这个行中, 以A开头, 以B结尾, 指的是整行, 不是某个单词,-w 是匹配整个单词, 而不能是单词的一部分。
  27. # grep 支持通配符:. 指一个非换行符的字符;*  指零个或多个先前字符,如:'*grep'匹配所有一个或多个空格后紧跟grep的行 ;.* 指代表任意个字符
  28.  
  29. # uname 是查看系统信息(简化后)的命令,uname -a 查看所有信息;uname -m 处理器架构如64位:i686 或32位:x86_64;uname -r 内核发布版本;uname -s 内核名称如 Linux 或 Darwin ; uname -n 主机名;
  30.  
  31. # if ...; then ... elif ...; then ...else(仅允许一个) ... fi   嵌套件判断;-f 参数用于判断文件是否是普通文件,即既不是目录,也不是设备文件
  32. # if 条件中常用的关于 文件 的判断有:[ -e FILE ] 如果 FILE 存在则为真(返回0);[ -s FILE ] 如果 FILE 存在且大小不为0则为真(返回0);[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 要老, 或者 FILE2 存在且 FILE1 不存在则为真(返回0)。
  33. # if 条件中常用的关于 字符串 的判断有:[ -n STRING ] 如果 STRING 的长度非零则为真 ,即判断是否为非空,非空即是真(返回0);[ STRING1 = STRING2 ] 如果两个字符串相同则为真(返回0);[ STRING1 != STRING2 ] 如果字符串不相同则为真(返回0);[ STRING1 ]如果字符串不为空则为真(返回0),与-n类似。
  34. # if 条件中常用的关于 数值 的判断有:INT1 -eq INT2  若 INT1 和 INT2 相等为真(返回0), = ;INT1 -ne INT2  若 INT1 和 INT2 不相等为真 , <> ;INT1 -gt INT2  若 INT1 大于 INT2 为真 ,> ;INT1 -ge INT2  若 INT1 大于等于 INT2 为真,>= ;INT1 -lt INT2  若 INT1 小于 INT2 为真 , <  ;INT1 -le INT2 若 INT1 小于等于 INT2 为真, <= 。
  35. # Shell中对比字符串只能使用 ==、<、>、!=、-z、-n。对比字符串时,末尾一定要加上x(或者a、b等)一个字符,因为if [ $1x == "ab"x ]时如果没有了x ,并且当 $1 是"",这个语句会翻译成if [  == "ab" ],左边相当于没有东西了,会报语法错误。
  36. #或者使用[[  ]],就不需要x了。使用 < 或者 > 时,如果是用[  ],需要用转义符"\",如\>。对比数字使用既能使用-eq、-ne、-gt、-ge、-lt、-le,也能使用 ==、<、>、!= 等。将字符串强制视为数字进行比较,否则容易报错。

3.4.Shell 脚本之自动从 GitHub 升降本脚本程序及读取版本号等信息

依据前面讲解的 GitHub 链接和域名的分类,进行实战应用了。
先来进行一下本脚本程序从 GitHub 自动升级,使用” https://raw.githubusercontent.com/ “类型的链接和域名

  1. # 自定义函数 Update_Shell() 进行脚本升级判断和下载,因为链接形式基本固定的,也就比较好处理。
  2. Update_Shell(){
  3. #	 sh_new_ver=$(wget --no-check-certificate -qO- -t1 -T3 "https://raw.githubusercontent.com/see-think-do/MyShell/master/autoupdate-github.sh"|grep 'sh_ver="'|awk -F "=" '{print $NF}'|sed 's/\"//g'|head -1) && sh_new_type="github"
  4.     sh_new_ver=$(wget --no-check-certificate -qO- -t1 -T3 "https://raw.githubusercontent.com/see-think-do/MyShell/master/autoupdate-github.sh"|grep 'sh_ver="'|awk -F "=" '{print $NF}'|sed 's/\"//g'|head -1)
  5.     [[ -z ${sh_new_ver} ]] && echo -e "${Error} 无法链接到目标 GitHub !" && exit 1
  6.     wget -N --no-check-certificate "https://raw.githubusercontent.com/see-think-do/MyShell/master/autoupdate-github.sh" && chmod +x autoupdate-github.sh
  7.     echo -e "${Info} 脚本已更新为最新版本[ ${sh_new_ver} ] ${Font_color_suffix}!(注意:因为更新方式为直接覆盖当前运行的脚本,所以需要重新运行本脚本)" && exit 0
  8. }

现在进入!!重点!!自动抓取 GitHub 上最新的版本号,为自动升级做准备啦,使用” https://api.github.com/ “类型的链接和域名。

  1. #定义函数 Check_pre_info() ,检测一下项目相关预设信息是否在脚本中已经设置,否则交互式读取
  2. Check_pre_info(){
  3.     echo && [[ -z ${OwnerName} ]] && echo -e "${Error} 没有预设 XXX 软件 GitHub 中的所有者名称!" && echo -n -e "${Tip}" &&  read -e -p "请输入 XXX 软件所有者信息:" OwnerName
  4.     echo && [[ -z ${RepositoryName} ]] && echo -e "${Error} 没有预设 XXX 软件 GitHub 中的仓库名称!" && echo -n -e "${Tip}" &&  read -e -p "请输入 XXX 软件所在的仓库信息:" RepositoryName
  5.     echo && [[ -z ${APPName} ]] && echo -e "${Error} 没有预设 XXX 软件的安装后的运行(进程)名称!" && echo -n -e "${Tip}" &&  read -e -p "请输入 XXX 软件安装后的运行(进程)名称,直接回车将使用[ ${RepositoryName} ]:" APPName 
  6.     [[ -z ${APPName} ]] && APPName=${RepositoryName}          #如果为空,即直接输入回车,则赋值APPName=${RepositoryName} 
  7.  
  8.     [[ -z ${OwnerName} ]] && [[ -z ${RepositoryName} ]] &&[[ -z ${APPName} ]] && echo &&  echo -e "${Error} 项目预设(或读取)的信息设置有误!" && exit 1
  9.  
  10.     echo && [[ -z ${APPath} ]] && echo  -e "${Tip} 没有预设[ ${APPName} ]软件在本地主机上的安装后的文件夹路径,建议设置"  && echo -n -e "${Tip}" && read -e -r -p "请输入[ ${APPName} ]软件的安装路径(将用于版本、PID等信息检测),形如\" /usr/bin/ \":" APPath
  11. }
  12.  
  13. # read 命令用于读取输入,可以是交互式的键盘等的输入,也可以是从文件读取," -e "参数指对功能键进行编码转换。
  14. # read 命令" -p "参数后面紧跟输出的提示信息(类似 echo ),类似于" OwnerName "是读取到输入值之后存入的变量。  
  15. # read 命令" -r "参数原样读取(Raw mode),不把反斜杠字符解释为转义字符。" -s "参数是不显示输入的内容,比如输入密码。

根据预设或交互式读取到的 XXX 软件在 GitHub 上的基本信息进行后续处理。

  1. #自定义函数 Check_new_ver() 首先获取现在安装的 XXX 软件的版本号,然后去抓取 GitHub 上 XXX 的最新版本号
  2. Check_new_ver(){
  3. #	 echo && echo -e "${Tip}请输入合适的命令读取当前安装的 XXX 的版本 : ${Green_font_prefix}[ 通常格式是: /安装路径/xxx -v  或 -V ]${Font_color_suffix}" &&  read -e now_version_cmd   #该方式存在安全隐患,如输入 rm 
  4. #	 if [[ -z "${now_version_cmd}" ]]; then                    #如果为空,即直接输入回车
  5. #	      echo -e "${Error} XXX 当前版本获取失败 !" && echo 
  6. #	 else
  7. #	      #now_version=${now_version_cmd} 
  8. #	      echo &&  echo -n -e "${Info} 检测到 XXX 当前版本为:"	   #echo -n 表示输出时不换行
  9. #	      ${now_version_cmd} | awk '{print $3}' 
  10. #	  fi
  11.  
  12.     now_version=$(${APPath}${APPName} -v|awk '{print $3}')
  13.     [[ -z ${now_version} ]] && echo -e "${Error} 第一次获取[ ${APPName} ]版本信息失败 !将进行第二次测试......" 
  14.     [[ ${now_version} ]] && now_version="v${now_version}" && echo -e "${Info} 第一次获取的[ ${APPName} ]版本信息是 [ ${now_version} ]" && echo
  15.  
  16.     now_version=$(${APPath}${APPName} -V|awk '{print $3}') 
  17.     [[ -z ${now_version} ]] && echo -e "${Error} 第二次获取[ ${APPName} ]版本信息失败 !" && echo
  18.     [[ ${now_version} ]] && now_version="v${now_version}" && echo -e "${Info} 第二次获取的[ ${APPName} ]版本信息是 [ ${now_version} ]" && echo
  19.  
  20.     echo -e "${Tip}请输入要下载的[ ${APPName} ]版本号 ${Green_font_prefix}[ 格式如: v20180101 或 v1.2.3abc 等]${Font_color_suffix}查看版本列表${Green_font_prefix}[ https://github.com/${OwnerName}/${RepositoryName}/releases ]${Font_color_suffix}"
  21.     echo -n -e "${Tip}" && read -e -p "直接回车即自动获取最新版本:" new_version
  22.     if [[ -z ${new_version} ]]; then       #字符串判断:if  [ -z $string  ]  如果 string 为空,返回0 (true) 
  23.         new_version=$(wget -qO- https://api.github.com/repos/${OwnerName}/${RepositoryName}/releases| grep "tag_name"| head -n 1| awk -F ":" '{print $2}'| sed 's/\"//g;s/,//g;s/ //g')
  24. 	[[ -z ${new_version} ]] && echo -e "${Error} [ ${APPName} ]最新版本获取失败!" && exit 1
  25. 	echo -e "${Info} 检测到[ ${APPName} ]最新版本为${Green_font_prefix}[ ${new_version} ]${Font_color_suffix}" && echo 
  26.     fi
  27.  
  28.     if [[ ${new_version} ]] && [[ $(wget -qO- https://api.github.com/repos/${OwnerName}/${RepositoryName}/releases| grep "tag_name" | grep "${new_version}"| head -n 1| awk -F ":" '{print $2}'| sed 's/\"//g;s/,//g;s/ //g') ]]; then
  29.         echo && echo -e "${Info} 检测到 GitHub 存在版本为${Green_font_prefix}[ ${new_version} ]${Font_color_suffix}的[ ${APPName} ]" 
  30.     else
  31.         echo -e "${Error} [ ${APPName} ]版本${Green_font_prefix}[ ${new_version} ]${Font_color_suffix}获取失败,可能版本错误!" && exit 1
  32.    fi	
  33.  
  34. #	 if [[ "${now_version}" != "${new_version}" ]]; then
  35. #	 echo -e "${Info} 发现[ ${APPName} ]已有新版本 [ ${new_version} ],旧版本 [ ${now_version} ]"	
  36. }
  37.  
  38. #形如: if [ $a = 123 ] ," = "作为“是否等于”时两边都必须加空格,否则失效;不加空格是赋值,因为 Shell 里面没有" == ",这是 C 语言才有的:是否等于。(ps:貌似部分 shell 可兼容 = 与 ==)
  39.  
  40. #如果通过版本号信息我们无法确定当前使用的是否是最新版,还可以抓取创建时间、发布时间、上传时间等信息进行辅助判断,命令如下
  41.    wget -qO- https://api.github.com/repos/${OwnerName}/${RepositoryName}/releases| grep -E "tag_name|created_at|published_at|\"assets\":|created_at|updated_at" | head -n 6

3.5.Shell 脚本之从 GitHub 自动按需下载软件包

脚本从 Releases 页面抓取 assets 下的 browser_download_url 信息,根据读取到的本机的操作系统版本及处理器架构,按需选择即可自动下载。优先选择对应系统及架构的二进制包,因为源码包需要考虑库的依赖问题。

  1. Download_XXX(){
  2.     [[ ${new_version} ]] && echo -n -e "${Tip}" && read -e -p "是否获取[ ${APPName} ]版本号为 [ ${new_version} ]各系统平台下载链接列表? [Y/n] :" yn
  3.     [[ -z ${yn} ]] && yn="y"          #如果为空,即直接输入回车,则赋值yn=y
  4.  
  5. #	 if [[ $yn == [Yy] ]]; then         # [Yy] 正则表达式,指匹配 Y 或者 y ,不如下面的清晰好理解  
  6.     if [ $yn = Y ] || [ $yn  = y ]; then
  7.         echo && echo -e "${Info} 正在获取[ ${APPName} ]版本号为 [ ${new_version} ]各系统平台下载链接列表......"&& echo
  8.     else
  9. 	exit 1	   
  10.     fi  
  11.  
  12.     [[ -z ${OwnerName} ]] && [[ -z ${RepositoryName} ]] &&[[ -z ${new_version} ]] && echo -e "${Error} 项目链接地址信息设置有误!" && exit 1
  13.  
  14. #先抓取对应版本的所有下载链接,然后,根据提示的系统信息及处理器架构,手动选择需要的版本类型。并记录行数,用于选择下载链接时的判断
  15. #    wget -q -O ${OwnerName}_${RepositoryName}_releases  https://api.github.com/repos/${OwnerName}/${RepositoryName}/releases | cat ${OwnerName}_${RepositoryName}_releases | grep -E "\"name\":|\"browser_download_url\":" | grep -e "${new_version}"| awk -F "\"" '{print $4 "   -----> " NR }' 
  16.     wget  -qO-  https://api.github.com/repos/${OwnerName}/${RepositoryName}/releases | grep -E "\"name\":|\"browser_download_url\":" | grep -e "${new_version}"| awk -F "\"" '{print $4 "   -----> " NR }' 
  17.  
  18. #  NR 是 awk 内置变量,表示读取的行数;NF 指浏览记录的域(类似于 列 )的个数;
  19.  
  20. #	 tmpSum="0"      #临时变量,记录下载链接行数
  21. #    $tmpSum=$(cat ${OwnerName}_${RepositoryName}_releases | grep -E "\"name\":|\"browser_download_url\":" | grep -e "${new_version}"| awk 'END{print NR}')
  22. #    echo $tmpSum
  23.  
  24.     echo -e "${Tip}请从以上编号中选取适合的下载链接编号!优先选择对应系统及架构的二进制包" && echo -n -e "${Tip}" && read -e -p "如果使用源码包编译时需要考虑库依赖,需要手动自行安装!" Num_DL
  25. #    echo && [[ -z ${Num_DL} ]] && [ "${Num_DL}" > "0" ] && [ "${Num_DL}" <= "${tmpSum}" ] && echo -e "${Error} 您的选择有误,请输入正确数字!" && exit 1
  26.     echo && [[ -z ${Num_DL} ]] && echo -e "${Error} 您的选择有误,请输入正确数字!" && exit 1
  27.  
  28. #根据上面选择的下载链接编号,提取最终的 download_url
  29.     download_url=$( wget -qO- https://api.github.com/repos/${OwnerName}/${RepositoryName}/releases | grep -E "\"name\":|\"browser_download_url\":" | grep -e "${new_version}" | awk -F "\"" -v awkNum_DL="${Num_DL}" 'NR==awkNum_DL {print $4 }')    
  30.     echo && [[ ${download_url} ]] && echo -e "${Info} 您选择的下载链接是:${Green_font_prefix}[ ${download_url} ]${Font_color_suffix}"
  31.  
  32.  # awk 的 -v 参数用于设定一个变量,只有这样才能使用 Shell 脚本里面定义的变量,注意是!!赋值!!,因此不要有空格。   
  33.  
  34.     download_filename=$(echo "${download_url}" |awk -F "/" '{print $NF }')   #使用echo 将变量 ${download_url} 转换成字符串,否则,awk 无法处理
  35.     echo && [[ ${download_filename} ]] && echo -e "${Info} 您选择的下载文件类型是:${Green_font_prefix}[ ${download_filename} ]${Font_color_suffix}"
  36.  
  37.  
  38.     echo -n -e "${Tip}" &&  read -e -p "是否开始下载版本为 [ ${new_version} ] 的[ ${APPName} ] ? [Y/n] :" yn1
  39.     [[ -z "${yn1}" ]] && yn1="y"          #如果为空,即直接输入回车,则赋值yn1=y
  40.     #if [[ $yn1 == [Yy] ]]; then         # [Yy] 正则表达式,指匹配 Y 或者 y ,不如下面的清晰好理解  
  41.     if [ $yn1 = Y  ] || [  $yn1  =  y  ]; then
  42.         [ -f  "${download_filename}""_""${new_version}" ] && echo -e "${Error} 本地已经存在一个版本为 [ ${new_version} ] 的[ ${APPName} ] !" && exit 1
  43. 	[ ! -f  "${download_filename}""_""${new_version}" ] && wget -N  -c --no-check-certificate ${download_url} -O  "${download_filename}""_""${new_version}" && echo -e "${Info} 版本为 [ ${new_version} ] 的[ ${APPName} ] 已经下载成功!"
  44.     else
  45.        exit 1
  46.     fi 
  47. }

3.6.Shell 人机交互信息的输出、读取输入

下面的脚本利用 echo 输出提示信息,利用 read 读取输入,利用 case 进行分支条件选择。这些内容虽然是我们运行脚本时最先看到的,但是因为函数调用的关系,需要将它放在所有所有自定义函数的后面,基本上也就是最后面了,起个大早赶个晚集吧,o(╯□╰)o

  1.    echo && echo -e "      GitHub 一键 XXX 程序升级脚本 ${Red_font_prefix} [${sh_ver}] ${Font_color_suffix}
  2.   ----Author:go2do | Blog:https://www.go2do.net ----
  3. ————————————  
  4.  ${Green_font_prefix} 0.${Font_color_suffix} 升级本脚本
  5. ————————————
  6.  ${Green_font_prefix} 1.${Font_color_suffix} 下载更新  XXX
  7.  ${Green_font_prefix} 2.${Font_color_suffix} 自动解压  XXX
  8.  ${Green_font_prefix} 3.${Font_color_suffix} 编译更新  XXX
  9.  ${Green_font_prefix} 4.${Font_color_suffix} 启动运行  XXX
  10. ————————————" && echo
  11.  
  12.     echo -n -e "${Tip}"&& read -e -p "请输入数字 [0-4]:" num   
  13.  
  14.     case "$num" in
  15.         0)
  16.         Update_Shell      #自定义的 Update_Shell 函数调用,调用函数不需要写上" () "
  17.         ;;
  18.         1)
  19.         Check_pre_info      #检测必要的项目信息是否已经预设置,否则交互式读取
  20.         Check_new_ver       #从 GitHub 获取版本信息
  21.         Check_sys           #检查、输出操作系统及处理器架构信息
  22.         Download_XXX        #自定义的 Download_XXX  函数调用,调用函数不需要写上" () "
  23.         ;;
  24. 	2)
  25. #	 Unzip_XXX         #自定义的 Unzip_XXX 函数调用,调用函数不需要写上" () "   !!本功能留待后续添加!!
  26.         echo -e "${Info} !!本功能留待后续添加!!"
  27. 	;;
  28. 	3)
  29. 	Check_root
  30.         Check_pre_info 
  31. 	Check_pid
  32. #	 Install_XXX       #自定义的Install_XXX 函数调用,调用函数不需要写上" () "  !!本功能留待后续添加!!
  33.         echo -e "${Info} !!本功能留待后续添加!!"
  34. 	;;
  35. 	4)
  36. 	Check_root
  37.         Check_pre_info 
  38. 	Check_pid
  39. #	 Running_XXX       #自定义的 Running_XXX 函数调用,调用函数不需要写上" () "  !!本功能留待后续添加!!
  40.         echo -e "${Info} !!本功能留待后续添加!!"
  41.         ;;
  42.         *)
  43. 	echo -e "${Error} 请输入正确数字 [0-4]"
  44.         ;;
  45.     esac
  46.  
  47. # case "$num" in 根据" $num "的值匹配下面的分支选择项的值,分支选择项的值可以是常量(常数)或者变量,用" ) "标识该分支选择值的结束。
  48. #用双分号" ;; "标识该分支选项的功能段的结束。一旦匹配到分支选择项,立即执行,不判断后续项。
  49. #如果没有确定的分支选择项的值被匹配,则可以用" * "捕获这类“异常”。" esac "是" case "的逆序,标识" case "语句的结束;类似的有:" if "与" fi "
  50.  
  51. #为了不让各个函数之间有调用或者说是解耦吧,让存在相互关联和先后顺序的函数有且仅有只有个返回值,下一个函数执行的第一步是先判断一下前一个函数的返回值是否满足要求。

写到这里,这个脚本的功能已经完成了,下载试一试吧。

3.7.Shell 脚本的调试

下面简单介绍一下 Shell 脚本调试。

【Shell 的一些常用选项的用法】
-n 只读取shell脚本,但不实际执行
-x 进入跟踪方式,显示所执行的每一条命令
-c "string" 从strings中读取命令

  “-n”可用于测试shell脚本是否存在语法错误,但不会实际执行命令。在shell脚本编写完成之后,实际执行之前,首先使用“-n”选项来测试脚本是否存在语法错误是一个很好的习惯。因为某些shell脚本在执行时会对系统环境产生影响,比如生成或移动文件等,如果在实际执行才发现语法错误,您不得不手工做一些系统环境的恢复工作才能继续测试这个脚本。

“-c”选项使shell解释器从一个字符串中而不是从一个文件中读取并执行shell命令。当需要临时测试一小段脚本的执行结果时,可以使用这个选项,如下所示:
sh -c 'a=1;b=2;let c=$a+$b;echo "c=$c"'

"-x"选项可用来跟踪脚本的执行,是调试shell脚本的强有力工具。“-x”选项使shell在执行脚本的过程中把它实际执行的每一个命令行显示出来(用于跟踪逻辑),并且在行首显示一个"+"号。 "+"号后面显示的是经过了变量替换之后的命令行的内容,有助于分析实际执行的是什么命令。 “-x”选项使用起来简单方便,可以轻松对付大多数的shell调试任务,应把其当作首选的调试手段。

Shell的执行选项除了可以在启动shell时指定外,亦可在脚本中用set命令来指定。 "set -参数"表示启用某选项,"set +参数"表示关闭某选项。有时候我们并不需要在启动时用"-x"选项来跟踪所有的命令行,这时我们可以在脚本中使用set命令,如以下脚本片段所示:
  $LINENO : 代表shell脚本的当前行号,类似于C语言中的内置宏__LINE__
  $FUNCNAME : 函数的名字,类似于C语言中的内置宏__func__,但宏__func__只能代表当前所在的函数名,而$FUNCNAME的功能更强大,它是一个数组变量,其中包含了整个调用链上所有的函数的名字,故变量${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字,而变量${FUNCNAME[1]}则代表调用函数${FUNCNAME[0]}的函数的名字,余者可以依此类推。

参考:https://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/

欢迎投稿、分享转载,转载请保留如下信息:格物躬行博客[https://www.go2do.net]

本文由 [go2do] 原创,本文链接: https://www.go2do.net/linux/how-use-shell-auto-update-github-beginners-guide.html



You may also like...

发表评论

电子邮件地址不会被公开。

本页共执行143次数据库查询,耗时0.344秒,使用内存 1.79 MB