使用 Shell 编写自动部署脚本的一些技巧

写了很久的 Shell 脚本,但总感觉自己写的东西各种 Low,就决定去读一读大佬写的东西。花了一周时间读了各种各样的 Shell 脚本,读完之后觉得大佬们各种牛批,就打算找个地方记一记。

传入参数处理

使用 $# 接受执行 shell 脚本时后置的参数,当传入二级参数时,依靠 shift 让参数或协议左移继续处理,直至处理完成。
while [[ $# > 0 ]];do
    key="$1"
    case $key in
        -p|--proxy)
        PROXY="-x ${2}"
        shift
        ;;
        -h|--help)
        HELP="1"
        ;;
        -f|--force)
        FORCE="1"
        ;;
        -c|--check)
        CHECK="1"
        ;;
        --remove)
        REMOVE="1"
        ;;
        --version)
        VERSION="$2"
        shift
        ;;
        -l|--local)
        LOCAL="$2"
        LOCAL_INSTALL="1"
        shift
        ;;
        --source)
        DIST_SRC="$2"
        shift
        ;;
        --errifuptodate)
        ERROR_IF_UPTODATE="1"
        ;;
        *)
        # unknown option
        ;;
    esac
    shift
done

定义入口函数

Shell 脚本运行时,在不同的机器有不同的效果,比如有的是从上往下执行,有的是短作业优先执行,如果没有定义函数,会造成执行的顺序错乱。
main(){
    #helping information
    [[ "$HELP" == "1" ]] && Help && return
    [[ "$CHECK" == "1" ]] && checkUpdate && return
    [[ "$REMOVE" == "1" ]] && remove && return
}

main

单个参数启动的简化写法

processName=PROCESSNAME
process=PROCESS
appDirectory=/usr/local/bin
logfile=/usr/local/bin/process.log
errfile=/usr/local/bin/process.error

start() {
    echo 'Starting '${processName}'...'
    cd ${appDirectory}
    nohup ./$process >>$logfile 2>>$errfile &
    sleep 1
}

stop() {
    echo 'Stopping '${processName}'...'
    pid=$(/usr/sbin/pidof ${process})
    kill ${pid}
    sleep 1 
}

status() {
    pid=$(/usr/sbin/pidof ${process})
    if [[ "$pid" != "" ]]; then
        echo ${processName}' is running...'
    else
        echo ${processName}' is not running...'
    fi
}

case $1 in
    start|stop|status) "$1" ;;
esac

多彩提示

Linux 自定义终端输出彩色信息非常繁琐,因此可以预定义一些色彩信息,通过函数传值简化写法。
RED="31m"      # Error message
GREEN="32m"    # Success message
YELLOW="33m"   # Warning message
BLUE="36m"     # Info message

colorEcho(){
    COLOR=$1
    echo -e "\033[${COLOR}${@:2}\033[0m"
}

colorEcho ${RED} "Failed to download! Please check your network or try again."

帮助信息

之前看别人写的帮助信息格式各种牛批,读了一些脚本之后发现原来全靠空格对齐…
Help(){
    echo "./install-release.sh [-h] [-c] [--remove] [-p proxy] [-f] [--version vx.y.z] [-l file]"
    echo "  -h, --help            Show help"
    echo "  -p, --proxy           To download through a proxy server, use -p socks5://127.0.0.1:1080 or -p http://127.0.0.1:1082 etc"
    echo "  -f, --force           Force install"
    echo "      --version         Install a particular version, use --version v3.15"
    echo "  -l, --local           Install from a local file"
    echo "      --remove          Remove installed Version"
    echo "  -c, --check           Check for update"
    return 0
}

返回值定义

和 C 相反, Shell 成功返回的 True 为 0,False 为 1 ,因此约定俗成的返回值应该是这样的:
# If not specify, default meaning of return value:
# 0: Success
# 1: System error
# 2: Application error
# 3: Network error

判断系统架构

在不同的系统架构里编译出来的C++程序,性能也是千奇百怪。在需要编译操作的脚本里,判断系统架构比判断包管理重要。
sysArch(){
    ARCH=$(uname -m)
    if [[ "$ARCH" == "i686" ]] || [[ "$ARCH" == "i386" ]]; then
        VDIS="32"
    elif [[ "$ARCH" == *"armv7"* ]] || [[ "$ARCH" == "armv6l" ]]; then
        VDIS="arm"
    elif [[ "$ARCH" == *"armv8"* ]] || [[ "$ARCH" == "aarch64" ]]; then
        VDIS="arm64"
    elif [[ "$ARCH" == *"mips64le"* ]]; then
        VDIS="mips64le"
    elif [[ "$ARCH" == *"mips64"* ]]; then
        VDIS="mips64"
    elif [[ "$ARCH" == *"mipsle"* ]]; then
        VDIS="mipsle"
    elif [[ "$ARCH" == *"mips"* ]]; then
        VDIS="mips"
    elif [[ "$ARCH" == *"s390x"* ]]; then
        VDIS="s390x"
    elif [[ "$ARCH" == "ppc64le" ]]; then
        VDIS="ppc64le"
    elif [[ "$ARCH" == "ppc64" ]]; then
        VDIS="ppc64"
    fi
    return 0
}

判断包管理系统

刚刚开始写 Shell 的时候,一直以为要先判断系统然后再决定包管理,结果每次写 Shell 判断系统的部分写了一大段,直到最近我学会了如下这种技巧。
getPMT(){
    if [[ -n `command -v apt-get` ]];then
        CMD_INSTALL="apt-get -y -qq install"
        CMD_UPDATE="apt-get -qq update"
    elif [[ -n `command -v yum` ]]; then
        CMD_INSTALL="yum -y -q install"
        CMD_UPDATE="yum -q makecache"
    elif [[ -n `command -v zypper` ]]; then
        CMD_INSTALL="zypper -y install"
        CMD_UPDATE="zypper ref"
    else
        return 1
    fi
    return 0
}

判断主机 IP 地址

主要依靠 curl 命令读取一些提供 IP 地址的网站。ip addr 这个命令通常只能获取到机子的内网 IP,因此依靠其他服务器进行判断会比较靠谱。
get_ip() {
    ip=$(curl -s https://ipinfo.io/ip)
    [[ -z $ip ]] && ip=$(curl -s https://api.ip.sb/ip)
    [[ -z $ip ]] && ip=$(curl -s https://api.ipify.org)
    [[ -z $ip ]] && ip=$(curl -s https://ip.seeip.org)
    [[ -z $ip ]] && ip=$(curl -s https://ifconfig.co/ip)
    [[ -z $ip ]] && ip=$(curl -s https://api.myip.com | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}")
    [[ -z $ip ]] && ip=$(curl -s icanhazip.com)
    [[ -z $ip ]] && ip=$(curl -s myip.ipip.net | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}")
}

暂停脚本

这里主要依靠 Linux 的 read 命令暂停程序执行。
pause() {
    read -rsp "$(echo -e "回车键继续或 Ctrl + C 取消.")" -d $'\n'
    echo
}

评论