-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrecursive_extract.sh
More file actions
executable file
·407 lines (336 loc) · 11.2 KB
/
Copy pathrecursive_extract.sh
File metadata and controls
executable file
·407 lines (336 loc) · 11.2 KB
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
401
402
403
404
405
406
407
#!/bin/bash
# 递归解压脚本
# 用法: ./recursive_extract.sh [目标目录] [最大递归深度]
# 示例: ./recursive_extract.sh /path/to/archive 5
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 默认参数
TARGET_DIR="${1:-.}"
MAX_DEPTH="${2:-20}"
CURRENT_DEPTH=0
VERBOSE=false
# 统计变量
TOTAL_EXTRACTED=0
FAILED_COUNT=0
# 日志文件
LOG_FILE="extraction_$(date +%Y%m%d_%H%M%S).log"
# 原始输入文件(如果传入的是压缩文件,则保留它)
ORIGINAL_FILE=""
# 打印带颜色的消息
log_info() {
if [ "$VERBOSE" = true ]; then
echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE"
else
echo "[INFO] $1" >> "$LOG_FILE"
fi
}
log_success() {
if [ "$VERBOSE" = true ]; then
echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE"
else
echo "[SUCCESS] $1" >> "$LOG_FILE"
fi
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"
}
# 清理压缩文件(除非是原始传入的文件)
cleanup_archive() {
local file="$1"
if [ "$(realpath "$file")" != "$ORIGINAL_FILE" ]; then
rm -f "$file"
log_info "已删除: $file"
fi
}
# 智能检测压缩格式(分层判断:先判断外层压缩,再判断内层归档)
detect_archive_format() {
local file="$1"
local mime_type=$(file -b --mime-type "$file" 2>/dev/null)
# 判断外层压缩格式(使用标准 MIME 类型)
case "$mime_type" in
application/gzip|application/x-gzip)
echo "gz"
;;
application/x-xz)
echo "xz"
;;
application/x-bzip2)
echo "bz2"
;;
application/x-tar)
echo "tar"
;;
application/zip)
echo "zip"
;;
*)
echo "unknown"
;;
esac
}
# 统一的解压函数(根据检测到的格式自动解压)
extract_archive() {
local file="$1"
# 文件存在性检查
if [ ! -f "$file" ]; then
log_error "文件不存在: $file"
((FAILED_COUNT++))
return 1
fi
# 自动检测格式
local format=$(detect_archive_format "$file")
if [ "$format" = "unknown" ]; then
log_warning "无法识别的文件类型: $file"
log_info "文件信息: $(file -b "$file")"
return 0 # 不是压缩文件,跳过而不计入失败
fi
local dir=$(dirname "$file")
local filename=$(basename "$file")
log_info "解压 ${format^^}: $file"
local cmd=""
local success=false
case "$format" in
zip)
# ZIP 需要创建子目录
local base="${filename%.*}"
local target="$dir/$base"
if unzip -q -o "$file" -d "$target" 2>>"$LOG_FILE"; then
success=true
fi
;;
tar)
if tar -xf "$file" -C "$dir" 2>>"$LOG_FILE"; then
success=true
fi
;;
gz)
if gunzip -f "$file" 2>>"$LOG_FILE"; then
success=true
fi
;;
xz)
if unxz -f "$file" 2>>"$LOG_FILE"; then
success=true
fi
;;
bz2)
if bunzip2 -f "$file" 2>>"$LOG_FILE"; then
success=true
fi
;;
esac
if [ "$success" = true ]; then
log_success "完成: $file"
((TOTAL_EXTRACTED++))
# gunzip/unxz/bunzip2 会自动删除原文件,其他格式需手动清理
case "$format" in
gz|xz|bz2)
# 这些工具自动删除原文件,不需要调用 cleanup_archive
;;
*)
cleanup_archive "$file"
;;
esac
return 0
else
log_error "失败: $file"
((FAILED_COUNT++))
return 1
fi
}
# 递归解压函数
recursive_extract() {
local current_dir="$1"
local depth="$2"
if [ "$depth" -gt "$MAX_DEPTH" ]; then
log_warning "达到最大递归深度 $MAX_DEPTH,停止解压"
return
fi
# 添加空行,让不同目录的扫描更清晰(仅在日志文件中)
echo "" >> "$LOG_FILE"
log_info "扫描目录: $current_dir (深度: $depth)"
local has_new_files=true
local total_archives=0
# 循环扫描当前目录,直到没有新的压缩文件产生
# 这样可以处理 tar.gz 等多层嵌套(gz解压后产生tar)
while [ "$has_new_files" = true ]; do
has_new_files=false
local archives_found=0
# 查找所有文件并智能检测压缩格式
while IFS= read -r -d '' file; do
# 直接检测格式(基于 MIME 类型,不依赖扩展名)
local format=$(detect_archive_format "$file")
case "$format" in
tar|gz|xz|bz2|zip)
((archives_found++))
((total_archives++))
if extract_archive "$file"; then
# 默认需要重新扫描(解压可能产生新压缩文件)
# 只有 zip 例外(解压到子目录,不影响当前目录)
[ "$format" != "zip" ] && has_new_files=true
fi
;;
esac
done < <(find "$current_dir" -maxdepth 1 -type f -print0 2>/dev/null)
# 如果本轮没有找到压缩文件,跳出循环
[ "$archives_found" -eq 0 ] && break
done
# 记录本目录处理情况
if [ "$total_archives" -gt 0 ]; then
log_info "在 $current_dir 共处理 $total_archives 个压缩文件"
fi
# 总是递归处理子目录(无论当前目录是否有压缩文件)
while IFS= read -r -d '' subdir; do
recursive_extract "$subdir" $((depth + 1))
done < <(find "$current_dir" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
}
# 显示使用说明
show_usage() {
cat << EOF
递归解压脚本 - 自动解压多层嵌套的压缩文件
用法:
$0 [选项] [目标目录] [最大递归深度]
选项:
-v, --verbose 显示详细日志(INFO和SUCCESS级别)
-h, --help 显示此帮助信息
参数:
目标目录 要解压的目录路径(默认:当前目录)
最大递归深度 最大递归层数(默认:20)
支持格式:
- ZIP (.zip)
- TAR (.tar)
- GZ (.gz)
- XZ (.xz)
- BZ2 (.bz2)
注意:
- 使用 file 命令智能检测格式,不依赖扩展名
- 默认只显示WARNING和ERROR,详细日志写入文件
- 使用 -v 参数可查看所有日志信息
示例:
$0 # 解压当前目录
$0 -v # 解压当前目录(显示详细日志)
$0 /path/to/archives # 解压指定目录
$0 -v /path/to/archives 5 # 解压指定目录,最大深度5层,显示详细日志
EOF
}
# 主函数
main() {
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_usage
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
-*)
log_error "未知选项: $1"
show_usage
exit 1
;;
*)
# 第一个非选项参数是目标目录
if [ -z "${TARGET_DIR_SET:-}" ]; then
TARGET_DIR="$1"
TARGET_DIR_SET=true
# 第二个非选项参数是最大深度
elif [ -z "${MAX_DEPTH_SET:-}" ]; then
MAX_DEPTH="$1"
MAX_DEPTH_SET=true
else
log_error "参数过多: $1"
show_usage
exit 1
fi
shift
;;
esac
done
# 检查目标路径是否存在
if [ ! -e "$TARGET_DIR" ]; then
log_error "路径不存在: $TARGET_DIR"
exit 1
fi
# 如果传入的是压缩文件,先解压它
if [ -f "$TARGET_DIR" ]; then
log_info "检测到传入的是文件,判断是否为压缩文件"
# 保存原始文件的绝对路径,避免被删除
ORIGINAL_FILE="$(realpath "$TARGET_DIR")"
log_info "原始文件将被保留: $ORIGINAL_FILE"
# 使用智能检测
local format=$(detect_archive_format "$TARGET_DIR")
if [ "$format" = "unknown" ]; then
log_error "不支持的文件类型: $TARGET_DIR"
log_error "文件信息: $(file -b "$TARGET_DIR")"
log_error "支持: .zip, .tar.gz, .tgz, .tar, .gz, .tar.xz, .xz, .tar.bz2, .bz2"
exit 1
fi
log_info "检测到 ${format^^} 文件,将先解压"
local parent_dir=$(dirname "$TARGET_DIR")
extract_archive "$TARGET_DIR"
# ZIP 解压到子目录,其他格式解压到当前目录
if [ "$format" = "zip" ]; then
local base=$(basename "$TARGET_DIR")
base="${base%.*}"
TARGET_DIR="$parent_dir/$base"
else
TARGET_DIR="$parent_dir"
fi
if [ ! -d "$TARGET_DIR" ]; then
log_error "解压后未找到目录: $TARGET_DIR"
exit 1
fi
fi
# 检查必要的命令是否存在
if ! command -v unzip &> /dev/null; then
log_error "未找到 unzip 命令,请先安装"
exit 1
fi
if ! command -v tar &> /dev/null; then
log_error "未找到 tar 命令,请先安装"
exit 1
fi
log_info "=========================================="
log_info "递归解压开始"
log_info "目标目录: $TARGET_DIR"
log_info "最大递归深度: $MAX_DEPTH"
log_info "日志文件: $LOG_FILE"
log_info "=========================================="
# 记录开始时间
START_TIME=$(date +%s)
# 转换为绝对路径
TARGET_DIR=$(cd "$TARGET_DIR" && pwd)
# 开始递归解压
recursive_extract "$TARGET_DIR" 1
# 记录结束时间
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
# 统计最终结果
log_info "=========================================="
log_info "解压完成!"
log_info "成功解压: $TOTAL_EXTRACTED 个文件"
log_info "失败/跳过: $FAILED_COUNT 个文件"
log_info "耗时: ${DURATION} 秒"
log_info "=========================================="
# 显示最终统计
log_info "最终统计:"
log_info "总文件数: $(find "$TARGET_DIR" -type f | wc -l)"
log_info "总目录数: $(find "$TARGET_DIR" -type d | wc -l)"
log_info "总大小: $(du -sh "$TARGET_DIR" | cut -f1)"
if [ "$VERBOSE" = true ]; then
echo ""
fi
log_success "详细日志已保存到: $LOG_FILE"
}
# 运行主函数
main "$@"