名称: shell-error-handling 用户可调用: false 描述: 用于在实现Shell脚本中的错误处理、清理例程或调试时使用。涵盖陷阱、退出代码和健壮的错误模式。 允许的工具:
- 读取
- 写入
- 编辑
- Bash
- Grep
- Glob
Shell错误处理
在Shell脚本中健壮的错误处理、清理和调试的模式。
退出代码
标准退出代码
| 代码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 一般错误 |
| 2 | 误用Shell命令 |
| 126 | 命令不可执行 |
| 127 | 命令未找到 |
| 128+N | 致命信号N |
| 130 | Ctrl+C (SIGINT) |
检查退出状态
# 检查最后一个命令的退出状态
if ! command; then
echo "Command failed with status $?" >&2
exit 1
fi
# 替代模式
command || {
echo "Command failed" >&2
exit 1
}
# 捕获退出状态
command
status=$?
if (( status != 0 )); then
echo "Failed with status $status" >&2
fi
陷阱用于清理
基本清理模式
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
local exit_code=$?
# 移除临时文件
rm -f "$TEMP_FILE" 2>/dev/null || true
exit "$exit_code"
}
trap cleanup EXIT
TEMP_FILE=$(mktemp)
# 脚本继续...
# cleanup在退出时自动运行
处理多个信号
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
echo "Cleaning up..." >&2
rm -rf "$WORK_DIR" 2>/dev/null || true
}
handle_interrupt() {
echo "Interrupted by user" >&2
cleanup
exit 130
}
trap cleanup EXIT
trap handle_interrupt INT TERM
WORK_DIR=$(mktemp -d)
陷阱最佳实践
# 在清理中保留原始退出代码
cleanup() {
local exit_code=$?
# 清理操作在这里
rm -f "$temp_file" 2>/dev/null || true
# 恢复退出代码
exit "$exit_code"
}
# 使用 || true 用于可选的清理
trap 'rm -f "$temp_file" 2>/dev/null || true' EXIT
错误报告
标准错误输出
# 总是将错误写入stderr
echo "Error: Something went wrong" >&2
# 错误函数
error() {
echo "Error: $*" >&2
}
# Die函数 - 错误并退出
die() {
echo "Fatal: $*" >&2
exit 1
}
# 使用
[[ -f "$config" ]] || die "Config file not found: $config"
详细日志记录
#!/usr/bin/env bash
set -euo pipefail
VERBOSE="${VERBOSE:-false}"
log() {
if [[ "$VERBOSE" == "true" ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
fi
}
error() {
echo "[ERROR] $*" >&2
}
log "Starting script"
log "Processing file: $file"
防御性编程
检查先决条件
# 检查必需命令是否存在
require_command() {
command -v "$1" >/dev/null 2>&1 || {
echo "Error: Required command '$1' not found" >&2
exit 1
}
}
require_command jq
require_command curl
require_command shellcheck
验证输入
# 验证参数
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <source> <destination>" >&2
exit 1
fi
source_file="$1"
dest_dir="$2"
# 验证文件存在
[[ -f "$source_file" ]] || {
echo "Error: Source file not found: $source_file" >&2
exit 1
}
# 验证目录
[[ -d "$dest_dir" ]] || {
echo "Error: Destination directory not found: $dest_dir" >&2
exit 1
}
安全临时文件
# 创建安全临时文件
TEMP_FILE=$(mktemp) || {
echo "Error: Failed to create temp file" >&2
exit 1
}
# 创建安全临时目录
TEMP_DIR=$(mktemp -d) || {
echo "Error: Failed to create temp directory" >&2
exit 1
}
# 总是清理
trap 'rm -rf "$TEMP_FILE" "$TEMP_DIR" 2>/dev/null || true' EXIT
调试
调试模式
#!/usr/bin/env bash
# 通过环境变量启用调试模式
if [[ "${DEBUG:-}" == "1" ]]; then
set -x
fi
set -euo pipefail
# 或通过标志切换
while getopts "d" opt; do
case $opt in
d) set -x ;;
*) echo "Usage: $0 [-d]" >&2; exit 1 ;;
esac
done
跟踪执行
# 为特定部分启用跟踪
set -x
problematic_code
set +x
# 使用自定义PS4跟踪
export PS4='+ ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
错误恢复模式
重试模式
retry() {
local max_attempts="${1:-3}"
local delay="${2:-1}"
shift 2
local cmd=("$@")
local attempt=1
while (( attempt <= max_attempts )); do
if "${cmd[@]}"; then
return 0
fi
echo "Attempt $attempt failed, retrying in ${delay}s..." >&2
sleep "$delay"
(( attempt++ ))
done
echo "All $max_attempts attempts failed" >&2
return 1
}
# 使用
retry 3 5 curl -f "http://example.com/api"
回退模式
# 尝试主要方法,回退到次要方法
get_config() {
if [[ -f "$HOME/.config/myapp/config" ]]; then
cat "$HOME/.config/myapp/config"
elif [[ -f "/etc/myapp/config" ]]; then
cat "/etc/myapp/config"
else
echo "Error: No config file found" >&2
return 1
fi
}