shell-scripting
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBash 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 bashWhy: Portable across systems where bash may not be at (e.g., macOS, BSD, NixOS).
/bin/bashAlternative: if you know the script only runs on Linux and prefer explicit paths.
#!/bin/bashbash
#!/usr/bin/env bash原因:在bash可能不在路径的系统(如macOS、BSD、NixOS)上保证可移植性。
/bin/bash替代方案:如果确定脚本仅在Linux运行且偏好明确路径,可使用。
#!/bin/bash2. Strict Mode
2. 严格模式
bash
set -euo pipefailWhat each flag does:
- : Exit immediately if any command fails (non-zero exit)
-e - : Treat unset variables as errors
-u - : Pipe fails if ANY command in pipeline fails (not just the last)
-o pipefail
When to add : Only for debugging, not in production scripts (makes output noisy).
-xbash
set -euo pipefail各标志的作用:
- :若任何命令执行失败(返回非零值)则立即退出
-e - :将未定义变量视为错误
-u - :流水线中任意命令失败则整个流水线失败(而非仅最后一个命令)
-o pipefail
何时添加:仅用于调试,生产脚本中不要使用(会导致输出冗余)。
-x3. ShellCheck Compliance
3. 符合ShellCheck规范
Run ShellCheck on EVERY script before committing:
bash
shellcheck script.shFix 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 pipefailbash
#!/usr/bin/env bash
set -euo pipefailBrief 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
你的代码写在这里
undefinedundefinedCore Safety Patterns
核心安全模式
Always Quote Variables
始终为变量添加引号
Why: Prevents word splitting and globbing disasters.
bash
undefined原因:防止单词拆分与通配符匹配灾难。
bash
undefinedWrong - 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
undefinedecho "${var}" # 推荐写法
echo "$var" # 可接受但一致性较差
echo $var # 错误写法 - 未加引号
undefinedCheck Required Variables
检查必填变量
bash
undefinedbash
undefinedFail 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中设置}"
undefinedValidate Inputs
验证输入
bash
undefinedbash
undefinedCheck 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}"
undefinedEssential 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 pipefailDescription: 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'
undefinedgrep ERROR "${logfile}" | jq -r '.message'
undefinedPattern 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 pipefailCreate 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}"
undefinedecho "工作目录:${tmpdir}"
undefinedPattern 3: Safe Function Definition
模式3:安全函数定义
Functions should be simple and focused:
bash
undefined函数应简洁且聚焦单一功能:
bash
undefinedGood: 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:
```bashprocess_file() {
local file="${1}"
local output="${2}"
[[ -f "${file}" ]] || die "输入文件缺失:${file}"
# 执行处理操作
sed 's/foo/bar/g' "${file}" > "${output}"}
**重要提示**:单独声明并设置命令替换的变量,以便捕获错误:
```bashWrong - hides errors
错误示例 - 隐藏错误
local result="$(failing_command)"
local result="$(failing_command)"
Correct - catches errors
正确示例 - 可捕获错误
local result
result="$(failing_command)" # Will fail properly with set -e
undefinedlocal result
result="$(failing_command)" # 开启set -e后会正确终止
undefinedPattern 4: Safe Array Handling
模式4:安全数组处理
Arrays are useful for handling lists with spaces:
bash
undefined数组适用于处理包含空格的列表:
bash
undefinedCreate 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}")
undefinedmapfile -t lines < <(grep pattern "${file}")
undefinedPattern 5: Conditional Testing
模式5:条件测试
Use for bash (safer and more features):
[[ ]]bash
undefined在Bash中使用更安全且功能更丰富:
[[ ]]bash
undefinedFile 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}"
undefinedPattern 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 pipefailFor 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}"
对于需要标志的脚本,保持逻辑简洁:
```bashUse 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
undefinedundefinedPattern 7: Process Substitution Over Temp Files
模式7:使用进程替换替代临时文件
Avoid creating temporary files when possible:
bash
undefined尽可能避免创建临时文件:
bash
undefinedInstead 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)
undefineddiff <(sort file1.txt) <(sort file2.txt)
undefinedPattern 8: Prefer Builtins Over External Commands
模式8:优先使用内置命令而非外部命令
Builtins are faster and more reliable:
bash
undefined内置命令更快且更可靠:
bash
undefinedUse 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)
undefinedlength="${#string}" # 而非:length=$(echo "${string}" | wc -c)
undefinedIntermediate 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"
undefinedlog "开始处理"
error "连接数据库失败"
undefinedPattern 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 "${@}"
undefinedmain "${@}"
undefinedPattern 11: Idempotent Operations
模式11:幂等操作
Scripts should be safe to run multiple times:
bash
undefined脚本应可安全重复执行:
bash
undefinedCheck 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
undefinedmv "${source}" "${dest}" # 同一文件系统上的操作是原子性的
undefinedPattern 12: Safe While Loop Reading
模式12:安全的While循环读取
Don't pipe to while (creates subshell):
bash
undefined不要通过管道传递给while(会创建子shell):
bash
undefinedWrong - 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[@]}"
undefinedmapfile -t lines <file.txt
count="${#lines[@]}"
undefinedStyle 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
undefinedLong command - break at logical points
长命令 - 在逻辑断点处换行
docker run
--name my-container
--volume "${PWD}:/data"
--env "FOO=bar"
my-image:latest
--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
undefinedcat <<EOF
这是一条长消息
跨越多个行
这样编写可读性更强。
EOF
undefinedNaming Conventions
命名规范
bash
undefinedbash
undefinedFunctions: 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
undefinedundefinedFile Extensions
文件扩展名
- Executables: extension OR no extension (prefer no extension for user-facing commands)
.sh - Libraries: Always extension and NOT executable
.sh
- 可执行文件:使用扩展名或无扩展名(面向用户的命令优先选择无扩展名)
.sh - 库文件:必须使用扩展名且不可执行
.sh
Function Documentation
函数文档
Document functions that aren't obvious:
bash
undefined对功能不直观的函数添加文档:
bash
undefinedGood: 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
}
undefinedprocess_logs() {
local logfile="${1}"
local output_dir="${2}"
# 实现代码
}
undefinedWhat to Avoid
避免事项
Don't Use These
禁止使用的写法
bash
undefinedbash
undefinedDon't use backticks - use $()
不要使用反引号 - 使用$()
output= # Old style
output=$(command) # Correct
commandoutput= # 旧写法
output=$(command) # 正确写法
commandDon'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
undefinedfunction foo() { ... } # 冗余写法
foo() { ... } # 简洁写法
undefinedAnti-Patterns
反模式
bash
undefinedbash
undefinedDon'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
undefinedmake build # 无法判断是否成功
make build || die "构建失败" # 推荐写法
undefinedComplexity 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
undefinedundefinedQuick 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 not
command -vwhich - Arrays used for lists with spaces
- No ,
evalparsing, or backticksls - Functions have local variables
在认为Bash脚本编写完成前,检查以下项:
- ShellCheck检测无警告
- 包含正确的Shebang()
#!/usr/bin/env bash - 启用严格模式()
set -euo pipefail - 所有变量均加引号(而非
"${var}")$var - 已检查必要依赖
- 错误信息输出至stderr
- 使用临时文件时添加清理trap
- 脚本尽可能实现幂等
- 代码量少于100行(或有充分理由超过)
- 使用而非
command -vwhich - 列表含空格时使用数组
- 未使用、解析
eval输出或反引号ls - 函数使用局部变量
Summary
总结
- Start simple: Don't over-engineer. Most scripts should be <50 lines.
- Use ShellCheck: It catches most problems automatically.
- Quote everything: not
"${var}".$var - Fail fast: and validate inputs.
set -euo pipefail - Know when to stop: If it's getting complex, use a real language.
- Compose don't complicate: Use pipes and process substitution.
- Be idempotent: Scripts should be safe to run multiple times.
- 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.
- 从简开始:不要过度设计。大多数脚本应少于50行。
- 使用ShellCheck:它会自动检测大多数问题。
- 所有变量加引号:使用而非
"${var}"。$var - 快速失败:启用并验证输入。
set -euo pipefail - 知止不殆:如果逻辑变得复杂,改用成熟语言。
- 组合而非复杂化:使用管道与进程替换。
- 保持幂等:脚本应可安全重复执行。
- 测试错误路径:确保脚本在失败时安全退出。
记住:Shell脚本用于粘合各类工具,而非构建复杂逻辑。保持脚本简洁、安全且聚焦单一功能。",