shell编程之代码规范(代码片段)

-飞鹤- -飞鹤-     2022-10-22     613

关键词:

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 "$10$20$30"

# 使用变量扩展赋值时,必须有大括号:
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

编程规范之书写规范(代码片段)

目录编程规范之书写规范1.每个缩进层级使用4个空格2.每行最多79个字符3.顶层函数或类的定义之间空两行4.采用UTF-8编码文件(通用编码格式)5.每行只使用import导入一个模块(分组标准库、三方库和本地库)6.在小括号、中括号... 查看详情

linux之shell编程(代码片段)

...序设计语言。Shellscript是一种为shell编写的脚本程序。Shell编程一般指shell脚本编程,不是指开发shell自身。Shell编程跟java 查看详情

linux之shell编程(代码片段)

...序设计语言。Shellscript是一种为shell编写的脚本程序。Shell编程一般指shell脚本编程,不是指开发shell自身。Shell编程跟java 查看详情

linux之shell脚本编程(代码片段)

Linux之shell脚本编程编程介绍shell介绍shell脚本编程介绍shell脚本命令shell编程基础知识编程介绍计算机编程的本质:输入、运算、输出编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言文... 查看详情

规范很重要!!学shell编程之前不得不了解的一些规则(代码片段)

Shell编程规范和变量一、Shell脚本概述1.1、Shell脚本的概念将要执行的命令按顺序保存到一个文本文件;给该文件可执行权限;可结合各种Shell控制语句以完成更复杂的操作。1.2、Shell脚本应用场景重复性操作交互性任务批... 查看详情

规范很重要!!学shell编程之前不得不了解的一些规则(代码片段)

Shell编程规范和变量一、Shell脚本概述1.1、Shell脚本的概念将要执行的命令按顺序保存到一个文本文件;给该文件可执行权限;可结合各种Shell控制语句以完成更复杂的操作。1.2、Shell脚本应用场景重复性操作交互性任务批... 查看详情

shell从入门到精通(32)优秀的shell编程习惯和规范(代码片段)

...maybe超过100行或者更多),建议使用其他结构化的编程语言。2、如果考虑性能,建议使用其他语言开头有“蛇棒”所谓shebang其实就是在很多脚本的第一行出现的以”#!”开头的注释,他指明了当我们没有指定解释... 查看详情

shell从入门到精通(32)优秀的shell编程习惯和规范(代码片段)

...maybe超过100行或者更多),建议使用其他结构化的编程语言。2、如果考虑性能,建议使用其他语言开头有“蛇棒”所谓shebang其实就是在很多脚本的第一行出现的以”#!”开头的注释,他指明了当我们没有指定解释... 查看详情

shell编程shell中的流程控制之if语句(代码片段)

系列文章【Shell编程】Shell中的正则表达式【Shell编程】字符截取命令cut、printf命令【Shell编程】字符截取命令awk、sed命令【Shell编程】字符处理命令sort和wc【Shell编程】条件判断目录系列文章单分支if条件语句实例如何提取出来根... 查看详情

shell编程之变量定义(代码片段)

shell编程之变量定义变量介绍变量定义变量分类本地变量环境变量全局变量内置变量扩展:其他变量变量取值变量介绍计算机中的单位: 1B=8b 1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB 1PB=1024TB 1EB=1024TB b:... 查看详情

shell编程之awk(数据筛选与处理)(代码片段)

shell编程之awk(数据筛选与处理)awk命令介绍awk语法awk的基本应用awk对字段(列)的提取awk对记录(行)的提取awk程序的优先级awk高级应用-F命令选项awk定义变量(-v命令)awk定义数组awk运算awk比较... 查看详情

shell编程之免交互(eofexpect使用)(代码片段)

shell编程之免交互一、HereDocument免交互概念1.结构2.EOF示例:1、免交互方式实现对行数的统计,将要统计的内容置于标记“EOF”之间,直接将内容传给wc-l来统计2、通过read命令接收输入并打印,输入值是两个EOF标记... 查看详情

linux之shell编程(11)--shell函数实例演示(代码片段)

一.Shell函数介绍前言前面讲了Shell中的一些基础内容,还没有看过的同学可以看我往期的文章,因为那些内容是Shell函数的前提。另外,Shell函数我会着重讲解,会有很多系列,也会有很多实例演示,让大家... 查看详情

运维linuxshell编程之函数使用(代码片段)

前言使用linux的shell编程,可以说函数是非常重要的内容,也是在编写各类shell脚本的时候经常用到的,本篇将介绍下函数相关的使用。shell函数分类系统函数自定义函数系统函数系统函数为linux自带的函数,可以在shell... 查看详情

shell之函数(代码片段)

...直接调取即可。Shell中的函数和C++、Java、Python、C#等其它编程语言中的函数类似,只是在语法细节有所差别。 函数定义Shell函数定义的语法格式如下:functionname()statements[returnvalue] 语法说明:function是 查看详情

php契约contracts之面向接口编程(代码片段)

什么是契约?契约就是所谓的面向接口编程。我们拿之前的例子说如果不使用接口会有什么问题?//定义写日志的接口规范interfaceLogpublicfunctionwrite();//文件记录日志classFileLogimplementsLogpublic 查看详情

php契约contracts之面向接口编程(代码片段)

什么是契约?契约就是所谓的面向接口编程。我们拿之前的例子说如果不使用接口会有什么问题?//定义写日志的接口规范interfaceLogpublicfunctionwrite();//文件记录日志classFileLogimplementsLogpublic 查看详情

《构建之法》(第十七章)读书笔记(代码片段)

...常是需要去维护的,是需要去给别人看的。但是,不同的编程语言对代码规范的要求是否相同呢?因为在工作室学的是前端语言,我对前端的代码规范比较了解。有一位博主总结的前端代码规范,个人感觉非常好:https://www.cnblog... 查看详情