16、高效编写Bash脚本:编码规范与实用工具

高效编写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 安装步骤

  1. 从LinuxCommand.org下载 new_script
curl -O http://linuxcommand.org/new_script.bash
  1. 将下载的文件移动到 PATH 中的目录:
mv new_script.bash ~/bin/new_script
  1. 设置文件为可执行:
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脚本。

提供了基于BP(Back Propagation)神经网络结合PID(比例-积分-微分)控制策略的Simulink仿真模型。该模型旨在实现对杨艺所著论文《基于S函数的BP神经网络PID控制器及Simulink仿真》中的理论进行实践验证。在Matlab 2016b环境下开发,经过测试,确保能够正常运行,适合学习和研究神经网络在控制系统中的应用。 特点 集成BP神经网络:模型中集成了BP神经网络用于提升PID控制器的性能,使之能更好地适应复杂控制环境。 PID控制优化:利用神经网络的自学习能力,对传统的PID控制算法进行了智能调整,提高控制精度和稳定性。 S函数应用:展示了如何在Simulink中通过S函数嵌入MATLAB代码,实现BP神经网络的定制化逻辑。 兼容性说明:虽然开发于Matlab 2016b,但理论上兼容后续版本,可能会需要调整少量配置以适配不同版本的Matlab。 使用指南 环境要求:确保你的电脑上安装有Matlab 2016b或更高版本。 模型加载: 下载本仓库到本地。 在Matlab中打开.slx文件。 运行仿真: 调整模型参数前,请先熟悉各模块功能和输入输出设置。 运行整个模型,观察控制效果。 参数调整: 用户可以自由调节神经网络的层数、节点数以及PID控制器的参数,探索不同的控制性能。 学习和修改: 通过阅读模型中的注释和查阅相关文献,加深对BP神经网络PID控制结合的理解。 如需修改S函数内的MATLAB代码,建议有一定的MATLAB编程基础。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值