Shell编程之代码规范

1. 前言

1.1. 目的

代码编写规范,主要包括两部分,代码风格和最佳工程实践。在代码风格上,没有一种代码编写风格是最好的,更重要的是与已有项目代码风格保持一致,以提高项目团队整体对代码的可读性。在工程实践上,统一一些开发流程,提升团队的协作效率,另外就是最佳工程实践规范,以提高代码的性能、可靠性以及可读性。

1.2. 基本原则

  1. 参考主流Shell编程命名代码风格。
  2. 代码规范借鉴工具ShellCheck。
  3. 在性能足够的情况下,可读性优先考虑。

1.3. 预定义

  1. 小写驼峰命名,如pathName。
  2. 大写驼峰命名,如PathName。
  3. 大写加下划线,如MAX_DEV_CNT=32。
  4. 小写加下划线,如path_name=/dev/nvme0。

2. 代码风格

2.1. 文件头

必须使用#!/bin/bash指定bash解释器,因为这是应用最广泛的解释器。版权及作者信息默认也需要添加。

#!/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. 缩进

tab键设置为4个空格,默认缩进为4个空格。

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

2.4. 函数

function定义,默认不需要加function修饰。函数统一放在源文件的全局变量之后,可执行代码之前,函数之间不放置可执行代码。代码功能比较少时,可以不定义main函数。

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

2.5. 最大行数

代码一行的最大长度限定在120个字符左右。

2.6. 代码换行

  1. 长字符串换行
long_string="I am an exceptionally\
long string."
echo "${long_string}"
  1. 多个管道或逻辑操作(&& ||等)
# Long commands
command1 \
  | command2 \
  | command3 \
  | command4

2.7. 循环

让; do和; then和while for 以及if在同一行

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语句

可选项中的多个命令应该被拆分成多行,模式表达式、操作和结束符 ;; 在不同的行。

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. 包中使用小写字母驼峰形式。
  4. 变量名使用小写字母加下划线的形式,局部变量尽量使用local修饰,减少变量名冲突。
  5. 常量使用大写字母加下划线形式,并且添加readonly修饰。
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. 引用

引用通常情况下应遵循以下原则:
● 默认情况下推荐使用引号引用包含变量、命令替换符、空格或shell元字符的字符串
● 在有明确要求必须使用无引号扩展的情况下,可不用引号
● 字符串为单词类型时才推荐用引号,而非命令选项或者路径名
● 不要对整数使用引号
● 特别注意 [[ 中模式匹配的引号规则
● 在无特殊情况下,推荐使用 $@ 而非 $*

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

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

3. 最佳工程实践

3.1. 适用场景

Shell是一种Unix-like系统自带的脚本语言,在Windows上可以使用Cygwin等模拟器来运行。Shell的功能比较简单,其强大主要体现在与其配套的大量命令行工具。

  1. 需要调用其他应用程序,有许多文本操作,但是没有太多数据处理,那么Shell是一个好的选择。
  2. 如果有复杂的计算,或者对性能有强烈的追求,那么Shell不是好的选择。

3.2. 文件类型

Shell脚本只能以.sh为后缀名,并且脚本库文件必须设置为非可执行类型。

3.3. 文件编码

源文件编码格式为UTF-8。

3.4. Error输出到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="$(command "$(command1)")"

# bad
var="`command \`command1\``"

3.6. 字符串匹配测试

优先使用[[ … ]],而不是[ … ], test,因为在 [[ 和 ]] 之间不会出现路径扩展或单词切分,所以使用 [[ … ]] 能够减少犯错,且 [[ … ]] 支持正则表达式匹配,而 [ … ] 不支持。

# 1:正则匹配,注意右侧没有引号
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
    echo "Match"
fi

# 2:严格匹配字符串"f*"(本例为不匹配)
if [[ "filename" == "f*" ]]; then
    echo "Match"
fi

# 3:[]中右侧不加引号将出现路径扩展,如果当前目录下有f开头的多个文件将报错[: too many arguments
if [ "filename" == f* ]; then
    echo "Match"
f

3.7. 字符串测试

# 推荐
if [[ "${my_var}" == "some_string" ]]; then
  do_something
fi

# 代码可行,但是不推荐
if [[ "${my_var}" = "val" ]]; then
  do_something
fi

# 使用-z或-n显式测试字符串
if [[ -z "${my_var}" ]]; then
  do_something
fi

# 不推荐
if [[ "${my_var}" ]]; then
  do_something
fi

# 代码可用,但是不推荐
if [[ "${my_var}" == "" ]]; then
  do_something
fi

3.8. 数字比较

# 推荐
if (( my_var > 3 )); then
  do_something
fi

# 推荐
if [[ "${my_var}" -gt 3 ]]; then
  do_something
fi

# 可行但是不推荐
if [[ "${my_var}" > 3 ]]; then
  # True for 4, false for 22.
  do_something
fi

3.9. 慎用管道连接while

管道连接while之后,命令是在子shell中执行,因为子Shell无法修改父Shell的变量,导致难以调试。
使用for循环代替。

# 不推荐
last_line='NULL'
your_command | while read line; do
    last_line="${line}"
done

# 推荐
total=0
for value in $(command); do
    total+="${value}"
done

3.10. 数组

使用新式语法赋值。

# 推荐
declare -a flags
flags=(--foo --bar='baz')
flags+=(--greeting="Hello ${name}")
mybinary "${flags[@]}"

# 不推荐
flags='--foo --bar=baz'
flags+=' --greeting="Hello world"'  # This won’t work as intended.
mybinary ${flags}

3.11. 文件加载

载入外部文件推荐使用source,代码可读性更好。

# 推荐
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 Style Guild
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前言 10 场景说明 11 基础知识简介 14 Linux 14 Linux产生与发展 14 Linux特点和优势 15 Shell 16 Shell脚本语言与编译型语言的差异 17 Shell分类 18 Shell使用 19 项目一 初识Linux Shell 20 [学习目标] 20 任务1.1 构建开发环境 22 任务1.2 vi/vim编辑器 33 1.2.1 vim使用实例 34 1.2.2 vim按键说明 36 任务1.3 Shell常用命令 41 1.3.1 基础命令 41 1.3.2 文件、目录操作命令 42 1.3.3 系统管理命令 52 1.3.4 关机/重启/退出 53 1.3.5 安装操作命令 53 1.3.6 用户操作命令 54 1.3.7 网络下载命令 55 1.3.8 服务操作命令 57 任务1.4 Shell脚本 58 1.4.1 第一个shell脚本:Hello World 58 1.4.2 脚本介绍 59 1.4.3 shell程序结构 59 1.4.4 shell脚本运行 60 任务1.5 Shell案例:打印有色彩的文本 61 任务1.6 Shell案例:图案打印 62 练习(每题10分,共计100分) 66 项目二 Shell变量 67 [学习目标] 67 任务2.1 Linux环境变量 68 2.1.1 查看环境变量 69 任务2.2 Shell变量 70 2.2.1 定义变量 70 2.2.2 使用变量 70 2.2.3 只读变量 71 2.2.4 删除变量 72 任务2.3 特殊符号 72 任务2.4 Shell案例:显示系统信息: 78 练习(每题12.5分,共计100分) 80 项目三 Shell传递参数 81 [学习目标] 81 任务3.1 Shell传递参数 82 任务3.2 Shell案例:参数比较 85 任务3.3Shell案例:通过参数描述变量 86 练习(前两题每题30分,第三题40分,共计100分) 87 项目四 Shell数组 89 [学习目标] 89 任务4.1 定义数组 90 任务4.2 使用数组 90 任务4.3 Shell案例:数组的使用 92 练习(100分) 95 项目五 Shell运算符 96 [学习目标] 96 任务5.1 Shell基本运算符 97 5.1.1 运算符 97 5.1.2 算术运算符 97 5.1.3 关系运算符 99 5.1.4 布尔运算符 101 5.1.5 逻辑运算符 103 5.1.6 字符串运算符 104 5.1.7 文件测试运算符 106 任务5.2 Shell案例:计算器 109 练习(每题25分,共计100分) 111 项目六 Shell命令输出 112 [学习目标] 112 任务6.1 Shell echo命令 113 任务6.2 Shell printf命令 116 任务6.3 Shell test命令 119 任务6.4 Shell案例:查看系统资源使用情况 122 练习(每题20分,共计100分) 125 项目七 Shell流程控制 126 [学习目标] 126 任务7.1 Shell流程控制 127 7.1.1 if 判断语句 127 7.1.2 for 循环语句 130 7.1.3 while 循环语句 131 7.1.4 until 循环语句 134 7.1.5 case多选语句 135 7.1.6 跳出循环体 137 任务7.2 Shell案例:重复执行命令和简单计算器 139 任务7.3 Shell案例:使用三种循环结构打印九九乘法表 141 练习(前两题每题30分,第三题40分,共计100分) 144 项目八 Shell函数 146 [学习目标] 146 任务8.1 Shell函数 147 8.1.1 Shell中函数的定义 147 8.1.2 函数参数 150 任务8.2 Shell案例 151 8.2.1 指定多个文件的行数 151 8.2.2 当前目录的所有文件 153 练习(每题50分,共计100分) 156 项目九 Shell输入/输出重定向 158 [学习目标] 158 任务9.1 linux文件描述符 159 任务9.2 Shell输入/输出重定向 160 9.2.1 输出重定向 160 9.2.2 输入重定向 163 9.2.3 重定向深入讲解 164 任务9.2 Shell案例:通过重定向测试批量添加ip 166 练习(每题25分,共计100分) 169 项目十 Shell文件包含 171 [学习目标] 171 任务10.1 Shell文件包含 172 任务10.2 Shell案例:查找最大文件 172 练习(每题50分,共计100分) 174 项目十一 Shell文本处理命令 175 [学习目标] 175 任务11.1 学习和使用grep 176 任务11.2 学习和使用sed 179 任务11.3 学习和使用cut 183 任务11.4 学习和使用awk 185 练习(第一题10分,第2-7每题15分,共计100分) 188 项目十二 Shell的调试 189 [学习目标] 189 任务12.1 使用trap命令 190 任务12.2 使用tee命令 192 任务12.3 使用“调试钩子” 193 任务12.4 使用Shell的执行选项 194 练习(每题50分,共计100分) 196 项目十三 Shell综合案例一(lnmp+wordpress) 197 [学习目标] 197 任务13.1 手动安装Linux Nginx Mysql PHP和WordPress 198 13.1.1 准备工作 198 13.1.2 安装Mysql5.6.29 199 13.1.3 安装PHP5.5.12 202 13.1.4 安装Nginx1.10 205 13.1.5 安装WordPress 206 13.1.6 启动网页 207 任务13.2 脚本安装Linux Nginx Mysql PHP 和WordPress 207 13.2.1 初始化安装脚本编写 207 13.2.2 Mysql安装脚本的编写 209 13.2.3 PHP安装脚本的编写 211 13.2.4 Nginx安装脚本的编写 213 13.2.5 WordPress安装脚本的编写 214 13.2.6 验证wordpress 216 练习(每题50分,共计100分) 218 项目十四 shell综合案例二(安装Openstack) 219 [学习目标] 219 任务14.1 手动安装Openstack 220 什么是Openstack 220 14.1.1基本环境准备 222 14.1.2 安装mysql数据库服务 225 14.1.3 安装keystone认证服务 226 14.1.4 安装glance镜像服务 230 14.1.5 安装nova计算服务 233 14.1.6 安装neutron网络服务 238 14.1.7 安装dashboard服务 248 任务14.2 脚本安装openstack 250 14.2.1 基本环境脚本的编写 255 14.2.2 mysql脚本的编写 256 14.2.3 keystone脚本的编写 258 14.2.4 glance脚本的编写 263 14.2.5 nova脚本的编写 266 14.2.6 neutron脚本的编写 270 14.2.7 dashboard脚本的编写 279 14.2.8 验证登录,使用openstack 280 附录Shell编码规范说明 295 1. 什么时候使用shell 295 2.Shell文件和解释器调用 295 3.环境 296 4.注释 296 5.格式 298 6.特性及错误 304 7.命名转换 308 8.调用命令 311

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值