Shell编程之代码规范
Posted -飞鹤-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shell编程之代码规范相关的知识,希望对你有一定的参考价值。
1. 前言
1.1. 目的
代码编写规范,主要包括两部分,代码风格和最佳工程实践。在代码风格上,没有一种代码编写风格是最好的,更重要的是与已有项目代码风格保持一致,以提高项目团队整体对代码的可读性。在工程实践上,统一一些开发流程,提升团队的协作效率,另外就是最佳工程实践规范,以提高代码的性能、可靠性以及可读性。
1.2. 基本原则
- 参考主流Shell编程命名代码风格。
- 代码规范借鉴工具ShellCheck。
- 在性能足够的情况下,可读性优先考虑。
1.3. 预定义
- 小写驼峰命名,如pathName。
- 大写驼峰命名,如PathName。
- 大写加下划线,如MAX_DEV_CNT=32。
- 小写加下划线,如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. 注释
尽量使用代码自注释,即用代码名来表达清楚。无法表达清楚的使用注释。注释应说明设计思路而不是描述代码的行为,代码的行为尽量依赖代码本身来表述清楚。
- 单行注释,#后面要空一格。
# Delete a file in a sophisticated manner.
- 函数注释
#######################################
# 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. 代码换行
- 长字符串换行
long_string="I am an exceptionally\\
long string."
echo "$long_string"
- 多个管道或逻辑操作(&& ||等)
# 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. 命名
- 文件名使用小写字母加下划线的形式,且以.sh结尾。
- 函数名使用小写字母加下划线的形式,包名使用::。
- 包中使用小写字母驼峰形式。
- 变量名使用小写字母加下划线的形式,局部变量尽量使用local修饰,减少变量名冲突。
- 常量使用大写字母加下划线形式,并且添加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. 变量引用
- 针对参数或内置变量,可以不用。
- 针对字符串变量,默认添加。
- 针对数字变量,引用可以不加和字符串变量区别开。
# 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的功能比较简单,其强大主要体现在与其配套的大量命令行工具。
- 需要调用其他应用程序,有许多文本操作,但是没有太多数据处理,那么Shell是一个好的选择。
- 如果有复杂的计算,或者对性能有强烈的追求,那么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. 补充
- 推荐使用ShellCheck,VS Code可以下载ShellCheck插件,自动检测代码规范。
- 参考Google Shell Style Guild。
以上是关于Shell编程之代码规范的主要内容,如果未能解决你的问题,请参考以下文章
(转) 三种编程命名规范(匈牙利命名法驼峰式命名法帕斯卡命名法)