shell-scripting

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Bash Scripting Best Practices

Bash脚本编写最佳实践

Guidance for writing reliable, maintainable bash scripts following modern best practices. Emphasises simplicity, automated tooling, and defensive programming without over-engineering.
本指南介绍如何遵循现代最佳实践编写可靠、可维护的Bash脚本,强调简洁性、自动化工具与防御性编程,同时避免过度设计。

When to Use Shell (and When Not To)

何时使用Shell(以及何时不使用)

Use Shell For:

适合使用Shell的场景:

  • Small utilities and simple wrapper scripts (<100 lines)
  • Orchestrating other programmes and tools
  • Simple automation tasks
  • Build/deployment scripts with straightforward logic
  • Quick data transformation pipelines
  • 小型工具与简单包装脚本(少于100行)
  • 编排其他程序与工具
  • 简单自动化任务
  • 逻辑直接的构建/部署脚本
  • 快速数据转换流水线

Do NOT Use Shell For:

不适合使用Shell的场景:

  • Complex business logic or data structures
  • Performance-critical code
  • Scripts requiring extensive error handling
  • Anything over ~100 lines or with non-straightforward control flow
  • When you need proper data structures beyond arrays
Critical: If your script grows too large (1000+ lines) or complex, consider offering to rewrite it in a proper language (Python, Go, etc.) before it becomes unmaintainable.
  • 复杂业务逻辑或数据结构
  • 性能敏感型代码
  • 需要大量错误处理的脚本
  • 代码量超过约100行或控制流程不直观的脚本
  • 需要数组之外的复杂数据结构的场景
关键提示:如果你的脚本变得过大(1000行以上)或过于复杂,建议在其变得难以维护之前,改用Python、Go等成熟语言重写。

Mandatory Foundations

必备基础

Every bash script must have these elements:
每个Bash脚本都必须包含以下元素:

1. Proper Shebang

1. 正确的Shebang

bash
#!/usr/bin/env bash
Why: Portable across systems where bash may not be at
/bin/bash
(e.g., macOS, BSD, NixOS).
Alternative:
#!/bin/bash
if you know the script only runs on Linux and prefer explicit paths.
bash
#!/usr/bin/env bash
原因:在bash可能不在
/bin/bash
路径的系统(如macOS、BSD、NixOS)上保证可移植性。
替代方案:如果确定脚本仅在Linux运行且偏好明确路径,可使用
#!/bin/bash

2. Strict Mode

2. 严格模式

bash
set -euo pipefail
What each flag does:
  • -e
    : Exit immediately if any command fails (non-zero exit)
  • -u
    : Treat unset variables as errors
  • -o pipefail
    : Pipe fails if ANY command in pipeline fails (not just the last)
When to add
-x
: Only for debugging, not in production scripts (makes output noisy).
bash
set -euo pipefail
各标志的作用
  • -e
    :若任何命令执行失败(返回非零值)则立即退出
  • -u
    :将未定义变量视为错误
  • -o pipefail
    :流水线中任意命令失败则整个流水线失败(而非仅最后一个命令)
何时添加
-x
:仅用于调试,生产脚本中不要使用(会导致输出冗余)。

3. ShellCheck Compliance

3. 符合ShellCheck规范

Run ShellCheck on EVERY script before committing:
bash
shellcheck script.sh
Fix all warnings. ShellCheck catches:
  • Unquoted variables
  • Deprecated syntax
  • Common bugs and pitfalls
  • Portability issues
提交前必须对每个脚本运行ShellCheck:
bash
shellcheck script.sh
修复所有警告。ShellCheck可检测:
  • 未加引号的变量
  • 已弃用的语法
  • 常见bug与陷阱
  • 可移植性问题

4. Basic Script Structure

4. 基础脚本结构

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

Brief description of what this script does

脚本功能简要描述

Simple error reporting

简单错误报告

die() { echo "Error: ${1}" >&2 exit 1 }
die() { echo "Error: ${1}" >&2 exit 1 }

Your code here

你的代码写在这里

undefined
undefined

Core Safety Patterns

核心安全模式

Always Quote Variables

始终为变量添加引号

Why: Prevents word splitting and globbing disasters.
bash
undefined
原因:防止单词拆分与通配符匹配灾难。
bash
undefined

Wrong - dangerous

错误示例 - 存在风险

cp $source $destination rm -rf $prefix/bin
cp $source $destination rm -rf $prefix/bin

Correct - safe

正确示例 - 安全

cp "${source}" "${destination}" rm -rf "${prefix}/bin"
cp "${source}" "${destination}" rm -rf "${prefix}/bin"

Special case: Always use braces with variables

特殊情况:变量始终使用大括号包裹

echo "${var}" # Good echo "$var" # Acceptable but less consistent echo $var # Bad - unquoted
undefined
echo "${var}" # 推荐写法 echo "$var" # 可接受但一致性较差 echo $var # 错误写法 - 未加引号
undefined

Check Required Variables

检查必填变量

bash
undefined
bash
undefined

Fail fast if required variables aren't set

若必填变量未设置则立即终止

: "${REQUIRED_VAR:?REQUIRED_VAR must be set}"
: "${REQUIRED_VAR:?REQUIRED_VAR必须设置}"

Or with custom message

或自定义提示信息

: "${DATABASE_URL:?DATABASE_URL is required. Set it in .env}"
undefined
: "${DATABASE_URL:?DATABASE_URL为必填项,请在.env中设置}"
undefined

Validate Inputs

验证输入

bash
undefined
bash
undefined

Check file exists before operating on it

操作文件前检查文件是否存在

[[ -f "${config_file}" ]] || die "Config file not found: ${config_file}"
[[ -f "${config_file}" ]] || die "未找到配置文件:${config_file}"

Check command exists before using it

使用命令前检查命令是否存在

command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
command -v jq >/dev/null 2>&1 || die "需要安装jq但未找到"

Validate directory before cd

切换目录前验证目录是否存在

[[ -d "${target_dir}" ]] || die "Directory does not exist: ${target_dir}"
undefined
[[ -d "${target_dir}" ]] || die "目录不存在:${target_dir}"
undefined

Essential Patterns

核心模式

Pattern 1: Simple Script Template

模式1:简单脚本模板

Use this for straightforward scripts:
bash
#!/usr/bin/env bash
set -euo pipefail
适用于逻辑直接的脚本:
bash
#!/usr/bin/env bash
set -euo pipefail

Description: Process log files and extract errors

描述:处理日志文件并提取错误信息

die() { echo "Error: ${1}" >&2 exit 1 }
die() { echo "Error: ${1}" >&2 exit 1 }

Check dependencies

检查依赖

command -v jq >/dev/null 2>&1 || die "jq required"
command -v jq >/dev/null 2>&1 || die "需要安装jq"

Validate arguments

验证参数

[[ $# -eq 1 ]] || die "Usage: ${0} <logfile>" logfile="${1}" [[ -f "${logfile}" ]] || die "File not found: ${logfile}"
[[ $# -eq 1 ]] || die "使用方法:${0} <日志文件>" logfile="${1}" [[ -f "${logfile}" ]] || die "未找到文件:${logfile}"

Main logic

主逻辑

grep ERROR "${logfile}" | jq -r '.message'
undefined
grep ERROR "${logfile}" | jq -r '.message'
undefined

Pattern 2: Cleanup on Exit

模式2:退出时自动清理

Use trap for guaranteed cleanup:
bash
#!/usr/bin/env bash
set -euo pipefail
使用trap确保执行清理操作:
bash
#!/usr/bin/env bash
set -euo pipefail

Create temp directory and ensure cleanup

创建临时目录并确保退出时清理

tmpdir=$(mktemp -d) trap 'rm -rf "${tmpdir}"' EXIT
tmpdir=$(mktemp -d) trap 'rm -rf "${tmpdir}"' EXIT

Now use tmpdir safely - cleanup happens automatically

现在可安全使用tmpdir - 退出时会自动清理

echo "Working in: ${tmpdir}"
undefined
echo "工作目录:${tmpdir}"
undefined

Pattern 3: Safe Function Definition

模式3:安全函数定义

Functions should be simple and focused:
bash
undefined
函数应简洁且聚焦单一功能:
bash
undefined

Good: Simple, single-purpose function

推荐:简洁、单一职责的函数

check_dependency() { local cmd="${1}" command -v "${cmd}" >/dev/null 2>&1 || die "${cmd} not installed" }
check_dependency() { local cmd="${1}" command -v "${cmd}" >/dev/null 2>&1 || die "未安装${cmd}" }

Good: Local variables, clear purpose

推荐:使用局部变量,目的明确

process_file() { local file="${1}" local output="${2}"
[[ -f "${file}" ]] || die "Input file missing: ${file}"

# Do processing
sed 's/foo/bar/g' "${file}" > "${output}"
}

**Important**: Declare and set variables from command substitution separately to catch errors:

```bash
process_file() { local file="${1}" local output="${2}"
[[ -f "${file}" ]] || die "输入文件缺失:${file}"

# 执行处理操作
sed 's/foo/bar/g' "${file}" > "${output}"
}

**重要提示**:单独声明并设置命令替换的变量,以便捕获错误:

```bash

Wrong - hides errors

错误示例 - 隐藏错误

local result="$(failing_command)"
local result="$(failing_command)"

Correct - catches errors

正确示例 - 可捕获错误

local result result="$(failing_command)" # Will fail properly with set -e
undefined
local result result="$(failing_command)" # 开启set -e后会正确终止
undefined

Pattern 4: Safe Array Handling

模式4:安全数组处理

Arrays are useful for handling lists with spaces:
bash
undefined
数组适用于处理包含空格的列表:
bash
undefined

Create array

创建数组

declare -a files=("file one.txt" "file two.txt" "file three.txt")
declare -a files=("file one.txt" "file two.txt" "file three.txt")

Iterate safely - always quote with [@]

安全遍历 - 始终使用[@]并加引号

for file in "${files[@]}"; do echo "Processing: ${file}" done
for file in "${files[@]}"; do echo "正在处理:${file}" done

Build command arguments safely

安全构建命令参数

declare -a flags=(--verbose --output "${output_file}") mycommand "${flags[@]}" "${input}"
declare -a flags=(--verbose --output "${output_file}") mycommand "${flags[@]}" "${input}"

Read command output into array

将命令输出读取到数组

mapfile -t lines < <(grep pattern "${file}")
undefined
mapfile -t lines < <(grep pattern "${file}")
undefined

Pattern 5: Conditional Testing

模式5:条件测试

Use
[[ ]]
for bash (safer and more features):
bash
undefined
在Bash中使用
[[ ]]
更安全且功能更丰富:
bash
undefined

File tests

文件测试

[[ -f "${file}" ]] # File exists [[ -d "${dir}" ]] # Directory exists [[ -r "${file}" ]] # File readable [[ -w "${file}" ]] # File writable [[ -x "${binary}" ]] # File executable
[[ -f "${file}" ]] # 文件存在 [[ -d "${dir}" ]] # 目录存在 [[ -r "${file}" ]] # 文件可读 [[ -w "${file}" ]] # 文件可写 [[ -x "${binary}" ]] # 文件可执行

String tests

字符串测试

[[ -z "${var}" ]] # String is empty [[ -n "${var}" ]] # String is not empty [[ "${a}" == "${b}" ]] # String equality (use ==, not =)
[[ -z "${var}" ]] # 字符串为空 [[ -n "${var}" ]] # 字符串非空 [[ "${a}" == "${b}" ]] # 字符串相等(使用==而非=)

Numeric comparison (use (( )) for numbers)

数值比较(使用(( ))处理数字)

(( count > 0 )) (( total >= minimum ))
(( count > 0 )) (( total >= minimum ))

Combined conditions

组合条件

[[ -f "${file}" && -r "${file}" ]] || die "File not readable: ${file}"
undefined
[[ -f "${file}" && -r "${file}" ]] || die "文件不可读:${file}"
undefined

Pattern 6: Simple Argument Handling

模式6:简单参数处理

For simple scripts, prefer positional arguments:
bash
#!/usr/bin/env bash
set -euo pipefail
对于简单脚本,优先使用位置参数:
bash
#!/usr/bin/env bash
set -euo pipefail

For 1-3 arguments, just use positional parameters

对于1-3个参数,直接使用位置参数

[[ $# -eq 2 ]] || die "Usage: ${0} <source> <dest>"
source="${1}" dest="${2}"
[[ -f "${source}" ]] || die "Source not found: ${source}"

For scripts needing flags, keep it simple:

```bash
[[ $# -eq 2 ]] || die "使用方法:${0} <源文件> <目标文件>"
source="${1}" dest="${2}"
[[ -f "${source}" ]] || die "未找到源文件:${source}"

对于需要标志的脚本,保持逻辑简洁:

```bash

Use environment variables instead of complex flag parsing

使用环境变量替代复杂的标志解析

VERBOSE="${VERBOSE:-false}" DRY_RUN="${DRY_RUN:-false}"
VERBOSE="${VERBOSE:-false}" DRY_RUN="${DRY_RUN:-false}"

Run like: VERBOSE=true DRY_RUN=true ./script.sh input.txt

运行方式:VERBOSE=true DRY_RUN=true ./script.sh input.txt

undefined
undefined

Pattern 7: Process Substitution Over Temp Files

模式7:使用进程替换替代临时文件

Avoid creating temporary files when possible:
bash
undefined
尽可能避免创建临时文件:
bash
undefined

Instead of:

不推荐的写法:

first_command > /tmp/output.txt second_command < /tmp/output.txt rm /tmp/output.txt
first_command > /tmp/output.txt second_command < /tmp/output.txt rm /tmp/output.txt

Use process substitution:

推荐使用进程替换:

second_command <(first_command)
second_command <(first_command)

For multiple inputs:

处理多个输入:

diff <(sort file1.txt) <(sort file2.txt)
undefined
diff <(sort file1.txt) <(sort file2.txt)
undefined

Pattern 8: Prefer Builtins Over External Commands

模式8:优先使用内置命令而非外部命令

Builtins are faster and more reliable:
bash
undefined
内置命令更快且更可靠:
bash
undefined

Use bash parameter expansion over sed/awk for simple cases

简单场景下使用Bash参数扩展替代sed/awk

filename="${path##/}" # basename dirname="${path%/}" # dirname extension="${filename##.}" # get extension name="${filename%.}" # remove extension
filename="${path##/}" # 获取文件名(替代basename) dirname="${path%/}" # 获取目录名(替代dirname) extension="${filename##.}" # 获取文件扩展名 name="${filename%.}" # 移除文件扩展名

Use (( )) for arithmetic over expr

使用(( ))处理算术运算替代expr

count=$(( count + 1 )) # Not: count=$(expr ${count} + 1)
count=$(( count + 1 )) # 而非:count=$(expr ${count} + 1)

Use [[ ]] over [ ] or test

使用[[ ]]替代[ ]或test

[[ -f "${file}" ]] # Not: test -f "${file}"
[[ -f "${file}" ]] # 而非:test -f "${file}"

Use ${#var} for string length

使用${#var}获取字符串长度

length="${#string}" # Not: length=$(echo "${string}" | wc -c)
undefined
length="${#string}" # 而非:length=$(echo "${string}" | wc -c)
undefined

Intermediate Patterns

进阶模式

Pattern 9: Structured Logging

模式9:结构化日志

Keep logging simple and consistent:
bash
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] ${1}" >&2
}

error() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: ${1}" >&2
}
保持日志简洁且一致:
bash
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] ${1}" >&2
}

error() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: ${1}" >&2
}

Usage

使用示例

log "Starting process" error "Failed to connect to database"
undefined
log "开始处理" error "连接数据库失败"
undefined

Pattern 10: Main Function Pattern

模式10:主函数模式

For longer scripts (50+ lines), use a main function:
bash
#!/usr/bin/env bash
set -euo pipefail

setup() {
    # Dependency checks, variable initialisation
    command -v jq >/dev/null 2>&1 || die "jq required"
}

process() {
    # Main logic here
    log "Processing data"
}

cleanup() {
    # Cleanup if needed
    log "Cleanup complete"
}

main() {
    setup
    process
    cleanup
}
对于较长的脚本(50行以上),使用主函数:
bash
#!/usr/bin/env bash
set -euo pipefail

setup() {
    # 依赖检查、变量初始化
    command -v jq >/dev/null 2>&1 || die "需要安装jq"
}

process() {
    # 主逻辑写在这里
    log "正在处理数据"
}

cleanup() {
    # 必要时执行清理操作
    log "清理完成"
}

main() {
    setup
    process
    cleanup
}

Call main with all script arguments

传入所有脚本参数调用主函数

main "${@}"
undefined
main "${@}"
undefined

Pattern 11: Idempotent Operations

模式11:幂等操作

Scripts should be safe to run multiple times:
bash
undefined
脚本应可安全重复执行:
bash
undefined

Check before creating

创建前先检查

if [[ ! -d "${target_dir}" ]]; then mkdir -p "${target_dir}" fi
if [[ ! -d "${target_dir}" ]]; then mkdir -p "${target_dir}" fi

Check before writing config

写入配置前先检查

if [[ ! -f "${config_file}" ]]; then echo "DEFAULT_VALUE=true" > "${config_file}" fi
if [[ ! -f "${config_file}" ]]; then echo "DEFAULT_VALUE=true" > "${config_file}" fi

Use atomic operations

使用原子操作

mv "${source}" "${dest}" # Atomic on same filesystem
undefined
mv "${source}" "${dest}" # 同一文件系统上的操作是原子性的
undefined

Pattern 12: Safe While Loop Reading

模式12:安全的While循环读取

Don't pipe to while (creates subshell):
bash
undefined
不要通过管道传递给while(会创建子shell):
bash
undefined

Wrong - variables modified in subshell are lost

错误示例 - 子shell中修改的变量会丢失

count=0 cat file.txt | while read -r line; do (( count++ )) done echo "${count}" # Will be 0!
count=0 cat file.txt | while read -r line; do (( count++ )) done echo "${count}" # 结果为0!

Correct - use process substitution

正确示例 - 使用进程替换

count=0 while read -r line; do (( count++ )) done < <(cat file.txt) echo "${count}" # Correct count
count=0 while read -r line; do (( count++ )) done < <(cat file.txt) echo "${count}" # 结果正确

Or use mapfile for simple cases

简单场景下可使用mapfile

mapfile -t lines <file.txt count="${#lines[@]}"
undefined
mapfile -t lines <file.txt count="${#lines[@]}"
undefined

Style Guidelines

风格指南

Formatting

格式规范

  • Indentation: 2 spaces, never tabs
  • Line length: Maximum 120 characters
  • Long strings: Use here-documents or embedded newlines
bash
undefined
  • 缩进:2个空格,禁止使用制表符
  • 行长度:最大120个字符
  • 长字符串:使用here-doc或嵌入换行符
bash
undefined

Long command - break at logical points

长命令 - 在逻辑断点处换行

docker run
--name my-container
--volume "${PWD}:/data"
--env "FOO=bar"
my-image:latest
docker run \ --name my-container \ --volume "${PWD}:/data" \ --env "FOO=bar" \ my-image:latest

Long string - use here-doc

长字符串 - 使用here-doc

cat <<EOF This is a long message that spans multiple lines and is more readable this way. EOF
undefined
cat <<EOF 这是一条长消息 跨越多个行 这样编写可读性更强。 EOF
undefined

Naming Conventions

命名规范

bash
undefined
bash
undefined

Functions: lowercase with underscores

函数:小写字母加下划线

check_dependencies() { ... } process_files() { ... }
check_dependencies() { ... } process_files() { ... }

Local variables: lowercase with underscores

局部变量:小写字母加下划线

local input_file="${1}" local line_count=0
local input_file="${1}" local line_count=0

Constants/environment variables: UPPERCASE with underscores

常量/环境变量:大写字母加下划线

readonly MAX_RETRIES=3 readonly CONFIG_DIR="/etc/myapp"
readonly MAX_RETRIES=3 readonly CONFIG_DIR="/etc/myapp"

Source files: lowercase with underscores

源文件:小写字母加下划线

my_library.sh

my_library.sh

undefined
undefined

File Extensions

文件扩展名

  • Executables:
    .sh
    extension OR no extension (prefer no extension for user-facing commands)
  • Libraries: Always
    .sh
    extension and NOT executable
  • 可执行文件:使用
    .sh
    扩展名或无扩展名(面向用户的命令优先选择无扩展名)
  • 库文件:必须使用
    .sh
    扩展名且不可执行

Function Documentation

函数文档

Document functions that aren't obvious:
bash
undefined
对功能不直观的函数添加文档:
bash
undefined

Good: Simple function, no comment needed (name says it all)

推荐:简单函数,无需注释(名称已说明功能)

check_file_exists() { [[ -f "${1}" ]] }
check_file_exists() { [[ -f "${1}" ]] }

Good: Complex function, documented

推荐:复杂函数,添加文档

Processes log files and extracts error messages

处理日志文件并提取错误信息

Arguments:

参数:

$1 - Input log file path

$1 - 输入日志文件路径

$2 - Output directory

$2 - 输出目录

Returns:

返回值:

0 on success, 1 on failure

0表示成功,1表示失败

process_logs() { local logfile="${1}" local output_dir="${2}" # Implementation }
undefined
process_logs() { local logfile="${1}" local output_dir="${2}" # 实现代码 }
undefined

What to Avoid

避免事项

Don't Use These

禁止使用的写法

bash
undefined
bash
undefined

Don't use backticks - use $()

不要使用反引号 - 使用$()

output=
command
# Old style output=$(command) # Correct
output=
command
# 旧写法 output=$(command) # 正确写法

Don't use eval - almost always wrong

不要使用eval - 几乎都是错误的

eval "${user_input}" # Dangerous!
eval "${user_input}" # 存在风险!

Don't use expr - use (( ))

不要使用expr - 使用(( ))

result=$(expr 5 + 3) # Old style result=$(( 5 + 3 )) # Correct
result=$(expr 5 + 3) # 旧写法 result=$(( 5 + 3 )) # 正确写法

Don't use [ ] when [[ ]] is available

有[[ ]]可用时不要使用[ ]

[ -f "${file}" ] # POSIX compatible, less features [[ -f "${file}" ]] # Bash, safer and more features
[ -f "${file}" ] # 兼容POSIX,但功能较少 [[ -f "${file}" ]] # Bash专用,更安全且功能丰富

Don't use $[ ] for arithmetic - deprecated

不要使用$[ ]处理算术运算 - 已弃用

result=$[5 + 3] # Deprecated result=$(( 5 + 3 )) # Correct
result=$[5 + 3] # 已弃用 result=$(( 5 + 3 )) # 正确写法

Don't use function keyword unnecessarily

不要不必要地使用function关键字

function foo() { ... } # Redundant foo() { ... } # Cleaner
undefined
function foo() { ... } # 冗余写法 foo() { ... } # 简洁写法
undefined

Anti-Patterns

反模式

bash
undefined
bash
undefined

Don't glob or split unquoted

不要对未加引号的变量进行通配或拆分

rm ${files} # DANGEROUS rm "${files}" # Safe
rm ${files} # 非常危险 rm "${files}" # 安全写法

Don't use ls output in scripts

不要在脚本中解析ls的输出

for file in $(ls); do # Breaks with spaces for file in *; do # Correct
for file in $(ls); do # 文件名含空格时会出错 for file in *; do # 正确写法

Don't pipe yes to commands

不要通过管道传递yes给命令

yes | risky-command # Bypasses important prompts
yes | risky-command # 绕过重要的确认提示

Don't ignore error codes

不要忽略错误码

make build # Did it work? make build || die "Build failed" # Better
undefined
make build # 无法判断是否成功 make build || die "构建失败" # 推荐写法
undefined

Complexity Warning Signs

复杂度警告信号

If your script has any of these, consider rewriting in Python/Go:
  • More than 100 lines
  • Complex data structures beyond simple arrays
  • Nested loops over arrays of arrays
  • Heavy string manipulation logic
  • Complex state management
  • Mathematical calculations beyond basic arithmetic
  • Need for unit testing individual functions
  • JSON/YAML parsing beyond simple jq queries
如果你的脚本出现以下任意情况,考虑改用Python/Go重写:
  • 代码量超过100行
  • 涉及数组之外的复杂数据结构
  • 数组的数组嵌套循环
  • 大量字符串处理逻辑
  • 复杂状态管理
  • 基础算术之外的数学计算
  • 需要对单个函数进行单元测试
  • 超出简单jq查询的JSON/YAML解析

Advanced: Dry-Run Pattern

进阶:空运行模式

For scripts that modify things:
bash
DRY_RUN="${DRY_RUN:-false}"

run() {
    if [[ "${DRY_RUN}" == "true" ]]; then
        echo "[DRY RUN] ${*}" >&2
        return 0
    fi
    "${@}"
}
适用于会修改数据的脚本:
bash
DRY_RUN="${DRY_RUN:-false}"

run() {
    if [[ "${DRY_RUN}" == "true" ]]; then
        echo "[空运行] ${*}" >&2
        return 0
    fi
    "${@}"
}

Usage

使用示例

run cp "${source}" "${dest}" run rm -f "${old_file}"
run cp "${source}" "${dest}" run rm -f "${old_file}"

Run script: DRY_RUN=true ./script.sh

运行脚本:DRY_RUN=true ./script.sh

undefined
undefined

Quick Reference Checklist

快速参考检查清单

Before considering a bash script complete:
  • ShellCheck passes with no warnings
  • Has proper shebang (
    #!/usr/bin/env bash
    )
  • Has strict mode (
    set -euo pipefail
    )
  • All variables quoted (
    "${var}"
    )
  • Required dependencies checked
  • Proper error messages to stderr
  • Cleanup trap if using temp files
  • Script is idempotent where possible
  • Under 100 lines (or has strong justification)
  • Uses
    command -v
    not
    which
  • Arrays used for lists with spaces
  • No
    eval
    ,
    ls
    parsing, or backticks
  • Functions have local variables
在认为Bash脚本编写完成前,检查以下项:
  • ShellCheck检测无警告
  • 包含正确的Shebang(
    #!/usr/bin/env bash
  • 启用严格模式(
    set -euo pipefail
  • 所有变量均加引号(
    "${var}"
    而非
    $var
  • 已检查必要依赖
  • 错误信息输出至stderr
  • 使用临时文件时添加清理trap
  • 脚本尽可能实现幂等
  • 代码量少于100行(或有充分理由超过)
  • 使用
    command -v
    而非
    which
  • 列表含空格时使用数组
  • 未使用
    eval
    、解析
    ls
    输出或反引号
  • 函数使用局部变量

Summary

总结

  1. Start simple: Don't over-engineer. Most scripts should be <50 lines.
  2. Use ShellCheck: It catches most problems automatically.
  3. Quote everything:
    "${var}"
    not
    $var
    .
  4. Fail fast:
    set -euo pipefail
    and validate inputs.
  5. Know when to stop: If it's getting complex, use a real language.
  6. Compose don't complicate: Use pipes and process substitution.
  7. Be idempotent: Scripts should be safe to run multiple times.
  8. Test error paths: Make sure your script fails safely.
Remember: Shell scripts are for gluing things together, not building complex logic. Keep them simple, safe, and focused.
  1. 从简开始:不要过度设计。大多数脚本应少于50行。
  2. 使用ShellCheck:它会自动检测大多数问题。
  3. 所有变量加引号:使用
    "${var}"
    而非
    $var
  4. 快速失败:启用
    set -euo pipefail
    并验证输入。
  5. 知止不殆:如果逻辑变得复杂,改用成熟语言。
  6. 组合而非复杂化:使用管道与进程替换。
  7. 保持幂等:脚本应可安全重复执行。
  8. 测试错误路径:确保脚本在失败时安全退出。
记住:Shell脚本用于粘合各类工具,而非构建复杂逻辑。保持脚本简洁、安全且聚焦单一功能。",