[shell] plantilla y especificación de script de shell

Referencia: Plantilla de script cShell_operación y mantenimiento@小兵的博客-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

Análisis de parámetros de línea de inicio

Análisis de parámetros con getopts

  • Si una letra va seguida de un ":", significa que la opción de la línea de comandos va seguida de un argumento.
  • Por ejemplo, optsting se escribe como "b:o:h", lo que indica que admite el reconocimiento de opciones -b, -o, -h, y las opciones -b y -o deben ir seguidas de un parámetro.
  • Salida -b pero sin argumentos especificados, vaya aquí :)
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

llamada de parámetro

  • llamada de función normal
function build()
{
    echo "building ..."
}

build 
  • funcion con parametros
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

ejecutar comando de shell

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}
}

comparación de texto

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
}

Obtenga la ruta y el nombre del script del script

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

Extraiga el código del repositorio de códigos

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}
}

Leer el archivo ini de configuración

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}

Archivo ini correspondiente (cfg.ini)

OUTPUT_CFG_PATH=./data

Hora de impresión

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

redirección de salida

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
}

depuración de secuencias de comandos

Artefacto de depuración de secuencias de comandos, imprima lo que ejecuta cada línea de secuencia de comandos

#!/bin/sh -x


Autor: DayDayUppppppp
Enlace: https://www.jianshu.com/p/34c76024c0ef
 

Vim agrega automáticamente encabezados de archivo a los archivos de shell

Modifique el archivo /etc/vimrc

Solo agrega al final

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

especificación de código

(Extracto de: Especificación de código de programación de shell_script de shell que especifica el formato de codificación )

1.2 Principios básicos

  1. Consulte el estilo de código de nomenclatura de la programación principal de shell.
  2. La especificación del código se basa en la herramienta ShellCheck.
  3. Cuando el rendimiento es suficiente, la legibilidad tiene prioridad.

1.3 Predefinido

  1. Camello en minúsculas, como pathName.
  2. Camello en mayúsculas, como PathName.
  3. Escriba en mayúsculas y subraye, como MAX_DEV_CNT=32.
  4. Minúsculas con guión bajo, como path_name=/dev/nvme0.

2. Estilo de código

2.1 Encabezado del archivo

#!/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 Notas

  1. Para comentarios de una sola línea, debe haber un espacio después de #.
# Delete a file in a sophisticated manner.
  1. anotación de función
#######################################
# 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 Sangría

La tecla de tabulación se establece en 4 espacios y la sangría predeterminada es de 4 espacios.

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

2.4 Funciones

definición de la función, no es necesario agregar la modificación de la función por defecto. Las funciones se colocan uniformemente después de las variables globales en el archivo fuente y antes del código ejecutable, y no se coloca ningún código ejecutable entre las funciones. Cuando la función de código es relativamente pequeña, es posible que no se defina la función principal.

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

2.5 Número máximo de líneas

La longitud máxima de una línea de código está limitada a unos 120 caracteres.

2.7 Bucle

let ; do and ; then y while for y if en la misma línea

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 declaración de caso

Los comandos múltiples en opcionales deben dividirse en varias líneas, expresión de patrón, operación y terminador ;; en diferentes líneas.

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

Si la expresión es muy simple, puedes usar el patrón simple:

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 Denominación

  1. El nombre del archivo usa letras minúsculas y guiones bajos, y termina con .sh.
  2. Use letras minúsculas y guiones bajos para los nombres de las funciones y use :: para los nombres de los paquetes.
  3. Los paquetes usan camelCase en minúsculas.
  4. Use letras minúsculas y guiones bajos para los nombres de variables, y use variables locales tanto como sea posible para reducir los conflictos de nombres de variables.
  5. Las constantes usan letras mayúsculas y guiones bajos, y agregan modificadores de solo lectura.
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 Referencias de variables

  1. Para parámetros o variables integradas, se puede omitir {}.
  2. Para las variables de cadena, {} se agrega de manera predeterminada.
  3. Para variables numéricas, las referencias se pueden distinguir de las variables de cadena sin {}.
# 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 Referencias

Por lo general, las citas deben seguir los siguientes principios:
● De manera predeterminada, se recomienda usar comillas para citar cadenas que contengan variables, caracteres de sustitución de comandos, espacios o metacaracteres de shell ●
Si existe un requisito claro de usar expansión sin comillas, las comillas pueden ser se omite
● Las comillas se recomiendan solo cuando las cadenas son palabras, no opciones de comando o nombres de ruta
● No use comillas para números enteros
● Preste especial atención a las reglas de comillas para la coincidencia de patrones en [[
● En ningún caso especial, se recomienda usar $@ en lugar de $*
 

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

# 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:

Práctica de ingeniería Uso recomendado

(Extracto de: https://blog.csdn.net/feihe0755/article/details/126484099)

3.4 Salida de error a STDERR

Todos los mensajes de error deben enviarse a 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 Sustitución de mando

Use la nueva sintaxis $(comando) en lugar de los acentos graves de la sintaxis anterior, la nueva sintaxis es más legible.

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

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

3.6 Prueba de coincidencia de cadenas

Prefiere usar [[ … ]] en lugar de [ … ], prueba, porque no habrá expansión de ruta ni segmentación de palabras entre [[ y ]], por lo que usar [[ … ]] puede reducir los errores y [[ … ]] admite la coincidencia de expresiones regulares, mientras que [ … ] no lo hace.

3.11 Carga de archivos

Se recomienda usar la fuente para cargar archivos externos, y el código es más legible.

# 推荐
source base.sh

# 不推荐
. base.sh

3.12 Tuberías y parámetros

Si no es necesario, es más eficiente usar parámetros directamente sin usar canalizaciones para pasar parámetros.

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

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

3.13 Cálculos matemáticos

Puede usar (()) para cálculos matemáticos simples y awk o bc para cálculos complejos.

# recomendación
(( i = 10 * j + 400 ))

# funciona, pero no se recomienda
i=$(expr 4 + 4)
 

3.14 Comprobación del valor de retorno del comando

Necesidad de verificar el valor de retorno del comando

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 Comandos internos y externos

Hay algunos comandos que admiten tanto herramientas de comandos externas como la sintaxis integrada de Shell. Se recomienda utilizar los comandos internos integrados para una mayor eficiencia.

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

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

4. suplemento

  1. Se recomienda utilizar ShellCheck. VS Code puede descargar el complemento ShellCheck para detectar automáticamente las especificaciones del código.
  2. Referencia Google Shell Style Guild .

Supongo que te gusta

Origin blog.csdn.net/bandaoyu/article/details/130178173
Recomendado
Clasificación