# Shell脚本书写规范

## **Shell脚本书写规范**

### **代码风格规范**

```shell
#!/usr/bin/env bash
```

### **代码注释**

```shell
#!/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                                       #
#########################################################
```

### **参数规范**

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

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

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

### **环境变量**

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

```shell
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的时候还是尽量英文，毕竟有些机器还是没有直接支持中文的，打出来可能会有乱码。

### **执行权限**

```shell
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 \
```

### **代码有效率**

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

### **勤用双引号**

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

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

### **函数入口**

```shell
#!/usr/bin/env bash

func1(){
    #do sth
}

func2(){
    #do sth
}

main(){
    func1
    func2
}

main "$@"

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

### **考虑作用域**

```shell
#!/usr/bin/env bash

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

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

### **巧用`heredocs`**

```shell
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
```

### **脚本路径**

```shell
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

### **小技巧**

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

```shell
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                     # 和用户交互，读入输入变量
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://close.gitbook.io/yun-wei-bi-ji/centos/shell/shell-jiao-ben-shu-xie-gui-fan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
