编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 质量保障

  • 产品思考

  • 软实力

  • 开发流程

  • Git应用

  • 技术模版

  • 技术规范

    • 技术规范
    • C++编程代码规范指南
    • Java编程代码规范指南
    • JavaScript编程规范指南
    • TypeScript编程规范指南
    • Python编程代码规范指南
    • Go编程代码规范指南
    • Kotlin编程代码规范指南
    • Swift编程代码规范指南
    • Rust编程代码规范指南
    • Shell编程代码规范指南
      • 目录
      • 01.规范概述
        • 1.1 为何需要代码规范
        • 1.2 核心目标
        • 1.3 要求等级
        • 1.4 Shell与Bash版本
      • 02.文件头部规范
        • 2.1 Shebang 【必须】
        • 2.2 严格模式 【必须】
        • 2.3 可选选项 【推荐】
      • 03.命名规范
        • 3.1 命名总表
        • 3.2 正确与错误示例
        • 3.3 命名反模式
      • 04.代码格式规范
        • 4.1 缩进与空格 【必须】
        • 4.2 管道换行 【推荐】
        • 4.3 空行与逻辑分组
      • 05.注释规范
        • 5.1 核心原则
        • 5.2 文件头注释
        • 5.3 函数注释
        • 5.4 TODO 与 FIXME
      • 06.变量规范
        • 6.1 引号包裹 【必须】
        • 6.2 花括号与默认值 【必须】
        • 6.3 变量作用域 【必须】
        • 6.4 数组操作 【推荐】
        • 6.5 readonly 与 declare 【推荐】
      • 07.函数规范
        • 7.1 函数定义 【必须】
        • 7.2 参数与返回值 【必须】
        • 7.3 main 函数入口 【推荐】
        • 7.4 函数库组织 【推荐】
      • 08.条件判断与测试
        • 8.1 [[ vs [ 选型 【必须】
        • 8.2 文件与字符串测试
        • 8.3 算术判断 【推荐】
        • 8.4 case 分支 【推荐】
      • 09.循环与数组
        • 9.1 while read 逐行处理 【推荐】
        • 9.2 遍历文件与 find 【推荐】
        • 9.3 数组遍历 【推荐】
      • 10.子命令与管道
        • 10.1 命令替换 【必须】
        • 10.2 管道与重定向
        • 10.3 进程替换与 here 文档 【推荐】
      • 11.字符串与文本处理
        • 11.1 参数扩展 【推荐】
        • 11.2 文本处理工具链 【推荐】
      • 12.错误处理
        • 12.1 set 选项详解 【必须】
        • 12.2 退出码约定 【必须】
        • 12.3 trap 清理资源 【必须】
        • 12.4 错误日志 【推荐】
      • 13.安全与防御
        • 13.1 输入验证 【必须】
        • 13.2 路径遍历防御 【必须】
        • 13.3 临时文件安全 【必须】
        • 13.4 依赖检查 【推荐】
        • 13.5 权限检查 【推荐】
      • 14.调试与工具链
        • 14.1 调试模式
        • 14.2 shellcheck 静态检查
        • 14.3 shfmt 格式化
        • 14.4 bats 测试框架 【可选】
        • 14.5 CI 集成
      • 15.常见反模式
      • 16.代码审查清单
      • 17.常见陷阱速查
        • 17.1 变量与引号陷阱
        • 17.2 流程控制陷阱
        • 17.3 性能陷阱
        • 17.4 安全陷阱
    • 项目代码提交规范
  • markdown

  • mermaid

  • license

  • 博客部署

  • 技术招聘

  • 测试经验

  • 技术
  • 技术规范
杨充
2021-09-05
目录

Shell编程代码规范指南

# Shell编程代码规范指南

本规范参考 Google Shell Style Guide (opens new window) 及 ShellCheck Wiki (opens new window),结合项目实践精简整理。

# 目录

  • 01.规范概述
    • 1.1 为何需要代码规范
    • 1.2 核心目标
    • 1.3 要求等级
    • 1.4 Shell与Bash版本
  • 02.文件头部规范
    • 2.1 Shebang
    • 2.2 严格模式
    • 2.3 可选选项
  • 03.命名规范
    • 3.1 命名总表
    • 3.2 正确与错误示例
    • 3.3 命名反模式
  • 04.代码格式规范
    • 4.1 缩进与空格
    • 4.2 管道换行
    • 4.3 空行与逻辑分组
  • 05.注释规范
    • 5.1 核心原则
    • 5.2 文件头注释
    • 5.3 函数注释
    • 5.4 TODO与FIXME
  • 06.变量规范
    • 6.1 引号包裹
    • 6.2 花括号与默认值
    • 6.3 变量作用域
    • 6.4 数组操作
    • 6.5 readonly与declare
  • 07.函数规范
    • 7.1 函数定义
    • 7.2 参数与返回值
    • 7.3 main函数入口
    • 7.4 函数库组织
  • 08.条件判断与测试
    • [8.1 [[ vs 选型
    • 8.2 文件与字符串测试
    • 8.3 算术判断
    • 8.4 case分支
  • 09.循环与数组
    • 9.1 while read逐行处理
    • 9.2 遍历文件与find
    • 9.3 数组遍历
  • 10.子命令与管道
    • 10.1 命令替换
    • 10.2 管道与重定向
    • 10.3 进程替换与here文档
  • 11.字符串与文本处理
    • 11.1 参数扩展
    • 11.2 文本处理工具链
  • 12.错误处理
    • 12.1 set选项详解
    • 12.2 退出码约定
    • 12.3 trap清理资源
    • 12.4 错误日志
  • 13.安全与防御
    • 13.1 输入验证
    • 13.2 路径遍历防御
    • 13.3 临时文件安全
    • 13.4 依赖检查
    • 13.5 权限检查
  • 14.调试与工具链
    • 14.1 调试模式
    • 14.2 shellcheck静态检查
    • 14.3 shfmt格式化
    • 14.4 bats测试框架
    • 14.5 CI集成
  • 15.常见反模式
  • 16.代码审查清单
  • 17.常见陷阱速查
    • 17.1 变量与引号陷阱
    • 17.2 流程控制陷阱
    • 17.3 性能陷阱
    • 17.4 安全陷阱

# 01.规范概述

# 1.1 为何需要代码规范

疑惑:Shell 脚本通常就几十行,能跑就行,为什么还要制定规范?

答疑:Shell 是一门"宽容到危险"的语言。不加引号能跑、变量不声明能跑、管道中断能跑——但空格文件名、空变量、子命令失败等边界情况会悄无声息地出错。规范的本质是用防御性写法,让脚本在任何输入下都能安全运行。

# 1.2 核心目标

目标 说明
健壮性 空格、特殊字符、空值都不会让脚本崩溃
可读性 脚本像文档一样流畅
一致性 整个项目像一个人写的
安全性 避免命令注入、路径遍历、临时文件竞争
可调试性 出问题时能快速定位

# 1.3 要求等级

  • 必须(Mandatory):必须采用,违反将被 Code Review 驳回。部分规则由 shellcheck 强制执行。
  • 推荐(Preferable):理应采用,特殊情况可不采用但需注释说明。
  • 可选(Optional):自行决定。

# 1.4 Shell与Bash版本

脚本统一使用 Bash 4.0+(macOS 默认 Bash 3.2,需通过 Homebrew 安装新版)。执行环境为 Linux/macOS,不使用 zsh 特有语法。可移植脚本应限制在 POSIX sh。


# 02.文件头部规范

# 2.1 Shebang 【必须】

#!/usr/bin/env bash
# ✅ 用 /usr/bin/env 查找 bash(可移植,兼容不同安装路径)
# ❌ #!/bin/bash  — 在某些系统上 bash 不在 /bin
# ❌ #!/bin/sh    — 可能指向 dash/ash,不支持 Bash 扩展语法
1
2
3
4

# 2.2 严格模式 【必须】

#!/usr/bin/env bash
set -euo pipefail  # 严格模式三件套

# set -e  : 任何命令失败(返回非零)立即退出
# set -u  : 使用未定义变量时报错(而不是默默用空字符串)
# set -o pipefail : 管道中任一命令失败,整个管道失败

# 这三个选项让脚本在出错时"快速失败",
# 而不是带着错误继续执行导致更隐蔽的问题。
1
2
3
4
5
6
7
8
9

# 2.3 可选选项 【推荐】

# set -x  : 打印每个命令及展开后的参数(调试用,生产环境注释掉)
# set -v  : 打印每行脚本原文(调试用)
# shopt -s nullglob   : 无匹配的 glob 展开为空(而非保留字面量 *.txt)
# shopt -s extglob    : 启用扩展通配符(@(a|b) 等)
# shopt -s globstar   : 启用 ** 递归通配
# shopt -s nocaseglob : 大小写不敏感通配
1
2
3
4
5
6

# 03.命名规范

# 3.1 命名总表

类型 规范 示例
文件名 全小写 + 下划线 deploy.sh, backup_database.sh
函数名 小写 + 下划线 download_file, parse_args
变量名 小写 + 下划线 file_path, retry_count
常量/环境变量 全大写 + 下划线 MAX_RETRIES, CONFIG_DIR
只读变量 readonly + 全大写 readonly SCRIPT_DIR
私有函数 _ 前缀 _internal_helper, _validate_input
循环变量 简洁但有意义 i, file, line
全局变量 全大写(与局部区分) TEMP_DIR, LOG_FILE

# 3.2 正确与错误示例

# ✅ 正确
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly MAX_RETRIES=5

download_file() {
    local url="$1"
    local dest="$2"
    # ...
}

# ✅ 私有函数用 _ 前缀
_validate_url() { [[ "$1" =~ ^https?:// ]]; }

# ❌ 错误
# function downloadFile()  — 不用 function 关键字,不用驼峰命名
# local Url=$1              — 变量名用了大写(与环境变量冲突风险)
# MAX_RETRIES=5              — 缺少 readonly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3.3 命名反模式

反模式 示例 改进
过度缩写 dl_f, prs download_file, parse_args
含义不清 data, tmp, x response_body, temp_dir
大写变量名(非环境变量) local URL="$1" local url="$1"
拼音命名 dian_hua phone_number
无意义的后缀 download_file_func download_file

# 04.代码格式规范

# 4.1 缩进与空格 【必须】

# ✅ 2 个空格缩进
download_file() {
  local url="$1"
  local dest="$2"

  if [[ ! -f "${dest}" ]]; then
    curl -fsSL "${url}" -o "${dest}"
  fi
}

# ✅ 运算符两侧加空格
result=$((a + b * c))
# ❌ result=$((a+b*c))

# ✅ 分号前无空格、后有空格
if [[ -f "${file}" ]]; then
# ❌ if [[ -f "${file}" ]];then
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4.2 管道换行 【推荐】

# ✅ 长管道:用 | 结尾换行,每个命令一行
cat "${log_file}" \
  | grep "ERROR" \
  | awk '{print $3}' \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -10

# ✅ 长命令参数:用 \ 折行,参数对齐
curl -fsSL "https://api.example.com" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}' \
  -o "${output_file}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4.3 空行与逻辑分组

#!/usr/bin/env bash
set -euo pipefail

# --- 常量定义 ---
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly CONFIG_FILE="${SCRIPT_DIR}/config.yml"
readonly MAX_RETRIES=3

# --- 私有函数 ---
_validate_input() {
  local input="$1"
  [[ -n "${input}" ]] || { echo "ERROR: empty input" >&2; exit 1; }
}

# --- 业务函数 ---
download_file() {
  # 不同逻辑块之间空一行
  local url="$1"
  local dest="$2"

  if [[ -f "${dest}" ]]; then
    echo "Skip: ${dest} already exists"
    return 0
  fi

  echo "Downloading: ${url} -> ${dest}"
  curl -fsSL "${url}" -o "${dest}"
}

# --- 入口 ---
main() {
  parse_args "$@"
  do_work
}
main "$@"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 05.注释规范

# 5.1 核心原则

注释解释"为什么",代码说明"做了什么"。

# ❌ 差的注释:复述代码
count=$((count + 1))  # count 加 1

# ✅ 好的注释:解释意图
count=$((count + 1))  # 跳过 CSV 文件的标题行

# ✅ 记录决策原因
# 使用 --retry 3 而非无限重试,因为第三方 API 在高峰期偶尔超时,
# 但 3 次内必定成功(2023-06 确认的行为)
curl --retry 3 "${url}"

# ❌ 过时注释(危险!)
# 调用 V1 接口
curl "https://api-v2.example.com"  # ← 注释和代码不匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 5.2 文件头注释

#!/usr/bin/env bash
#
# deploy.sh — 部署指定 tag 到目标服务器。
#
# 用法:
#   ./deploy.sh <tag> [--env prod|staging]
#
# 环境变量:
#   DEPLOY_KEY    - SSH 私钥路径(必需)
#   DOCKER_REGISTRY - 镜像仓库地址(默认 docker.io)
#
# 退出码:
#   0 - 部署成功
#   1 - 参数错误
#   2 - 连接失败
#   3 - 部署失败
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 5.3 函数注释

# Downloads a file from given URL with retry logic.
#
# Globals:
#   MAX_RETRIES - max download attempts (default 3)
#
# Arguments:
#   url  - source URL (required, must be https)
#   dest - destination path (required)
#
# Returns:
#   0 if download succeeds
#   1 if all retries exhausted
#
# Outputs:
#   Progress info to stdout
#   Errors to stderr
#
# Example:
#   download_file "https://example.com/data.tar.gz" "/tmp/data.tar.gz"
download_file() {
    local url="$1"
    local dest="$2"
    # ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 5.4 TODO 与 FIXME

# TODO(yc): 添加增量部署支持,预计 v2.0 实现
deploy_full() { ... }

# FIXME(yc): 当镜像 tag 包含特殊字符(如 /)时,docker pull 失败
#            需要增加 tag 合法性校验
pull_image() { ... }

# HACK(yc): macOS 的 sed 与 GNU sed 不兼容,临时方案
#          待统一构建环境后移除(#1234)
sed -i '' 's/old/new/g' "${file}"
1
2
3
4
5
6
7
8
9
10

# 06.变量规范

# 6.1 引号包裹 【必须】

# ✅ 始终用双引号包裹变量引用(防止空格分词和 glob 展开)
echo "Processing: ${file_name}"
cp "${source}" "${dest}"
rm -rf "${temp_dir}"

# ❌ 不加引号的常见后果
# var="file with spaces.txt"
# cp $var /tmp/    → cp file with spaces.txt /tmp/  (被空格拆开)
# cp "$var" /tmp/  → cp 'file with spaces.txt' /tmp/

# ✅ 命令替换也要加引号
local output
output="$(curl -s "${url}")"     # ✅
output=$(curl -s "${url}")       # ❌ 不加引号:输出中的换行/空格导致单词拆分
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 6.2 花括号与默认值 【必须】

# ✅ 花括号明确变量边界
echo "${prefix}_suffix"          # ✅ 清晰
# echo "$prefix_suffix"           # ❌ 会被解释为变量 prefix_suffix

# ✅ 默认值
port="${PORT:-8080}"             # PORT 未设置或为空 → 使用 8080
log_level="${LOG_LEVEL:-info}"

# ✅ := 同时赋值
use_tls="${USE_TLS:=true}"       # 未设置 → 赋值 true
echo "${USE_TLS}"                # 永远有值

# ✅ 必须提供的变量
db_url="${DB_URL:?DB_URL is required}"     # 未设置 → 报错退出
# echo "${optional_var:?"未设置"}           # ❌ 用 :? 要有充分理由

# ✅ 备选值
fallback="${BACKUP_HOST:-${PRIMARY_HOST:-localhost}}"   # 逐级备用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.3 变量作用域 【必须】

# ✅ 函数内用 local 限定作用域
process_file() {
    local input_file="$1"              # 函数局部变量
    local temp_file
    temp_file="$(mktemp)"

    # ...
}

# ✅ 全局变量用 readonly 保护(常量)
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# ✅ 需要修改的全局变量用大写(警示这是全局的)
CURRENT_STEP=""                       # 全大写 = 全局,小心修改

# ❌ 不要在函数内修改全大写全局变量(除非明确这就是设计意图)
# ❌ 函数内不用 local → 变量在全局作用域生效(副作用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 6.4 数组操作 【推荐】

# ✅ 数组定义
fruits=("apple" "banana" "cherry")

# ✅ 遍历数组
for fruit in "${fruits[@]}"; do       # "${fruits[@]}" — 每个元素独立加引号
    echo "Fruit: ${fruit}"
done

# ❌ ${fruits[*]} — 所有元素合并成一个字符串(空格连接)
# ❌ $fruits       — 只引用第一个元素 fruits[0]

# ✅ 数组长度
echo "Count: ${#fruits[@]}"

# ✅ 追加元素
fruits+=("date")

# ✅ 切片
echo "${fruits[@]:1:2}"              # banana cherry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 6.5 readonly 与 declare 【推荐】

# ✅ readonly 保护常量
readonly SCRIPT_DIR
readonly MAX_RETRIES=5
readonly DOCKER_REGISTRY="docker.io/myapp"

# ✅ declare 声明类型
declare -i retry_count=0              # 整型
declare -a items=()                   # 数组
declare -A users=()                   # 关联数组(Bash 4.0+)

# ✅ 关联数组
declare -A config
config["timeout"]=30
config["retries"]=3

for key in "${!config[@]}"; do        # 遍历 key
    echo "${key}=${config[${key}]}"
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 07.函数规范

# 7.1 函数定义 【必须】

# ✅ 推荐写法(不使用 function 关键字,添加 () 更清晰)
download_file() {
    local url="$1"
    local dest="$2"
    # ...
}

# ❌ 不推荐的写法
# function download_file { ... }          # 多余的关键字
# function download_file() { ... }        # 混用,bash 允许但不推荐

# ✅ 简单函数可写成一行(单逻辑)
is_root() { [[ "$(id -u)" -eq 0 ]]; }
is_installed() { command -v "$1" &>/dev/null; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 7.2 参数与返回值 【必须】

# ✅ 参数通过 $1, $2, ... 接收,用 local 接住
process_item() {
    local item_name="$1"
    local item_type="${2:-json}"     # 第二个参数可选,默认 json
    local force="${3:-false}"

    echo "Processing ${item_name} (type=${item_type})"

    if [[ "${force}" == "true" ]]; then
        echo "Force mode enabled"
    fi
}

# ✅ 所有参数:"$@"
print_all() {
    for arg in "$@"; do              # "$@" — 每个参数独立加引号
        echo "Arg: ${arg}"
    done
}
# ❌ $* — 所有参数合并成一个字符串

# ✅ 返回码(0 = 成功,非 0 = 失败)
is_valid_email() {
    local email="$1"
    [[ "${email}" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
    return $?                         # 返回测试结果的退出码
}

# ✅ "返回"字符串:通过 stdout(调用方用 $(...) 捕获)
get_config_value() {
    local key="$1"
    grep "^${key}:" "${CONFIG_FILE}" | cut -d':' -f2- | sed 's/^[[:space:]]*//'
}

# ✅ 错误信息通过 stderr 输出
download_file() {
    local url="$1"
    if ! curl -fsSL "${url}" -o "${dest}"; then
        echo "ERROR: Failed to download ${url}" >&2
        return 1
    fi
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 7.3 main 函数入口 【推荐】

# ✅ 用 main 函数作为入口(便于跟踪变量作用域、执行顺序)
main() {
    parse_args "$@"
    validate_environment
    run_tasks
}
main "$@"

# 好处:
# 1. 脚本"骨架"一目了然
# 2. main 内的变量是 local 的
# 3. 可以整个 main 函数放在脚本末尾,与"执行入口"合一
1
2
3
4
5
6
7
8
9
10
11
12

# 7.4 函数库组织 【推荐】

# ✅ 引用公共函数库
# source 用绝对路径或相对脚本路径
source "${SCRIPT_DIR}/lib/utils.sh"
source "${SCRIPT_DIR}/lib/logging.sh"

# ❌ source ./utils.sh   — 相对路径依赖当前工作目录
# ❌ . utils.sh           — 同上

# ✅ 函数库中的函数应设计为无副作用(不修改全局状态)
# ✅ 如果函数库需要在被 source 时做初始化,用条件变量防止重复执行
if [[ -z "${_UTILS_INITIALIZED:-}" ]]; then
    readonly _UTILS_INITIALIZED="true"
    readonly _UTILS_TEMP_DIR="$(mktemp -d)"
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 08.条件判断与测试

# 8.1 [[ vs [ 选型 【必须】

# ✅ [[ ]] — Bash 内置,更安全(不用转义 < > ( ) 等字符)
if [[ "${name}" == "admin" ]]; then
    echo "Welcome admin"
fi

# ✅ [[ ]] 支持正则匹配
if [[ "${email}" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "Valid email"
fi

# ✅ [[ ]] 支持 && || 组合条件
if [[ "${name}" == "admin" ]] && [[ -f "${config}" ]]; then
    start_service
fi

# ❌ [ ] — 仅在需要 POSIX 兼容时使用(需小心转义和空格)
# ❌ [[ -f $file ]]   — 变量没加引号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 8.2 文件与字符串测试

# ✅ 文件测试
[[ -f "${file}" ]]     # 普通文件存在(非目录)
[[ -d "${dir}" ]]      # 目录存在
[[ -x "${script}" ]]   # 可执行
[[ -r "${file}" ]]     # 可读
[[ -s "${file}" ]]     # 文件存在且非空
[[ ! -f "${file}" ]]   # 文件不存在

# ✅ 字符串测试
[[ -z "${var}" ]]      # 字符串为空
[[ -n "${var}" ]]      # 字符串非空
[[ "${a}" == "${b}" ]] # 字符串相等
[[ "${a}" != "${b}" ]] # 字符串不等

# ✅ 多个条件组合
if [[ -d "${dir}" && -w "${dir}" ]]; then
    write_to_dir
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 8.3 算术判断 【推荐】

# ✅ (()) — 算术判断(支持 > < >= <= == !=)
if (( count > 0 )); then
    echo "count is positive: ${count}"
fi

if (( retries <= MAX_RETRIES )); then
    retry
fi

# ✅ 算术表达式赋值
local result=$((a + b * c))
# 等价于:(( result = a + b * c ))

# ❌ 不要用 [[ ]] 做算术比较
# [[ "${count}" -gt 0 ]]   — 在 Bash 中也工作,但 (()) 更清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 8.4 case 分支 【推荐】

# ✅ case 处理多分支输入(比多个 if-elif 更清晰)
case "${action}" in
    start)
        start_service
        ;;
    stop)
        stop_service
        ;;
    restart)
        stop_service
        start_service
        ;;
    status)
        show_status
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}" >&2
        exit 1
        ;;
esac

# ✅ case + 通配符(匹配模式)
case "${file}" in
    *.tar.gz|*.tgz)  tar -xzf "${file}" ;;
    *.tar.bz2)       tar -xjf "${file}" ;;
    *.zip)           unzip "${file}" ;;
    *)               echo "Unknown archive format" >&2 ;;
esac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 09.循环与数组

# 9.1 while read 逐行处理 【推荐】

# ✅ while IFS= read -r:保留行内空格,不解释反斜杠
while IFS= read -r line; do
    echo "Line: ${line}"
done < "${input_file}"

# IFS=    → 不 trim 行首行尾空白
# -r      → 不解释反斜杠转义
# < file  → 通过重定向(而非管道)避免子 shell 变量丢失

# ✅ 管道场景用 process substitution
while IFS= read -r line; do
    process "${line}"
done < <(grep "ERROR" "${log_file}")
# done < <(cmd)  → 进程替换:cmd 的输出作为文件输入,不创建子 shell

# ❌ cmd | while read ...  → while 在子 shell 中,变量修改无法传到外部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 9.2 遍历文件与 find 【推荐】

# ✅ find -print0 + while read -d '':正确处理文件名中的空格/换行
while IFS= read -r -d '' file; do
    process "${file}"
done < <(find . -name "*.md" -print0)
# -print0   → 用 NUL 字符分隔文件名(文件名中唯一不可能出现的字符)
# -d ''     → 以 NUL 字符为分隔符

# ❌ 不要用 for 遍历 ls 输出(空格分词问题)
# for file in $(ls *.md); do ...   # 文件名有空格 → 被拆开
# for file in *; do process "$file"; done  # ✅ 正确的 glob 遍历
1
2
3
4
5
6
7
8
9
10

# 9.3 数组遍历 【推荐】

fruits=("apple" "banana" "cherry")

# ✅ 遍历所有值
for fruit in "${fruits[@]}"; do
    echo "Fruit: ${fruit}"
done

# ✅ 遍历索引
for i in "${!fruits[@]}"; do
    echo "Index ${i}: ${fruits[${i}]}"
done

# ✅ C 风格 for(有明确起始/终止条件时)
for (( i = 0; i < MAX_RETRIES; i++ )); do
    if try_connect "${i}"; then
        break
    fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 10.子命令与管道

# 10.1 命令替换 【必须】

# ✅ $() 而非反引号(支持嵌套,易读)
current_dir="$(pwd)"
file_count="$(ls -1 | wc -l)"

# ❌ `cmd`  — 反引号,嵌套困难,与引号混淆
# nested=`echo \`date\``

# ✅ 嵌套命令替换
latest_file="$(find "$(get_download_dir)" -name "*.tar.gz" | tail -1)"
1
2
3
4
5
6
7
8
9

# 10.2 管道与重定向

# ✅ 管道:串联命令
# 注意:每个命令的运行结果是独立的退出码
# 需要 set -o pipefail 来让管道第一个失败的命令成为整个管道的退出码
cat "${logfile}" | grep "ERROR" | sort | uniq -c | sort -rn | head -10

# ✅ 标准输出重定向
echo "log" > "${log_file}"        # 覆盖
echo "append" >> "${log_file}"    # 追加

# ✅ 标准错误重定向
echo "error" >&2                  # 输出到 stderr
command 2>/dev/null               # 丢弃 stderr
command &>"${log_file}"           # stdout 和 stderr 合并写入文件

# ✅ here document
cat > "${config_file}" <<EOF
server {
    host: ${host}
    port: ${port}
}
EOF
# <<EOF          → 变量展开
# <<'EOF'        → 不展开变量
# <<-EOF         → 去掉前导 tab(不能是空格)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 10.3 进程替换与 here 文档 【推荐】

# ✅ 进程替换:一个命令的输出作为另一个命令的"文件"输入
diff <(sort file1.txt) <(sort file2.txt)
# 绕过临时文件,直接比较两个排序结果

# ✅ here 文档变量展开 / 不展开
cat <<EOF
Host: ${HOSTNAME}       # 变量会展开
Date: $(date)           # 命令会执行
EOF

cat <<'EOF'
Host: ${HOSTNAME}       # 字面量输出,不展开
Date: $(date)           # 字面量输出,不执行
EOF

# ✅ here string(将字符串作为 stdin)
grep "pattern" <<< "${input_string}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 11.字符串与文本处理

# 11.1 参数扩展 【推荐】

filename="backup-2024-01-15.tar.gz"

# ✅ 获取文件名(去掉目录)
basename="${filename##*/}"            # backup-2024-01-15.tar.gz

# ✅ 获取扩展名
extension="${filename##*.}"           # gz

# ✅ 获取文件名(去掉扩展名)
name="${filename%.*}"                 # backup-2024-01-15.tar
name="${filename%%.*}"                # backup-2024-01-15(去掉全部后缀)

# ✅ 替换
no_spaces="${filename// /_}"          # 全部空格替换为下划线
first_only="${filename/ /_}"          # 只替换第一个

# ✅ 长度
echo "Length: ${#filename}"

# ✅ 子串
date_part="${filename:7:10}"         # 2024-01-15(从第 7 个字符取 10 个)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 11.2 文本处理工具链 【推荐】

# ✅ awk / sed / grep 组合
# 提取访问最多的 10 个 IP
awk '{print $1}' access.log \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -10

# ✅ sed 替换
sed -i "s/{{VERSION}}/${version}/g" "${config_file}"

# ✅ grep 常用选项
grep -r "TODO" src/              # 递归
grep -v "SKIP"                   # 排除匹配行
grep -i "error"                  # 忽略大小写
grep -c "error"                  # 只输出匹配计数
grep -n "error"                  # 显示行号
grep -A 3 "error"                # 匹配行及后 3 行
grep -B 2 "error"                # 匹配行及前 2 行

# ✅ jq:JSON 处理(首选)
echo "${response}" | jq -r '.data.user.name'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 12.错误处理

# 12.1 set 选项详解 【必须】

#!/usr/bin/env bash
set -euo pipefail

# set -e 的例外(某些命令失败是正常的):
# ✅ 条件语句中:if ! command; then ...
# ✅ while/until 条件中:while ! ping -c1 "${host}"; do ...
# ✅ || 短路:command || true(明确允许失败)
# ✅ && 的左侧:cmd1 && cmd2(cmd1 失败则整体失败)

# ✅ 临时关闭 set -e
set +e
some_fallible_command
exit_code=$?
set -e
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 12.2 退出码约定 【必须】

# ✅ 明确退出码(0 = 成功,非 0 = 失败)
# 约定分配:
#   0  - 成功
#   1  - 一般错误
#   2  - 参数/用法错误
#   3  - 网络/连接错误
#   4  - 权限错误
#   5  - 配置错误

# ✅ 检查关键命令
if ! mkdir -p "${dir}"; then
    echo "ERROR: Cannot create directory: ${dir}" >&2
    exit 1
fi

# ✅ 使用 exit_code 变量统一管理
exit_code=0
task_a || exit_code=$?
task_b || exit_code=$?
exit "${exit_code}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 12.3 trap 清理资源 【必须】

# ✅ trap:注册 EXIT/INT/TERM 信号的清理函数
#   EXIT  — 脚本正常结束或 exit 时
#   INT   — Ctrl+C
#   TERM  — kill 默认信号

cleanup() {
    local exit_code=$?
    echo "Cleaning up..." >&2

    # 清理临时文件
    [[ -d "${TEMP_DIR:-}" ]] && rm -rf "${TEMP_DIR}"

    # 释放锁
    [[ -f "${LOCK_FILE:-}" ]] && rm -f "${LOCK_FILE}"

    # 杀后台进程
    [[ -n "${BG_PID:-}" ]] && kill "${BG_PID}" 2>/dev/null

    exit "${exit_code}"          # 保留原始退出码
}
trap cleanup EXIT INT TERM

# ✅ 每条 trap 只能注册一个 handler,需要时用列表管理
declare -a _CLEANUP_FUNCS=()
add_cleanup() { _CLEANUP_FUNCS+=("$1"); }
run_cleanup() {
    for func in "${_CLEANUP_FUNCS[@]}"; do
        "${func}"
    done
}
trap run_cleanup EXIT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 12.4 错误日志 【推荐】

# ✅ 统一的日志函数
readonly LOG_FILE="/var/log/myapp/deploy.log"

log_info()  { echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO  $*" | tee -a "${LOG_FILE}"; }
log_warn()  { echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN  $*" | tee -a "${LOG_FILE}" >&2; }
log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR $*" | tee -a "${LOG_FILE}" >&2; }

die() {         # fatal error + exit
    log_error "$@"
    exit 1
}

# ✅ 使用
log_info "Starting deployment..."
if ! deploy; then
    die "Deployment failed: ${?}"
fi
log_info "Deployment complete"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 13.安全与防御

# 13.1 输入验证 【必须】

# ✅ 所有外部输入必须验证
validate_url() {
    local url="$1"
    if [[ ! "${url}" =~ ^https?:// ]]; then
        echo "ERROR: Invalid URL: ${url}" >&2
        return 1
    fi
}

validate_number() {
    local num="$1"
    if [[ ! "${num}" =~ ^[0-9]+$ ]]; then
        echo "ERROR: Not a number: ${num}" >&2
        return 1
    fi
}

# ✅ 防止命令注入:不直接拼接用户输入到命令字符串
# ❌ eval "echo ${user_input}"                    — 危险!
# ❌ ssh host "echo ${user_input}"                — 如果 input 含 "; rm -rf / " →
# ✅ 用参数传递,让命令自己处理引号
ssh host echo "${user_input}"                     # ✅ ssh 自动转义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 13.2 路径遍历防御 【必须】

# ✅ 检查路径是否包含 ..(父目录引用)
validate_path() {
    local path="$1"
    if [[ "${path}" =~ \.\. ]]; then
        echo "ERROR: Path traversal detected in: ${path}" >&2
        return 1
    fi

    # 进一步:要求路径必须在指定基础目录内
    # local real_path
    # real_path="$(realpath "${path}" 2>/dev/null)" || return 1
    # if [[ "${real_path}" != "${BASE_DIR}"/* ]]; then
    #     echo "ERROR: Path outside allowed directory" >&2
    #     return 1
    # fi
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 13.3 临时文件安全 【必须】

# ✅ mktemp:安全创建临时文件/目录
temp_file="$(mktemp)"                    # /tmp/tmp.XXXXXX
temp_dir="$(mktemp -d)"                  # /tmp/tmp.XXXXXX/
temp_file="$(mktemp /tmp/myapp.XXXXXX)"  # 指定模板

# ❌ 不要手动构造临时文件名(可预测 → 竞态条件)
# temp_file="/tmp/myapp_$$"              # PID 可预测,不安全

# ✅ 自动清理
cleanup() { rm -f "${temp_file}"; rm -rf "${temp_dir}"; }
trap cleanup EXIT
1
2
3
4
5
6
7
8
9
10
11

# 13.4 依赖检查 【推荐】

# ✅ 运行前检查依赖
check_dependency() {
    local cmd="$1"
    if ! command -v "${cmd}" &>/dev/null; then
        echo "ERROR: '${cmd}' is required but not installed" >&2
        exit 1
    fi
}

check_dependency curl
check_dependency jq
check_dependency docker

# ✅ 检查依赖版本
check_version() {
    local cmd="$1"
    local min_version="$2"
    # 例如检查 bash 版本
    if (( BASH_VERSINFO[0] < 4 )); then
        echo "ERROR: Bash >= 4.0 required, found ${BASH_VERSION}" >&2
        exit 1
    fi
}
check_version bash 4.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 13.5 权限检查 【推荐】

# ✅ 需要 root 时明确检查
if [[ "$(id -u)" -ne 0 ]]; then
    echo "ERROR: This script must be run as root" >&2
    exit 4
fi

# ✅ 检查文件权限
if [[ ! -r "${config_file}" ]]; then
    echo "ERROR: Cannot read ${config_file}" >&2
    exit 4
fi
1
2
3
4
5
6
7
8
9
10
11

# 14.调试与工具链

# 14.1 调试模式

# ✅ 逐行打印:set -x(生产环境注释掉)
# set -x
# echo "Current user: $(whoami)"
# set +x

# ✅ 定向调试输出到文件
exec 3>/tmp/debug.log
BASH_XTRACEFD=3        # 将 set -x 的输出重定向到 fd 3
set -x

# ✅ 条件调试
if [[ "${DEBUG:-}" == "true" ]]; then
    set -x
fi

# ✅ 手动打印调用栈
print_stack() {
    local frame=0
    while caller "${frame}"; do
        ((frame++))
    done
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 14.2 shellcheck 静态检查

# shellcheck:Shell 脚本静态分析工具(必装)
# 安装:brew install shellcheck  (macOS)
#      apt install shellcheck    (Ubuntu)

shellcheck myscript.sh                    # 检查单个文件
shellcheck scripts/*.sh                   # 批量检查
shellcheck -e SC2034 myscript.sh          # 排除特定规则

# 常用被排除的规则:
# SC2034 - 变量未使用(有时故意保留)
# SC1090 - 无法解析 source 路径
# SC1091 - 未找到 sourced 文件
# SC2155 - declare/export 与 local 混用
1
2
3
4
5
6
7
8
9
10
11
12
13

# 14.3 shfmt 格式化

# shfmt:Shell 格式化工具
# 安装:brew install shfmt

shfmt -w myscript.sh                      # 原地格式化
shfmt -w -i 2 -ci scripts/*.sh            # -i 2 缩进,-ci case 缩进
shfmt -d myscript.sh                      # 只显示 diff,不修改
shfmt -l scripts/*.sh                     # 列出需要格式化的文件
1
2
3
4
5
6
7

# 14.4 bats 测试框架 【可选】

# bats:Bash 自动化测试框架
# 安装:brew install bats-core

# deploy_test.bats
setup() {
    load 'test_helper/bats-support/load'
    load 'test_helper/bats-assert/load'
    source "${BATS_TEST_DIRNAME}/../deploy.sh"
}

@test "deploy fails without tag argument" {
    run deploy_main ""
    assert_failure
    assert_output --partial "tag is required"
}

@test "deploy succeeds with valid tag" {
    run deploy_main "v1.2.3"
    assert_success
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 14.5 CI 集成

# GitHub Actions 示例
- name: Shell Code Quality
  run: |
    # 格式检查
    shfmt -d scripts/         # 只显示 diff
    # 静态分析
    shellcheck scripts/*.sh
    # 单元测试(如果有)
    bats tests/
1
2
3
4
5
6
7
8
9

# 15.常见反模式

反模式 问题 改进
变量不加引号 空格导致单词拆分 "${var}"
用 ls 遍历文件 空格文件名出错 find -print0 或 shell glob
cat file \| cmd 无用进程(UUOC) cmd < file 或 cmd file
反引号 `cmd` 嵌套困难、与引号混淆 $(cmd)
硬编码路径 不可移植 变量或 mktemp
eval 拼接命令 命令注入风险 用数组传参
忽略命令返回值 错误被静默 加 || exit_code=$?
管道中的 while read 变量修改在子 shell 中丢失 进程替换 while ... done < <(cmd)
echo 输出任意数据 echo "-n" 被解释为选项 printf "%s\n" "${data}"
[ ] 中不引用变量 [ $var = "" ] 当 var 为空 → 语法错误 [ "${var}" = "" ]

# 16.代码审查清单

每次 Code Review 时,按以下清单逐项检查:

## 头部
- [ ] Shebang 为 #!/usr/bin/env bash
- [ ] set -euo pipefail 存在
- [ ] 文件头注释完整(用途、用法、退出码)

## 命名
- [ ] 函数/变量 snake_case,常量/全局变量 SCREAMING_SNAKE
- [ ] 私有函数 _ 前缀
- [ ] 无拼音、含义模糊的命名

## 变量
- [ ] 变量引用全部加双引号("${var}")
- [ ] 函数内用 local 声明变量
- [ ] 常量用 readonly 保护
- [ ] 数组引用用 "${arr[@]}"

## 函数
- [ ] 参数用 local 接住
- [ ] 返回值明确(return 0/非0)
- [ ] 错误信息输出到 stderr(>&2)

## 条件与循环
- [ ] 用 [[ ]] 而非 [ ]
- [ ] for 循环中的变量加引号
- [ ] while read 保留 IFS= read -r
- [ ] 无 for i in $(ls ...) 模式

## 命令与管道
- [ ] 命令替换用 $() 而非反引号
- [ ] 需要 pipefail 的场景 set -o pipefail
- [ ] 无 UUOC(cat file | cmd)

## 错误处理
- [ ] 关键命令执行结果检查(|| exit_code=$?)
- [ ] trap 清理临时资源(EXIT INT TERM)
- [ ] exit 退出码有意义

## 安全
- [ ] 外部输入(参数、环境变量、文件内容)有校验
- [ ] 无 eval 拼接用户输入
- [ ] 临时文件用 mktemp 创建
- [ ] 无路径遍历风险
- [ ] 依赖命令有 check_dependency

## 可维护性
- [ ] 通过 shellcheck 检查
- [ ] 通过 shfmt 格式化
- [ ] 复杂逻辑有注释说明"为什么"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 17.常见陷阱速查

以下陷阱即使在有经验的开发者中也常见,值得定期回顾。

# 17.1 变量与引号陷阱

# 陷阱 描述 正解
1 变量不加引号 rm $file 当 file 含空格 → 删错文件 rm "${file}"
2 $* vs $@ $* 把所有参数合并成一个字符串 "$@" — 每个参数独立引号
3 echo 处理 -n echo "${var}" 当 var="-n" → 被吃掉 printf "%s\n" "${var}"
4 IFS 影响词分割 修改 IFS 后 $var 的行为变化 尽量不用未引用变量
5 空变量测试 [ $x = "" ] 当 x 为空 → 语法错误 [ "${x}" = "" ] 或 [[ -z ${x} ]]

# 17.2 流程控制陷阱

# 陷阱 描述 正解
1 set -e 被 if 等吞掉 if cmd; then — cmd 失败不会触发 set -e 理解 set -e 的例外情况
2 管道中 while 在子 shell cmd | while read; do var=1; done; echo $var — 为空 while ... done < <(cmd)
3 [ ] 缺少空格 [ -f"${file}" ] → 语法错误 [ -f "${file}" ](各处加空格)
4 == 在 [ ] 中不可移植 [ "${a}" == "${b}" ] 在 POSIX sh 中不工作 [ "${a}" = "${b}" ] 或用 [[ ]]

# 17.3 性能陷阱

# 陷阱 描述 正解
1 循环中执行子命令 for f in $(find ...); do → 子进程开销大 find -print0 \| while read 或用进程替换
2 cat file \| grep pattern 多余 cat 进程(UUOC) grep pattern file
3 循环中频繁重定向 每次循环打开/关闭文件 在循环外 exec 3>file 复用 fd
4 大文件用 while read + echo 逐行 每个 echo 一个 write 系统调用 用 awk 或 sed 批量处理

# 17.4 安全陷阱

# 陷阱 描述 正解
1 eval 拼接用户输入 eval "echo ${input}" → 命令注入 永远不要 eval 用户输入
2 临时文件名可预测 temp=/tmp/myapp_$$ → 竞态条件 mktemp
3 curl \| bash 不验证 下载并执行远程脚本 → 中间人攻击 验证 HTTPS + 校验 SHA256
4 密码在命令行中 mysql -p"${PASSWORD}" → ps 可见 用配置文件、环境变量或 stdin

本文档将随 Shell 实践演进持续更新,欢迎通过 GitHub issues (opens new window) 反馈问题和建议。

#Shell#Bash#代码规范
上次更新: 2026/06/17, 11:39:29
Rust编程代码规范指南
项目代码提交规范

← Rust编程代码规范指南 项目代码提交规范→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式