blob: 1ff74a5fe2f058f5cc377974441ea5bb61df67dd (
plain) (
blame)
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
|
#!/bin/bash
#
# Shell 脚本调试流程示例
# 展示常见的 Bash 脚本错误和调试方法
#
# ============================================
# 调试设置
# ============================================
# 取消注释以启用调试模式
# set -x # 打印每个命令
# set -e # 错误时退出
# set -u # 未定义变量报错
# set -o pipefail # 管道中任何命令失败则失败
# 或组合使用
# set -xeuo pipefail # 严格模式
# ============================================
# 错误处理函数
# ============================================
# 错误退出函数
die() {
local message="$1"
local exit_code="${2:-1}"
echo "Error: $message" >&2
exit "$exit_code"
}
# 记录函数
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" >&2
}
log_info() { log "INFO" "$@"; }
log_warn() { log "WARN" "$@"; }
log_error() { log "ERROR" "$@"; }
# ============================================
# 问题 1:未引用的变量
# ============================================
# 错误示例:变量未引用
demo_unquoted_variable() {
echo "=== 问题 1:未引用的变量 ==="
local name="John Doe"
# 错误:变量未引用,空值会导致语法错误
# if [ $name = "John Doe" ]; then
# echo "Match"
# fi
# 正确:始终引用变量
if [ "$name" = "John Doe" ]; then
log_info "变量匹配: $name"
fi
}
# ============================================
# 问题 2:命令失败继续执行
# ============================================
demo_command_failure() {
echo "=== 问题 2:命令失败继续执行 ==="
# 错误:cd 失败后继续执行
# cd /nonexistent_directory
# rm -rf file.txt # 会删除当前目录的文件!
# 正确:检查命令是否成功
cd /tmp || die "无法切换到 /tmp 目录"
log_info "成功切换到目录: $(pwd)"
cd - > /dev/null || true
}
# ============================================
# 问题 3:循环中的变量作用域(管道问题)
# ============================================
demo_pipeline_scope() {
echo "=== 问题 3:管道中的变量作用域 ==="
local count=0
# 错误:管道创建子 shell,外部变量不改变
# echo -e "1\n2\n3" | while read line; do
# count=$((count + 1))
# done
# echo "Count: $count" # 输出 0
# 正确:使用重定向
while read line; do
count=$((count + 1))
done < <(echo -e "1\n2\n3")
log_info "计数结果: $count"
}
# ============================================
# 问题 4:数组操作
# ============================================
demo_array_operations() {
echo "=== 问题 4:数组操作 ==="
local fruits=("apple" "banana" "cherry")
# 错误的数组访问
# echo $fruits[1] # 输出 apple[1]
# 正确的数组访问
log_info "第一个元素: ${fruits[0]}"
log_info "第二个元素: ${fruits[1]}"
log_info "所有元素: ${fruits[@]}"
log_info "数组长度: ${#fruits[@]}"
# 遍历数组
for fruit in "${fruits[@]}"; do
log_info "水果: $fruit"
done
}
# ============================================
# 问题 5:字符串比较
# ============================================
demo_string_comparison() {
echo "=== 问题 5:字符串比较 ==="
local name="John"
# 错误:数字比较使用 =
# if [ $age = 18 ]; then
# 正确:字符串比较
if [[ "$name" == "John" ]]; then
log_info "字符串匹配"
fi
# 正确:数字比较
local age=18
if [ "$age" -eq 18 ]; then
log_info "数字匹配"
fi
}
# ============================================
# 问题 6:算术运算
# ============================================
demo_arithmetic() {
echo "=== 问题 6:算术运算 ==="
local a=10
local b=5
# 错误:使用 let 或 $(())
# result = a + b # 这是命令调用
# 正确的算术运算
local result=$((a + b))
log_info "加法: $a + $b = $result"
result=$((a - b))
log_info "减法: $a - $b = $result"
result=$((a * b))
log_info "乘法: $a * $b = $result"
result=$((a / b))
log_info "除法: $a / $b = $result"
# 使用 let
let result=a+b
log_info "let 加法: $result"
}
# ============================================
# 参数验证
# ============================================
validate_arguments() {
echo "=== 参数验证 ==="
# 检查参数数量
if [ $# -lt 2 ]; then
die "用法: $0 <文件> <目录>" 2
fi
local file="$1"
local dir="$2"
# 检查文件存在
if [ ! -f "$file" ]; then
die "文件不存在: $file" 3
fi
# 检查目录存在
if [ ! -d "$dir" ]; then
die "目录不存在: $dir" 4
fi
log_info "参数验证通过"
log_info "文件: $file"
log_info "目录: $dir"
}
# ============================================
# 使用 trap 进行清理
# ============================================
demo_trap() {
echo "=== 使用 trap 进行清理 ==="
# 设置清理函数
cleanup() {
log_info "执行清理操作..."
# 这里可以执行清理操作
}
# 捕获退出信号
trap cleanup EXIT
# 捕获错误信号
trap 'log_error "发生错误,行号: $LINENO"' ERR
# 捕获中断信号
trap 'log_warn "脚本被中断"; cleanup; exit 130' INT
log_info "执行一些操作..."
# 模拟操作
sleep 1
}
# ============================================
# 调试技巧示例
# ============================================
demo_debugging() {
echo "=== 调试技巧 ==="
# 1. 使用 echo 调试
local value="test"
echo "[DEBUG] value = $value" >&2
# 2. 使用 printf 格式化输出
printf "[DEBUG] Count: %d, Name: %s\n" 42 "John" >&2
# 3. 检查变量是否设置
if [ -z "${unset_var+x}" ]; then
log_warn "变量 unset_var 未设置"
fi
# 4. 显示调用栈
log_info "调用栈:"
local i=0
while caller $i; do
((i++))
done 2>/dev/null || true
}
# ============================================
# 文件操作错误处理
# ============================================
demo_file_operations() {
echo "=== 文件操作错误处理 ==="
local tmpfile=$(mktemp) || die "无法创建临时文件"
# 确保文件被删除
trap "rm -f '$tmpfile'" EXIT
# 写入文件
echo "Test content" > "$tmpfile" || die "无法写入文件: $tmpfile"
# 读取文件
local content
content=$(cat "$tmpfile") || die "无法读取文件: $tmpfile"
log_info "文件内容: $content"
# trap 会自动清理
}
# ============================================
# 带重试的操作
# ============================================
demo_retry() {
echo "=== 带重试的操作 ==="
local max_attempts=3
local attempt=1
while [ $attempt -le $max_attempts ]; do
log_info "尝试 $attempt/$max_attempts..."
# 模拟可能失败的操作
if [ $attempt -eq 2 ]; then
log_info "成功!"
return 0
fi
log_warn "失败,重试..."
((attempt++))
sleep 1
done
log_error "所有尝试都失败了"
return 1
}
# ============================================
# 检查依赖
# ============================================
check_dependencies() {
echo "=== 检查依赖 ==="
local required_commands=("curl" "jq" "git")
for cmd in "${required_commands[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
log_error "缺少依赖: $cmd"
return 1
fi
log_info "✓ $cmd 可用"
done
log_info "所有依赖都已满足"
}
# ============================================
# 主函数
# ============================================
main() {
echo "============================================"
echo "Shell 脚本调试示例"
echo "============================================"
# 运行各个示例
demo_unquoted_variable
echo ""
demo_command_failure
echo ""
demo_pipeline_scope
echo ""
demo_array_operations
echo ""
demo_string_comparison
echo ""
demo_arithmetic
echo ""
demo_trap
echo ""
demo_debugging
echo ""
demo_file_operations
echo ""
demo_retry
echo ""
check_dependencies
echo ""
log_info "所有示例执行完成"
}
# 运行主函数
main "$@"
|