脚本规范:Google Shell Style Guide要点 | Script Standards: Google Shell Style Guide Essentials

章节概述

本章详细讲解 Google Shell Style Guide 的核心规范,包括文件头格式、变量命名、缩进规则、函数命名、注释标准以及错误处理最佳实践。遵循这些规范能显著提升 Shell 脚本的可读性、可维护性和团队协作效率。

核心理念:代码是写给人看的,附带能在机器上执行。Google Shell Style Guide 将这一哲学贯彻到每一个细节,让 Shell 脚本也能像工程化语言一样优雅、一致、可维护。


第1节:文件头规范(Shebang + 注释)

文件头是脚本的第一印象,必须包含 shebang 行和版权/用途注释。

Shebang 行

#!/bin/bash
# 必须是文件的第一行
# bash 优于 sh,因为 bash 提供更多现代特性
 
#!/usr/bin/env bash
# 推荐方式,可移植性更好,自动寻找 PATH 中的 bash

标准文件头模板

#!/usr/bin/env bash
#
# Copyright 2026 Your Company Name.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
 
# ============================================================
# 文件名: script.sh
# 描述: 该脚本用于处理用户数据并生成报告
# 用法: ./script.sh [OPTIONS] <input_file>
# 作者: 张三 (zhangsan@example.com)
# 日期: 2026-08-22
# ============================================================

文件头规范对照表

元素要求说明
Shebang#!/usr/bin/env bash第一行,无空行
版权声明Apache 2.0 或 MIT团队统一选择
文件描述必须包含用途、用法、参数说明
作者信息推荐方便联系和维护
版本信息推荐VERSION="1.0.0"

第2节:变量命名规范

变量命名是代码可读性的基石,Google 规范有严格的命名约定。

变量命名规则

# ✅ 正确:全大写 + 下划线(常量/环境变量)
readonly MAX_RETRIES=3
export DATABASE_URL="mysql://localhost/mydb"
 
# ✅ 正确:小写 + 下划线(局部变量)
local count=0
local user_name=""
 
# ❌ 错误:驼峰命名
local userName=""     # 不要用驼峰
local USERNAME=""     # 常量才全大写
 
# ❌ 错误:连字符(bash 不支持)
local my-var=""       # 连字符在 bash 中是减号

变量命名速查表

类型风格示例
常量UPPER_SNAKE_CASEMAX_BUFFER_SIZE=1024
环境变量UPPER_SNAKE_CASEexport PATH="/usr/bin"
局部变量lower_snake_caselocal file_count=0
循环变量单字母或语义名i, item, line
布尔变量is_/has_/can_ 前缀is_valid=true
数组lower_snake_caselocal files=()

变量引用

# ✅ 始终用双引号包裹变量(防止分词和通配符展开)
echo "${user_name}"
cat "${config_file}"
 
# ✅ 数组展开必须用 "${array[@]}"
files=("a.txt" "b.txt")
cat "${files[@]}"
 
# ❌ 危险:不加引号
echo $user_name    # 如果值含空格会出错
cat $config_file   # 如果路径含空格会出错

第3节:缩进与格式(2空格)

Google 规范明确要求 2空格缩进,不使用 Tab。

缩进规则

# ✅ 2空格缩进
if [[ -f "${file}" ]]; then
  echo "Found file: ${file}"
  if [[ -r "${file}" ]]; then
    echo "File is readable"
  fi
fi
 
# ❌ 不要用 Tab 或 4空格
if [[ -f "${file}" ]]; then
    echo "4 spaces is wrong"    # 不要用 4 空格
fi
 
# ✅ case 语句缩进
case "${action}" in
  start)
    start_service
    ;;
  stop)
    stop_service
    ;;
  *)
    echo "Unknown action: ${action}" >&2
    return 1
    ;;
esac
 
# ✅ 函数体缩进
my_function() {
  local result=""
  result=$(do_something)
  echo "${result}"
}

格式规范

项目规则示例
缩进2空格 local x=1
Tab禁止使用
续行4空格缩进command \
--option \
--value
空行函数间1空行函数与函数之间
行尾无尾随空格
文件尾1个换行符

续行格式

# ✅ 长命令续行:4空格缩进
very_long_command \
    --option1 value1 \
    --option2 value2 \
    --option3 value3
 
# ✅ 管道链续行
cat "${input_file}" \
    | grep "^ERROR" \
    | sort \
    | uniq -c \
    | sort -rn \
    > "${output_file}"

第4节:函数命名与结构

函数是代码复用的核心,命名和结构需遵循统一标准。

函数命名

# ✅ 正确:小写 + 下划线,动词开头
do_something() { ... }
get_user_input() { ... }
validate_config() { ... }
is_valid_email() { ... }
 
# ✅ 内部辅助函数用下划线前缀(约定私有)
_is_valid_port() {
  local port="${1}"
  [[ "${port}" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 ))
}
 
# ❌ 错误:驼峰或大写
DoSomething() { ... }   # 不要用大写开头
getUserInput() { ... }   # 不要用驼峰

函数定义格式

# ✅ 推荐格式:括号单独一行
my_function() {
  # 函数体
}
 
# 也可以用 function 关键字(但不推荐,可移植性差)
function my_function {
  # 函数体
}
 
# 函数结构模板
calculate_sum() {
  # 1. 参数校验
  if [[ $# -lt 2 ]]; then
    echo "Usage: calculate_sum <num1> <num2>" >&2
    return 1
  fi
 
  # 2. 参数解析
  local num1="${1}"
  local num2="${2}"
 
  # 3. 核心逻辑
  local result=$(( num1 + num2 ))
 
  # 4. 输出结果
  echo "${result}"
}

函数文档注释

# 计算两个数字的和
#
# 参数:
#   $1 - 第一个数字
#   $2 - 第二数字
#
# 返回值:
#   0 - 成功
#   1 - 参数错误
#
# 示例:
#   result=$(calculate_sum 3 5)
#   echo "${result}"  # 输出: 8
calculate_sum() {
  # ...
}

第5节:注释规范

注释是代码的说明书,好的注释能节省大量维护时间。

注释类型

# ✅ 行注释:# 后跟一个空格
# 这是注释
 
# ✅ 文件头注释:描述脚本用途
# 该脚本用于自动化部署
 
# ✅ TODO 注释:标记待办事项
# TODO(zhangsan): 需要添加错误处理
# TODO(2026-12-01): 重构为更通用的实现
 
# ✅ BUG 注释:记录已知问题
# BUG: 在 CentOS 7 上会出错,已知问题
 
# ✅ HACK 注释:记录临时解决方案
# HACK: 为了兼容旧版本,暂时使用这个方法
 
# ❌ 无用注释
echo "hello"  # 输出 hello    # 这种注释是多余的
x=1           # x 等于 1      # 这种注释也是多余的
 
# ✅ 解释"为什么"而非"是什么"
# 使用 sleep 而非等待循环,因为目标服务启动需要时间
sleep 10
 
# 而不是:
# 等待 10 秒
sleep 10

注释最佳实践对照

场景做法示例
复杂逻辑必须注释说明算法思路
正则表达式必须注释解释每个部分的含义
临时方案用 HACK 标记# HACK: ...
已知问题用 BUG 标记# BUG: ...
待办事项用 TODO 标记# TODO(owner): ...
简单赋值不加注释count=0 不需要注释

第6节:错误处理规范

健壮的脚本必须有完善的错误处理机制。

基本错误处理

# ✅ set -euo pipefail(推荐组合)
set -euo pipefail
 
# -e: 命令失败时立即退出
# -u: 使用未定义变量时报错
# -o pipefail: 管道中任意命令失败则整体失败
 
# ✅ trap 捕获错误
cleanup() {
  local exit_code=$?
  echo "Script failed with exit code: ${exit_code}" >&2
  # 清理临时文件
  rm -f "${TMP_FILE:-}"
  exit "${exit_code}"
}
trap cleanup EXIT ERR
 
# ✅ 函数返回值检查
if ! do_something; then
  echo "Failed to do something" >&2
  return 1
fi

错误处理模式

# 模式1:错误信息输出到 stderr
error() {
  echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}
 
# 模式2:带退出码的错误
die() {
  local code="${1:-1}"
  shift
  echo "[FATAL] $*" >&2
  exit "${code}"
}
 
# 使用示例
validate_input() {
  local input="${1}"
  if [[ -z "${input}" ]]; then
    die 2 "Input cannot be empty"
  fi
}
 
# 模式3:可恢复错误
safe_execute() {
  local description="${1}"
  shift
  if ! "$@"; then
    error "Failed: ${description}"
    return 1
  fi
  return 0
}
 
# 使用
safe_execute "database backup" pg_dump -h localhost mydb > backup.sql

常见错误处理对照

错误场景推荐做法避免做法
命令失败set -e + trap忽略错误
未定义变量set -u直接使用空变量
管道失败set -o pipefail只检查最后一条命令
资源清理trap cleanup EXIT不清理临时文件
错误输出>&2 重定向到 stderr输出到 stdout

第7节:Google Shell Style Guide 核心要点汇总

必须遵守的规则(Must)

规则说明
Shebang第一行必须是 #!/usr/bin/env bash
2空格缩进禁止 Tab,禁止 4 空格
变量加引号"${var}" 而非 $var
set -euo pipefail每个脚本都应设置
函数用 ()my_func() { 而非 function my_func {
命令替换用 $()$() 而非反引号 ` `
[[ ]] 优于 [ ]bash 脚本用 [[ ]]

应该遵守的规则(Should)

规则说明
文件头注释包含版权、用途、用法
函数文档描述参数和返回值
常量用 readonly不可变值标记为只读
局部变量用 local函数内变量加 local
错误信息到 stderrecho "error" >&2
临时文件用 mktemp不要硬编码临时路径

工具链推荐

工具用途
shellcheck静态分析,检测常见错误
shfmt自动格式化 Shell 脚本
editorconfig统一编辑器配置
git hooks提交前自动检查格式
# shellcheck 示例
# shellcheck shell=bash
#!/usr/bin/env bash
 
# shellcheck disable=SC2086  # 故意不加引号的情况
echo $unquoted_var

实战:完整的脚本模板

#!/usr/bin/env bash
#
# Copyright 2026 Your Company.
#
# Licensed under the Apache License, Version 2.0
 
set -euo pipefail
 
# ============================================================
# 全局常量
# ============================================================
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly VERSION="1.0.0"
 
# ============================================================
# 日志函数
# ============================================================
log_info()  { echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S') $*"; }
log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2; }
die()       { log_error "$@"; exit 1; }
 
# ============================================================
# 清理函数
# ============================================================
cleanup() {
  local exit_code=$?
  rm -f "${TMP_FILE:-}"
  exit "${exit_code}"
}
trap cleanup EXIT ERR
 
# ============================================================
# 参数解析
# ============================================================
usage() {
  cat <<EOF
Usage: ${SCRIPT_NAME} [OPTIONS] <file>
Version: ${VERSION}
 
Options:
  -h, --help      Show this help message
  -v, --verbose   Enable verbose output
  -o <output>     Output file (default: stdout)
EOF
}
 
main() {
  local verbose=false
  local output=""
 
  while [[ $# -gt 0 ]]; do
    case "${1}" in
      -h|--help)    usage; exit 0 ;;
      -v|--verbose) verbose=true; shift ;;
      -o)           output="${2}"; shift 2 ;;
      -*)           die "Unknown option: ${1}" ;;
      *)            break ;;
    esac
  done
 
  [[ $# -eq 0 ]] && die "Missing required argument: <file>"
 
  local input_file="${1}"
 
  if [[ ! -f "${input_file}" ]]; then
    die "File not found: ${input_file}"
  fi
 
  log_info "Processing file: ${input_file}"
 
  # 核心逻辑
  process_file "${input_file}" "${output}"
}
 
process_file() {
  local input="${1}"
  local output="${2:-}"
 
  if [[ -n "${output}" ]]; then
    cat "${input}" > "${output}"
    log_info "Output written to ${output}"
  else
    cat "${input}"
  fi
}
 
main "$@"

本节内容覆盖了 Google Shell Style Guide 的核心要点,遵循这些规范能让你的 Shell 脚本达到工程级质量标准。