Shell脚本书写规范

Shell脚本书写规范

代码风格规范

#!/usr/bin/env bash

代码注释

#!/usr/bin/env bash
#########################################################
# Function :Rotate application logs                     #
# Platform :All Linux Based Platform                    #
# Version  :1.0                                         #
# Date     :2017-07-28                                  #
# Author   :Stephen.Shi                                 #
# Contact  :sp@meitu.com                                #
# Company  :MeiTu                                       #
#########################################################

参数规范

当我们的脚本需要接受参数的时候,我们一定要先判断参数是否合乎规范,并给出合适的回显,方便使用者了解参数的使用。 最少,最少,我们至少得判断下参数的个数

# Uasge
help_msg(){
    echo "Usage: $0 <server_group_to_operate>"
}

# arg judge
if [[ $# -ne 1 ]]
then
    help_msg
else
    echo "參數不符合規範,過多"
fi

环境变量

一般情况下我们会将一些重要的环境变量定义在开头,确保这些变量的存在。

source /etc/profile
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/apps/bin/"

变量的定义和引用

1.  名称长的变量尽量使用驼峰写法或者使用小写字母+下划线的方式进行命名
2.  常量的定义推荐使用大写字母进行命名
3.  在引用变量时为了避免不必要的麻烦,尽量使用${变量名}的方式进行引用

缩进规矩

缩进同一

命名标准

  1. 文件名规范,以.sh结尾,方便识别

  2. 通过脚本名称最好能够直接读出脚本的用途,比如常用xx服务的启动脚本可命名为start_xx.sh,oo服务的监控脚本可命名为oo_monit.sh,告警脚本可名称为abc_alert.sh

  3. 变量名字要有含义,不要拼错,尽量使用英文

  4. 统一命名风格,用驼峰或者下划线连接,**==推荐==**使用小写字母加下划线的方式

编码统一

在写脚本的时候尽量使用UTF-8编码,能够支持中文等一些奇奇怪怪的字符。 尽管脚本中可以写中文,但是在写注释以及打log的时候还是尽量英文,毕竟有些机器还是没有直接支持中文的,打出来可能会有乱码。

执行权限

chmod +x xxxx.sh

日志和回显

  1. 一些debug级别的信息,在脚本调试结束后需要关闭。

  2. 日志输出要带时间、要带时间、带时间,在写定时任务用的脚本时尤为重要

  3. 日志的输出推荐使用tee -a ${log_file}的方式,可以直接在main函数入口处添加日志输出。

脚本日志输出样例

logfile="/var/log/log_clean.log"

# define functions
function xx(){
    echo "xx"
}

function oo(){
    echo "oo"
}

# define main function
function main(){
    xx
    oo
}

# invoke main function
main|tee -a ${logfile}

太长要分行

# 例如
./configure \
–prefix=/usr \
–sbin-path=/usr/sbin/nginx \
–conf-path=/etc/nginx/nginx.conf \

代码有效率

sed -n '1p' file        # 会读取整个文件,效率慢
sed -n '1p;1q' file     # 只读取第一行,效率快
真正正确的用法应该是使用 `head -1 file`

勤用双引号

#!/bin/sh
# 已知当前文件夹有一个a.sh的文件
var="*.sh"
echo $var
echo "$var

#### 他的运行结果如下:
a.sh
*.sh

函数入口

#!/usr/bin/env bash

func1(){
    #do sth
}

func2(){
    #do sth
}

main(){
    func1
    func2
}

main "$@"

# 实现类似的main函数,使得脚本的结构化程度更好
# 另外在传递参数的时候可能会遇到参数接收位置的变化, 记得使用 `shift` 函数。

考虑作用域

#!/usr/bin/env bash

var=1
func(){
    var=2
}
func
echo $var

# 输出结果: 2
因此,相比直接使用全局变量,使用 `local` `readonly` 这类的命令,
其次我们可以使用  `declare` 来声明变量。 这些方式都比使用全局方式定义要好。

巧用heredocs

cat>>/etc/rsyncd.conf << EOF
log file = /usr/local/logs/rsyncd.log
transfer logging = yes
log format = %t %a %m %f %b
syslog facility = local3
EOF

脚本路径

script_dir=$(cd $(dirname $0) && pwd)         # cd进当前脚本的目录然后再pwd
script_dir=$(dirname $(readlink -f $0 ))      # 直接读取当前脚本的所在路径

代码要简短

能一条命令解决的问题绝不用两条命令解决 ----> 牵涉到代码的可读性、执行效率

其他小tip

  • 路径尽量保持绝对路径,绝多路径不容易出错,如果非要用相对路径,最好用./修饰

  • 优先使用bash的变量替换代替 awk sed,这样更加简短

  • 简单的if尽量使用&&||,写成单行。比如 [[ x > 2]] && echo x

  • 当export变量时,尽量加上子脚本的 namespace,保证变量不冲突

  • 会使用trap捕获信号,并在接受到终止信号时执行一些收尾工作

  • 使用mktemp生成临时文件或文件夹

  • 利用/dev/null过滤不友好的输出信息

  • 会利用命令的返回值判断命令的执行情况

  • 使用文件前要判断文件是否存在,否则做好异常处理

  • 不要处理ls后的数据(比如ls -l | awk '{ print $8 }'),ls的结果非常不确定,并且平台有关

  • 读取文件时不要使用for loop而要使用while read

小技巧

封装函数有必要 使用静态变量声明 readonlylocal 修饰变量 使用$()代替`(反单引号) 使用[[]]代替[]

bash -n test.sh            # 用-n对脚本进行语法检查, echo不是唯一的调试方法
bash -v test.sh            # 用-v跟踪脚本里的每个命令的执行
bash -x test.sh            # 用-x跟踪脚本里的每个命令的执行,并附加扩充信息
set -o nounset             # 遇到不存在的变量,终止脚本的执行
set -o errexit             # 遇到执行出错,终止脚本的执行
set -o verbose             # 打印读入shell的输入行
set -o xtrace              # 执行命令之前打印命令
date "+%Y-%m-%d %H:%M:%S"  # 输出当前时间:年月日 时分秒
read x                     # 和用户交互,读入输入变量

Last updated