高效编写Bash脚本:编码规范与实用工具
一、Bash脚本编码规范
1.1 基本格式规范
1.1.1 缩进
缩进应该使用空格,而不是制表符。大多数文本编辑器可以设置为使用空格进行制表。为了使每行包含最多的字符,每个缩进级别应为2个空格。
1.1.2 常量、变量和函数命名
- 常量名应全部大写。
- 变量和函数名应全部小写。
- 当常量或变量名由多个单词组成时,应使用下划线作为分隔符。避免使用驼峰命名法。
示例:
# 常量
CONSTANT_NAME="value"
# 变量
variable_name="value"
# 函数
function_name() {
echo "This is a function."
}
1.1.3 长命令选项名
当使用具有多个选项的不太常见的命令(或具有不太常见选项的更常见命令)时,有时最好使用长形式的选项名,并将选项列表拆分成多行。
示例:
rsync \
--archive \
--delete-excluded \
--rsh=ssh \
--one-file-system \
--relative \
--include-from="$INCLUDE_FILE" \
--exclude-from="$EXCLUDE_FILE" \
"$SOURCE" "$DESTINATION"
1.1.4 管道
管道应格式化以实现最大清晰度。如果一个管道可以整齐地放在一行上,就应该这样写。否则,管道应拆分成多行,每行一个管道元素。
示例:
# 短管道写在一行
command1 | command2
# 长管道拆分成多行
command1 \
| command2 \
| command3 \
| command4
1.2 复合命令格式
1.2.1 if语句
then
应该与
if
出现在同一行。
示例:
if [[ -r ~/.bashrc ]]; then
echo ".bashrc is readable."
else
echo ".bashrc is not readable." >&2
exit 1
fi
1.2.2 while、until和for循环
do
应该与
while
、
until
和
for
命令出现在同一行。
示例:
while [[ -z "$str" ]]; do
command1
command2
done
for i in 1 2 3 4 5; do
command1
command2
done
1.2.3 case语句
- 简单的单行命令可以按如下方式格式化:
case s in
1|one)
command1 ;;
2|two)
command2 ;;
3|three)
command3 ;;
*)
command4 ;;
esac
- 多个命令应按如下方式格式化:
case s in
1|one)
command1
command2
;;
2|two)
command3
command4
;;
3|three)
command5
command6
;;
*)
command7
;;
esac
1.2.4 逻辑复合命令
command1 && short_command
command2 \
|| long_command "$param1" "$param2" "$param3"
1.3 编码实践
1.3.1 注释
良好的代码注释对于脚本维护至关重要。注释的目的是解释程序的重要事实。不要注释显而易见且易于理解的代码,而是解释难以理解的部分。函数库中的每个函数都应该在前面有一个注释块,记录其用途和位置参数。常见的“todo”注释用于指出未来需要添加和更改的地方。
示例:
# TODO Fix this routine so it can do this better
function example_function() {
# 这里解释函数的用途和参数
echo "This is an example function."
}
1.3.2 内置命令与外部程序
在可能的情况下,使用bash内置命令而不是外部命令。例如,
basename
和
dirname
程序可以用参数扩展来替换,以从路径名中去除前导或尾随字符串。与外部程序相比,shell内置命令使用更少的资源,执行速度更快。
1.3.3 变量扩展和引号
双引号必须用于在参数扩展期间管理单词拆分。这在处理文件名时尤为重要。假设每个变量、参数扩展和命令替换都可能包含嵌入的空格、换行符等。虽然在某些情况下不需要引号(例如,在
[[ ... ]]
中),但我们仍然使用双引号,因为这样不会有任何坏处,而且总是引用变量比记住所有不需要引号的特殊情况更容易。
示例:
a="$var"
b="$1"
c="$(command1)"
command2 "$file1" "$file2"
[[ -z "$str" ]] || exit 1
1.3.4 路径名扩展和通配符
由于类Unix系统中的路径名可以包含除
/
和
NULL
之外的任何字符,我们在扩展期间需要采取特殊预防措施。
示例:
# 防止以 `-` 开头的文件名被解释为命令选项
command1 ./*.txt
# 而不是这样
command1 *.txt
1.3.5
[[ ... ]]
与
[ ... ]
除非脚本必须在POSIX兼容的环境中运行,否则在执行条件测试时使用
[[ ... ]]
而不是
[ ... ]
。与
[
和
test
bash内置命令不同,
[[ ... ]]
是shell语法的一部分,而不是一个命令。这意味着它可以更稳健地处理其内部元素(测试条件),因为不会发生路径名扩展和单词拆分。此外,
[[ ... ]]
还增加了一些额外的功能,如
=~
用于执行正则表达式测试。
1.3.6 整数算术
在执行整数算术时,使用
(( ... ))
代替
let
或
expr
。bash的
let
内置命令的工作方式与
(( ... ))
类似,但它的参数通常需要仔细加引号。
expr
是一个外部程序,比shell慢很多。
示例:
if (( i > 1 )); then
# 执行操作
fi
while (( y == 5 )); do
# 执行操作
done
# 算术赋值
(( y = x * 2 ))
# 算术计算扩展
echo $(( i * 7 ))
1.3.7
printf
与
echo
在输出参数扩展时,某些情况下使用
printf
比
echo
更可取。这在扩展路径名时尤其正确。由于路径名可以包含几乎任何字符,扩展可能会导致命令选项、命令序列等。
1.4 错误处理
1.4.1 错误处理原则
- 预见错误 :在设计脚本时,考虑可能的失败点。在脚本开始之前,确保所有必要的外部程序都已安装在系统上,预期的文件和目录存在并具有脚本操作所需的权限。
- 不造成损害 :如果脚本必须执行任何破坏性操作(例如删除文件),确保脚本只做它应该做的事情。在执行任何破坏性操作之前,测试所有必需的条件。
- 报告错误并提供线索 :当检测到错误时,报告错误并在必要时终止脚本。所有错误消息必须发送到标准错误,并应包含有助于调试的有用信息。可以使用错误消息函数来实现。
- 清理混乱 :在处理错误时,确保使系统保持良好状态。如果脚本创建了临时文件或执行了可能使系统处于不良状态的操作,在脚本退出之前提供一种方法使系统恢复到可用状态。
1.4.2 错误消息函数示例
error_exit() {
local error_message="$1"
printf "%s\n" "${PROGNAME}: ${error_message:-"Unknown Error"}" >&2
exit 1
}
command1 || error_exit "command1 failed in line $LINENO"
1.5 命令行选项和参数
脚本应尽可能支持短选项名和长选项名。例如,“帮助”功能应同时支持
-h
和
--help
选项。可以使用以下代码实现双选项:
# 解析命令行
while [[ -n "$1" ]]; do
case $1 in
-h | --help)
help_message
graceful_exit
;;
-q | --quiet)
quiet_mode=yes
;;
-s | --root)
root_mode=yes
;;
--* | -*)
usage > &2; error_exit "Unknown option $1"
;;
*)
tmp_script="$1"
break
;;
esac
shift
done
1.6 帮助用户
所有脚本都应包含“帮助”选项,即使脚本不支持其他选项或参数。帮助消息应包括脚本名称和版本号、脚本用途的简要描述(如在手册页中可能出现的那样)以及描述支持的选项和参数的使用消息。可以使用单独的
usage
函数来实现帮助消息和错误消息。
示例:
usage() {
printf "%s\n" \
"Usage: ${PROGNAME} [-h|--help ]"
printf "%s\n" \
" ${PROGNAME} [-q|--quiet] [-s|--root] [script]"
}
help_message() {
cat <<- _EOF_
${PROGNAME} ${VERSION}
Bash shell script template generator.
$(usage)
Options:
-h, --help Display this help message and exit.
-q, --quiet Quiet mode. No prompting. Outputs default script.
-s, --root Output script requires root privileges to run.
_EOF_
}
1.7 信号捕获
脚本除了正常退出和错误退出外,还可能在接收到信号时终止。为了避免脚本意外退出导致系统处于不良状态,我们可以使用陷阱来拦截信号,并在脚本退出之前执行清理程序。最重要的三个信号是
SIGINT
(当按下
Ctrl-c
时发生)、
SIGTERM
(当系统关闭或重启时发生)和
SIGHUP
(当终端连接终止时发生)。
示例:
# 捕获信号
trap "signal_exit TERM" TERM HUP
trap "signal_exit INT" INT
signal_exit() { # 处理捕获的信号
local signal="$1"
case "$signal" in
INT)
error_exit "Program interrupted by user"
;;
TERM)
printf "\n%s\n" "$PROGNAME: Program terminated" >&2
graceful_exit
;;
*)
error_exit "$PROGNAME: Terminating on unknown signal"
;;
esac
}
1.8 临时文件
尽可能避免使用临时文件。在许多情况下,可以使用进程替换来代替。这样做可以减少文件混乱,运行速度更快,并且在某些情况下更安全。
示例:
# 避免使用临时文件
# command1 > "$TEMPFILE"
# command2 < "$TEMPFILE"
# 考虑使用进程替换
command2 < <(command1)
如果无法避免使用临时文件,必须小心安全地创建它们。如果临时文件放在全局可写的目录(如
/tmp
)中,必须确保文件名是不可预测的。可以使用
mktemp
命令来创建临时文件。
示例:
TEMPFILE="$(mktemp /tmp/"$PROGNAME".$$.XXXXXXXXX)"
1.9 ShellCheck工具
有一个名为
shellcheck
的程序,可在大多数发行版的软件仓库中找到,它可以对shell脚本进行分析,并检测出许多类型的错误和不良脚本实践。使用它来检查脚本的质量是很值得的。
使用方法:
- 对于带有shebang的脚本:
shellcheck my_script
- 对于不包含shebang的shell脚本代码(如函数库):
shellcheck -s bash my_library
下面是一个总结上述编码规范关键要点的表格:
| 规范类型 | 具体要求 |
| — | — |
| 缩进 | 使用2个空格,不用制表符 |
| 命名 | 常量全大写,变量和函数全小写,用下划线分隔多单词 |
| 命令选项 | 长选项名可拆分多行 |
| 管道 | 短管道一行,长管道多行 |
| 复合命令 | 按规定格式书写if、while、case等语句 |
| 注释 | 解释难理解部分,函数前加注释块 |
| 内置命令 | 优先使用bash内置命令 |
| 变量引号 | 用双引号管理单词拆分 |
| 路径扩展 | 预防特殊字符问题 |
| 条件测试 | 优先用[[ … ]] |
| 整数算术 | 用(( … )) |
| 输出 | 扩展路径名时用printf |
| 错误处理 | 预见、报告、清理 |
| 选项参数 | 支持长短选项名 |
| 帮助信息 | 脚本包含帮助选项 |
| 信号捕获 | 拦截信号执行清理 |
| 临时文件 | 避免使用,必要时安全创建 |
| 代码检查 | 使用shellcheck工具 |
下面是一个关于脚本执行流程的mermaid流程图:
graph TD;
A[开始] --> B[检查必要资源];
B -->|资源不足| C[报告错误并退出];
B -->|资源充足| D[解析命令行参数];
D --> E[执行主要操作];
E -->|操作成功| F[正常退出];
E -->|操作失败| G[错误处理];
G --> H[清理临时文件等];
H --> I[报告错误并退出];
J[接收到信号] --> K[信号处理];
K --> H;
二、new_script工具
2.1 工具介绍
编写符合编码标准的脚本可能会很繁琐,为了克服这个问题,许多程序员依赖于包含大量常规代码的脚本模板。
new_script
是一个来自LinuxCommand.org的程序,它可以为bash脚本创建模板。与静态模板不同,
new_script
可以自定义生成包含使用和帮助消息的模板,以及脚本所需的命令行选项和参数的解析器。使用
new_script
可以节省大量的时间和精力,并帮助我们使即使是最随意的脚本也成为精心设计和健壮的程序。
2.2 安装步骤
-
从LinuxCommand.org下载
new_script:
curl -O http://linuxcommand.org/new_script.bash
-
将下载的文件移动到
PATH中的目录:
mv new_script.bash ~/bin/new_script
- 设置文件为可执行:
chmod +x ~/bin/new_script
2.3 测试安装
安装完成后,可以使用以下命令测试:
new_script --help
如果安装成功,将看到帮助消息:
new_script 3.5.3
Bash shell script template generator.
Usage: new_script [-h|--help ]
new_script [-q|--quiet] [-s|--root] [script]
Options:
-h, --help Display this help message and exit.
-q, --quiet Quiet mode. No prompting. Outputs default script.
-s, --root Output script requires root privileges to run.
2.4 选项和参数
通常,
new_script
不带选项运行。它会提示用户输入各种信息,用于构建脚本模板。如果未指定输出脚本文件名,用户将被提示输入一个。对于一些特殊用例,支持以下选项:
-
-h
或
--help
:显示帮助消息并退出。
-
-q
或
--quiet
:安静模式,不进行提示,输出默认脚本。
-
-s
或
--root
:输出的脚本需要root权限才能运行。
下面是使用
new_script
生成脚本的简单流程图:
graph TD;
A[运行new_script] --> B{是否有选项};
B -->|有选项| C[根据选项处理];
B -->|无选项| D[提示用户输入信息];
C --> E[生成脚本模板];
D --> E;
E --> F[保存脚本文件];
通过遵循上述编码规范和使用
new_script
工具,我们可以更高效、更规范地编写Bash脚本,提高脚本的质量和可维护性。
三、深入理解编码规范的重要性
3.1 提高代码可读性
编码规范中的缩进、命名规则等要求,极大地提高了代码的可读性。例如,统一使用2个空格进行缩进,使得代码的层次结构一目了然。就像在阅读一篇文章时,清晰的段落划分能让读者快速理解文章的结构和内容。再看命名规则,常量全大写,变量和函数全小写并用下划线分隔多单词,这样的命名方式让代码的含义更加直观。比如
MAX_CONNECTIONS
一看就知道是一个表示最大连接数的常量,
calculate_average
能很容易地让人明白这是一个计算平均值的函数。
3.2 增强代码可维护性
良好的注释和错误处理机制是增强代码可维护性的关键。注释可以帮助开发者在后续维护代码时,快速理解代码的功能和设计思路。特别是对于复杂的逻辑部分,详细的注释就像一份说明书,让维护者能够轻松上手。而错误处理机制则保证了在代码出现问题时,能够及时报告错误并进行相应的处理,避免问题进一步恶化。例如,在脚本执行过程中,如果某个命令执行失败,错误消息函数会输出详细的错误信息,包括错误发生的位置,这对于定位和解决问题非常有帮助。
3.3 提升代码性能
优先使用bash内置命令而不是外部程序,能够显著提升代码的性能。因为内置命令使用更少的系统资源,执行速度更快。比如用参数扩展代替
basename
和
dirname
程序,减少了外部程序的调用,从而节省了时间和资源。此外,使用
(( ... ))
进行整数算术运算,比
let
和
expr
更高效,因为
let
的参数需要仔细加引号,而
expr
是外部程序,执行速度较慢。
四、实际应用案例分析
4.1 备份脚本示例
下面是一个简单的备份脚本,它遵循了上述的编码规范:
#!/bin/bash
# 脚本名称
PROGNAME="backup_script"
# 版本号
VERSION="1.0"
# 错误退出函数
error_exit() {
local error_message="$1"
printf "%s\n" "${PROGNAME}: ${error_message:-"Unknown Error"}" >&2
exit 1
}
# 帮助消息函数
help_message() {
cat <<- _EOF_
${PROGNAME} ${VERSION}
备份脚本,用于备份指定目录。
Usage: ${PROGNAME} [-h|--help ] [-s|--source <source_dir>] [-d|--destination <destination_dir>]
Options:
-h, --help 显示此帮助消息并退出。
-s, --source 指定要备份的源目录。
-d, --destination 指定备份文件的目标目录。
_EOF_
}
# 解析命令行参数
source_dir=""
destination_dir=""
while [[ -n "$1" ]]; do
case $1 in
-h | --help)
help_message
exit 0
;;
-s | --source)
shift
source_dir="$1"
;;
-d | --destination)
shift
destination_dir="$1"
;;
--* | -*)
usage > &2; error_exit "Unknown option $1"
;;
*)
error_exit "Invalid argument $1"
;;
esac
shift
done
# 检查源目录和目标目录是否为空
if [[ -z "$source_dir" || -z "$destination_dir" ]]; then
error_exit "源目录和目标目录必须指定。"
fi
# 检查源目录是否存在
if [[ ! -d "$source_dir" ]]; then
error_exit "源目录 $source_dir 不存在。"
fi
# 检查目标目录是否存在,不存在则创建
if [[ ! -d "$destination_dir" ]]; then
mkdir -p "$destination_dir" || error_exit "无法创建目标目录 $destination_dir。"
fi
# 备份操作
rsync \
--archive \
--delete-excluded \
--rsh=ssh \
--one-file-system \
--relative \
"$source_dir" "$destination_dir" || error_exit "备份操作失败。"
echo "备份成功。"
4.2 案例分析
-
参数解析
:脚本通过
while循环和case语句解析命令行参数,支持短选项-s、-d和长选项--source、--destination,方便用户指定源目录和目标目录。 -
错误处理
:在脚本执行的各个关键步骤,都进行了错误检查。例如,检查源目录是否存在,目标目录是否可以创建等。一旦出现问题,就会调用
error_exit函数输出错误信息并退出脚本。 -
备份操作
:使用
rsync命令进行备份,通过长选项名拆分多行的方式,使命令更加清晰易读。同时,使用双引号引用变量,避免了单词拆分的问题。
五、总结与建议
5.1 总结
通过本文,我们详细介绍了Bash脚本的编码规范和
new_script
工具。编码规范涵盖了基本格式、复合命令、编码实践、错误处理等多个方面,遵循这些规范可以提高代码的可读性、可维护性和性能。
new_script
工具则可以帮助我们快速生成符合编码规范的脚本模板,节省开发时间和精力。
5.2 建议
- 养成良好的编码习惯 :在日常开发中,始终遵循编码规范,将规范融入到自己的编程习惯中。这样不仅可以提高自己的编程水平,还能让团队成员更容易理解和维护代码。
-
充分利用工具
:除了
new_script工具,还可以使用shellcheck工具对脚本进行检查,及时发现和纠正代码中的错误和不良实践。 - 持续学习和改进 :编码规范并不是一成不变的,随着技术的发展和经验的积累,我们应该不断学习新的知识,对编码规范进行优化和改进。
下面是一个关于遵循编码规范和使用工具的好处的表格:
| 好处 | 详细描述 |
| — | — |
| 提高效率 | 遵循规范和使用工具可快速编写脚本,节省时间 |
| 增强质量 | 规范的代码可读性和可维护性高,减少错误 |
| 便于协作 | 统一规范使团队成员间代码交流更顺畅 |
| 提升性能 | 合理使用内置命令等优化代码性能 |
最后,为了帮助大家更好地理解整个Bash脚本开发的流程,这里给出一个更全面的mermaid流程图:
graph TD;
A[开始规划脚本] --> B[学习编码规范];
B --> C[使用new_script生成模板];
C --> D[根据需求修改模板];
D --> E[编写脚本代码];
E --> F[使用shellcheck检查代码];
F -->|有错误| E;
F -->|无错误| G[测试脚本];
G -->|测试失败| E;
G -->|测试成功| H[部署脚本];
I[维护脚本] --> J[遵循规范修改代码];
J --> F;
通过这个流程图,我们可以看到从脚本的规划、编写、测试到部署和维护的整个过程,每个环节都与编码规范和工具的使用紧密相关。希望大家在实际开发中,能够充分利用这些知识和工具,编写出高质量的Bash脚本。
超级会员免费看
859

被折叠的 条评论
为什么被折叠?



