[shell] шаблон сценария оболочки и спецификация

Ссылка: сценарий cShell template_operation and Maintenance@小兵的博客-CSDN Blog

 

#!/bin/bash
################################文件头############################
# Copyright 2022, xxxxxx Co. Ltd.
# All rights reserved.
# FileName:    case001.sh
# Description: first case for test.
# Author:      Michael
# http://www.xxxxxx.com 
# Revision: 1.0.0
#################################################################

set -e            	#打开异常退出功能
# set -x            #打开Debug功能

######################定义变量######################
source /etc/profile		#避免用contab、ansible、Jenkins执行shell脚本出现环境变量不对的问题
WORKDIR="$(cd $(dirname "$0") || exit 1;pwd)"		#脚本所在路径
echo "Current Excute: bash ${WORKDIR}/$0 $@"

function_dir=${WORKDIR}/myfunction.lib

#创建日志文件
if [[ ! -d ${WORKDIR}/logs ]];then
    mkdir -p ${WORKDIR}/logs
else
    rm -f ${WORKDIR}/logs/*.log
fi
[[ $UID -ne 0 ]] && echo "[ERROR] Please Use root Excute......" && exit 1

#输出信息
# ${FUNCNAME[0]代表当前函数名,$LINENO代表当前代码行号
echo "$(date "+%Y-%m-%d %T.%N")[ERROR ${FUNCNAME[0]}:$LINENO] Invalid Param"
echo "$(date "+%Y-%m-%d %T.%N")[INFO]:Install Success"



#加载函数库
if [[ -f "$function_dir" ]];then
 	source $function_dir
else
	echo -e "\033[31m函数库不存在\033[0m"
	exit 71
fi




######################功能函数######################
#检查环境
Check_Env() {
    echo "[INFO] Begin Check_Env..."
    [[ $UID -ne 0 ]] && echo "[ERROR] Please Use Admin(root) Excute......" && exit 1
    #检查命令是否存在
    for cmd_bin in curl mvn
    do
        if ! command -v ${cmd_bin} &> /dev/null;then
            echo "[ERROR] ${cmd_bin} command Not Exist" && exit 1
        fi
    done
    echo "[INFO] Check_Env Success"
}



#帮助信息
Help() {
	cat << EOF
Usage: 
=======================================================================
optional arguments:
	-h	提供帮助信息
	-num	虚拟机编号
EXAMPLE:
	bash $0 -num 10 web1 eth0 192.168.4.1/24	
EOF
}



#######################主函数#######################
#参数校验
[[ $# -ne 1 ]] && echo "[ERROR] Invalid Param!!! eg:bash $0 ansible_path" && exit 1
[[ $# -le 5 ]] && echo "[ERROR] Invalid Param!!!,Please Excute:bash $0 -h" && exit 1


#主函数
main() {
    Print
    cecho 32 "开始执行......"
    echo "1.本地源"
    echo "2.网络源"
    read -p  "请选择:" choice
    case ${choice} in
    1)
        Conf_Apt;;
    2)
        echo "1.ubuntu14.04"
        echo "2.ubuntu16.04"
        echo "3.ubuntu18.04"
        read -p "请选择系统版本:" choice
        case ${choice} in
        1)
            Conf_Ubuntu14.04;;
        2)
            Conf_Ubuntu16.04;;
        3)
            Conf_Ubuntu18.04;;
        *)
            cecho 31 "Invalid option!"
        esac
        ;;	    
    *)
        cecho 31 "Invalid option!"
    esac		
}

main

if [[ $# -eq 0 ]];then
    Excute_All
elif [[ "$1" == "-c" -a "$#" -eq 2 ]];then
    case $2 in
    system)
        Init_System;;
    *)
        cecho 31 "Invalid option:bash `basename $0` [-h]"
    esac
elif [[ "$1" == "-h" ]];then
    Help
else
    Help && exit 1
fi

#读取短参数
[[ $# -eq 0 ]] && HELP
while getopts :hnum::a: ARGS
do
	case $ARGS in
	h)
		HELP;;
	nu|m)
		Name=rh7_node$OPTARG;;	
	\?)
		cecho 31 "Invalid option:bash `basename $0` [-h]"
	esac
done

Начать парсинг параметра строки

Разбор параметров с помощью getopts

  • Если за буквой следует «:», это означает, что за параметром командной строки следует аргумент.
  • Например, optsting записывается как «b:o:h», что указывает на то, что он поддерживает распознавание опций -b, -o, -h, а за опциями -b и -o должен следовать параметр.
  • Вывод -b, но аргументы не указаны, иди сюда :)
while getopts "b:o:h" opt_name
do
    case $opt_name in
        b) echo "-b Option is recognized, argument=$OPTARG"
            build
           ;;
        o) echo "-o Option is recognized, argument=$OPTARG"
            update $OPTARG
           ;;
        h) echo "-h Option is recognized"
            package
           ;;
        :) echo "$OPTARG Option need a argument"   # 比如输出-b 但是又没有指定参数,就走到这里
            print_help
           ;;
    esac
done

вызов параметра

  • обычный вызов функции
function build()
{
    echo "building ..."
}

build 
  • функция с параметрами
function update()
{
    update_mode=$1
    # “STRING” 的长度为零则为真
    if [ -z "${update_mode}" ]
    then
        echo ">> ERROR: build mode required."
        return 1
    fi

    if [[ ${update_mode} != "release" ]] && [[ ${update_mode} != "debug" ]]
    then
        echo ">> ERROR: invalid build mode: ${update_mode}"
        return 1
    fi

    if [ ${update_mode} == "release" ]
    then
        echo ">>>> relese update mode ..."
    fi

    if [ ${update_mode} == "debug" ]
    then
        echo ">>>> debug update mode ..."
    fi
    return 0
}

update debug

выполнить команду оболочки

function build_svr() {
    core=$(grep -c ^processor /proc/cpuinfo)
    echo ${core}

    res=$(make svr)
    if [ $? != 0 ] 
    then 
        echo "build svr fail"
    fi
    echo "build svr successfully"
    echo ${res}
}

сравнение текста

function update() {
    update_mode=$1
    # # “STRING” 的长度为零则为真
    if [ -z "${update_mode}" ]
    then
        echo ">> ERROR: build mode required."
        return 1
    fi

    if [[ ${update_mode} != "release" ]] && [[ ${update_mode} != "debug" ]]
    then
        echo ">> ERROR: invalid build mode: ${update_mode}"
        return 1
    fi

    if [ ${update_mode} == "release" ]
    then
        echo ">>>> relese update mode ..."
    fi

    if [ ${update_mode} == "debug" ]
    then
        echo ">>>> debug update mode ..."
    fi
    return 0
}

Получить путь и имя скрипта скрипта

SCRIPT_DIR="$(cd `dirname $0`; pwd)"
SCRIPT_NAME=$(basename $0)
OUTPUT_PATH=${SCRIPT_DIR}/data

function show_path() {
    echo ${SCRIPT_DIR}
    echo ${SCRIPT_NAME}

    cd ${OUTPUT_PATH}
    echo "hello" > 1.log
    
    cd ${SCRIPT_DIR}
}

show_path

Вытащите код из репозитория кода

function svn_checkout() {
    
    if [ ! -d ${OUTPUT_PATH} ]
    then
        mkdir ${OUTPUT_PATH}
        echo "create dir"
    fi
    echo "dir created"

    rm -rf ${OUTPUT_PATH}
    cd ${OUTPUT_PATH}
    svn checkout ${SVN_URL} . --username ${USERNAME} --password ${PASSWORD}
    cd ${SCRIPT_DIR}
}

Чтение ini-файла конфигурации

SCRIPT_DIR="$(cd `dirname $0`; pwd)"
SCRIPT_NAME=`basename $0`
OUTPUT_PATH=${SCRIPT_DIR}/data
SHELL_CFG=${SCRIPT_DIR}/cfg.ini

function load_cfg() {

    if [ -f "${SHELL_CFG}" ]
    then
        source ${SHELL_CFG}
    fi

    echo ${OUTPUT_CFG_PATH}
}

load_cfg

echo ${OUTPUT_CFG_PATH}

Соответствующий ini-файл (cfg.ini)

OUTPUT_CFG_PATH=./data

Время печати

print_date() {
    echo "========date========" 
    date 
}

перенаправление вывода

LOG_FILE="build.log"
ERR_FILE="build.err"
TEE="/usr/bin/tee"

set_env() {
    if [ ${SILENT} == 'true' ] 
    then
        exec 1>${LOG_FILE}
        exec 2>${ERR_FILE}
    else
        NPIPE=/tmp/$$.tmp
        trap "rm -f ${NPIPE}" EXIT
        mknod $NPIPE p
        ${TEE} <$NPIPE ${LOG_FILE} &
        exec 1>&-         # &- 关闭标准输出  # n&- 表示将 n 号输出关闭
        exec 1>$NPIPE

        exec 2>${ERR_FILE}
    fi
}

отладка сценария

Артефакт отладки скрипта, распечатайте, что выполняет каждая строка скрипта

#!/bin/sh -x


Автор: DayDayUppppppp
Ссылка: https://www.jianshu.com/p/34c76024c0ef
 

Vim автоматически добавляет заголовки файлов в файлы оболочки

Измените файл /etc/vimrc

Просто добавьте в конец

set ignorecase 
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()"

func SetTitle()
if expand("%:e") == 'sh'
call setline(1, "#!/bin/bash")
call setline(2,"##############################################################")
call setline(3, "# File Name:".expand("%"))
call setline(4, "# Version:V1.0")
call setline(5, "# Author:quanheng")
call setline(6, "# Organization:www.quanheng77.top")
call setline(7, "# Desc:")
call setline(8,"##############################################################")
endif
endfunc

спецификация кода

(Отрывок из: Спецификация кода сценария shell_programming_shell, указывающего формат кодирования )

1.2. Основные принципы

  1. Обратитесь к стилю именования кода основного программирования оболочки.
  2. Спецификация кода основана на инструменте ShellCheck.
  3. Там, где производительности достаточно, читабельность имеет приоритет.

1.3. Предопределенные

  1. Верблюжий регистр нижнего регистра, например pathName.
  2. Верблюжий регистр в верхнем регистре, например PathName.
  3. Используйте заглавные буквы и подчеркивание, например MAX_DEV_CNT=32.
  4. Нижний регистр с подчеркиванием, например path_name=/dev/nvme0.

2. Стиль кода

2.1 Заголовок файла

#!/bin/bash
################################################################ 
# Copyright 2022, xxxxxx Co. Ltd.
# All rights reserved.
# FileName:    case001.sh
# Description: first case for test.
# Author:      Michael
# http://www.xxxxxx.com 
# Revision: 1.0.0
#################################################################

2.2. Примечания

  1. Для однострочных комментариев после # должен быть пробел.
# Delete a file in a sophisticated manner.
  1. аннотация функции
#######################################
# Get configuration directory.
# Globals:
#   SOMEDIR
# Arguments:
#   None
# Outputs:
#   Writes location to stdout
#######################################
get_dir() {
  echo "${SOMEDIR}"
}

#######################################
# Delete a file in a sophisticated manner.
# Arguments:
#  $1: File to delete, a path.
# Returns:
#   0 if thing was deleted, otherwise non-zero.
#######################################
del_thing() {
  rm "$1"
}

2.3. Отступ

Для клавиши табуляции установлено значение 4 пробела, а отступ по умолчанию — 4 пробела.

main() {
    # 缩进4个空格
    say="hello World."
    echo "${say}"
}

2.4 Функции

определение функции, нет необходимости добавлять модификацию функции по умолчанию. Функции равномерно размещаются после глобальных переменных в исходном файле и перед исполняемым кодом, и между функциями не размещается исполняемый код. Когда функция кода относительно мала, основная функция может быть не определена.

main() {
    echo "hello World."
    exit 0
}

2.5 Максимальное количество строк

Максимальная длина строки кода ограничена примерно 120 символами.

2.7 Цикл

пусть ; делать и ; тогда и пока для и если на одной линии

for dir in "${dirs_to_cleanup[@]}"; do
  if [[ -d "${dir}/${ORACLE_SID}" ]]; then
    log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
    rm "${dir}/${ORACLE_SID}/"*
    if (( $? != 0 )); then
      error_message
    fi
  else
    mkdir -p "${dir}/${ORACLE_SID}"
    if (( $? != 0 )); then
      error_message
    fi
  fi
done

2.8 постановка дела

Несколько команд в параметрах должны быть разбиты на несколько строк, выражение шаблона, операция и терминатор ;; в разных строках.

case "${expression}" in
    a)
        variable="..."
        some_command "${variable}" "${other_expr}" ...
        ;;
    absolute)
        actions="relative"
        another_command "${actions}" "${other_expr}" ...
        ;;
    *)
        error "Unexpected expression '${expression}'"
        ;;
esac

Если выражение очень простое, вы можете использовать простой шаблон:

verbose='false'
aflag=''
bflag=''
files=''
while getopts 'abf:v' flag; do
    case "${flag}" in
        a) aflag='true' ;;
        b) bflag='true' ;;
        f) files="${OPTARG}" ;;
        v) verbose='true' ;;
        *) error "Unexpected option ${flag}" ;;
    esac
done

2.9 Именование

  1. В имени файла используются строчные буквы и знаки подчеркивания, и оно заканчивается на .sh.
  2. Используйте строчные буквы и символы подчеркивания для имен функций и используйте :: для имен пакетов.
  3. Пакеты используют верхний регистр camelCase.
  4. Используйте строчные буквы и знаки подчеркивания для имен переменных и максимально используйте локальные переменные, чтобы уменьшить конфликты имен переменных.
  5. В константах используются прописные буквы и символы подчеркивания, а также добавляются модификаторы только для чтения.
ysUtil::is_boot(){
    return 1
}

get_path() {
    echo "/dev/nvme0"
}

readonly MAX_PATH_LEN=256
test_dir() {
    local path_name
    path_name="$(get_path)" || return 1
    if [ ${#path_name} -gt $MAX_PATH_LEN ]; then 
        return 0
    fi
    
    return 1
}

2.10 Ссылки на переменные

  1. Для параметров или встроенных переменных {} можно опустить.
  2. Для строковых переменных {} добавляется по умолчанию.
  3. Для числовых переменных ссылки можно отличить от строковых переменных без {}.
# Special variables
echo $1 $2 $3
echo $? $!

# 当位置变量大于等于10,则必须有大括号:
echo "many parameters: ${10}"

# 当出现歧义时,必须有大括号:
# Output is "a0b0c0"
set -- a b c
echo "${1}0${2}0${3}0"

# 使用变量扩展赋值时,必须有大括号:
DEFAULT_MEM=${DEFUALT_MEM:-"-Xms2g -Xmx2g -XX:MaxDirectMemorySize=4g"}

# 其他常规变量的推荐处理方式:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read f; do
    echo "file=${f}"
done < <(ls -l /tmp)

2.11 Ссылки

Цитаты обычно должны следовать следующим принципам:
● По умолчанию рекомендуется использовать кавычки для заключения в кавычки строк, содержащих переменные, символы подстановки команд, пробелы или метасимволы оболочки.
● Если существует явное требование использовать расширение без кавычек, кавычки можно опущен
● Кавычки рекомендуются только в том случае, если строки являются словами, а не параметрами команды или именами путей
● Не используйте кавычки для целых чисел
● Обратите особое внимание на правила кавычек для сопоставления с образцом в [[
● Ни в каких особых случаях не рекомендуется использовать $@ вместо $*
 

# '单引号' 表示禁用变量替换
# "双引号" 表示需要变量替换

# 1: 命令替换需使用双引号
flag="$(some_command and its args "$@" 'quoted separately')"

# 2:常规变量需使用双引号
echo "${flag}"

# 3:整数不使用引号
value=32
# 示例4:即便命令替换输出为整数,也需要使用引号
number="$(generate_number)"
echo "$value"

# 5:单词可以使用引号,但不作强制要求
readonly USE_INTEGER='true'

# 6:输出特殊符号使用单引号或转义
echo 'Hello stranger, and well met. Earn lots of $$$'
echo "Process $$: Done making \$\$\$."

# 7:命令参数及路径不需要引号
grep -li Hugo /dev/null "$1"

# 8:常规变量用双引号,ccs可能为空的特殊情况可不用引号
git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"}

# 9:正则用单引号,$1可能为空的特殊情况可不用引号
grep -cP '([Ss]pecial|\|?characters*)$' ${1:+"$1"}

# 10:位置参数传递推荐带引号的"$@",所有参数作为单字符串传递用带引号的"$*"
# content of t.sh
func_t() {
    echo num: $#
    echo args: 1:$1 2:$2 3:$3
}

func_t "$@"
func_t "$*"
# 当执行 ./t.sh a b c 时输出如下:
num: 3
args: 1:a 2:b 3:c
num: 1
args: 1:a b c 2: 3:

Применение, рекомендуемое инженерной практикой

(Выдержка из: https://blog.csdn.net/feihe0755/article/details/126484099)

3.4 Вывод ошибок в STDERR

Все сообщения об ошибках должны выводиться в STDERR.

err() {
  echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}

if ! do_something; then
  err "Unable to do_something"
  exit 1
fi

3.5 Подстановка команд

Используйте новый синтаксис $(command) вместо обратных кавычек старого синтаксиса, новый синтаксис более удобочитаем.

# good
var=$(cat name.txt|grep "100")

# bad
var=`cat name.txt|grep "100"`

3.6 Проверка соответствия строк

Предпочитаю использовать [[ … ]] вместо [ … ], test, потому что не будет расширения пути или сегментации слов между [[ и ]], поэтому использование [[ … ]] может уменьшить количество ошибок, а [[ … ]] поддерживает сопоставление регулярных выражений, а [ … ] — нет.

3.11 Загрузка файла

Рекомендуется использовать исходный код для загрузки внешних файлов, и код становится более читабельным.

# 推荐
source base.sh

# 不推荐
. base.sh

3.12 Конвейеры и параметры

Если в этом нет необходимости, более эффективно использовать параметры напрямую, не используя конвейеры для передачи параметров.

# 推荐
grep "main" main.cpp
wc -l log.config

# 不推荐
cat main.cpp | grep "main"
cat log.config | wc -l

3.13 Математические расчеты

Вы можете использовать (()) для простых математических вычислений и awk или bc для сложных вычислений.

# рекомендация
(( i = 10 * j + 400 ))

# работает, но не рекомендуется
i=$( expr 4 + 4 )
 

3.14 Проверка возвращаемого значения команды

Необходимо проверить возвращаемое значение команды

if ! mv "${file_list[@]}" "${dest_dir}/"; then
  echo "Unable to move ${file_list[*]} to ${dest_dir}" >&2
  exit 1
fi

# Or
mv "${file_list[@]}" "${dest_dir}/"
if (( $? != 0 )); then
  echo "Unable to move ${file_list[*]} to ${dest_dir}" >&2
  exit 1
fi

3.15 Внутренние и внешние команды

Некоторые команды поддерживают как внешние командные инструменты, так и встроенный синтаксис Shell.Для большей эффективности рекомендуется использовать встроенные внутренние команды.

# 推荐使用内建的算术扩展
addition=$((${X} + ${Y}))
# 推荐使用内建的字符串替换
substitution="${string/#foo/bar}"

# 不推荐调用外部命令进行简单的计算
addition="$(expr ${X} + ${Y})"
# 不推荐调用外部命令进行简单的字符串替换
substitution="$(echo "${string}" | sed -e 's/^foo/bar/')"

4. дополнение

  1. Рекомендуется использовать ShellCheck.VS Code может загрузить подключаемый модуль ShellCheck для автоматического определения спецификаций кода.
  2. Ссылка на Гильдию стилей Google Shell .

Guess you like

Origin blog.csdn.net/bandaoyu/article/details/130178173