ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AutoHotkey 脚本批量解压:ZIP 自动化完整实现

AutoHotkey 脚本批量解压:ZIP 自动化完整实现

AutoHotkey 脚本批量解压:ZIP 自动化完整实现

【免费下载链接】AutoHotkeyAutoHotkey - macro-creation and automation-oriented scripting utility for Windows.项目地址: https://gitcode.com/gh_mirrors/au/AutoHotkey

AutoHotkey 是 Windows 上的自动化脚本语言,本文用它写一个 ZIP 自动解压脚本:扫描目录里的压缩包,逐个解压到独立子目录,并输出带时间戳的日志。

月底那批压缩包,不想再点二十次右键

月末财务发来 23 个 ZIP:每月一包的报表,散在D:\Inbox里,要分别解压到各自目录才能归档。手动操作就是"选中—解压到此处—确认"重复 23 遍,全程没有思考量,但耗时和出错点都在这。

这个任务的特点正好适合脚本:输入是一个目录通配符,规则固定,结果可验证(每个包对应一个目录)。缺的只是把这三步串起来的东西。

为什么用 AutoHotkey 而不是现成工具

  • 不用装额外的 GUI 批处理软件,官方安装包约 3 MB,脚本即文档
  • 文件遍历、建目录、日志都是内置命令,解压只需调用一行命令行
  • 后续要改成定时监控、按日期命名,都是在同一文件里加几行

对比 7-Zip 自带的7zG批处理:AHK 的优势在于日志、错误分支、界面交互都写在一个脚本里,维护成本更低。

环境准备:AutoHotkey v1.1.x 加 7-Zip

  • AutoHotkey v1.1.33 及以上,本脚本按 v1.x 语法编写
  • 7-Zip,解压走它的命令行版7z.exe(默认安装在C:\Program Files\7-Zip\7z.exe),命令行同步等待比 GUI 弹窗稳定得多

脚本顶部的配置区一共 5 个变量:

InboxDir := "D:\Inbox" OutRoot := "D:\Inbox\Unpacked" SevenZip := "C:\Program Files\7-Zip\7z.exe" LogFile := "D:\Inbox\unzip.log" SkipHidden := True

5 行代码扫描目录里的全部 ZIP

Loop, Files负责列出目标目录中所有匹配通配符的文件,循环体内通过内置变量拿到每个文件的路径:

Loop, Files, %InboxDir%\*.zip, F { ZipPath := A_LoopFileFullPath ; 当前 ZIP 的完整路径 FileGetBaseName, Name, %ZipPath% ; 不含扩展名的文件名 ... }

末尾的F标志表示只匹配文件、排除目录,对应 source/script2.cpp 中FileIsFilteredOut的过滤逻辑:目录属性命中时按循环模式直接排除。

整个文件循环由 source/script.cpp 里的Line::PerformLoopFilePattern实现,它先用ParseLoopFilePattern把D:\Inbox\*.zip拆成"目录 + 通配符"两部分,再基于FindFirstFile/FindNextFile逐个取结果。源码注释里写明了这个结构约 128 KB,所以放在堆上而不是栈上——这也是它支持长路径的原因。

3 条命令为每个 ZIP 建独立目录

每个包解压到一个以包名命名的子目录,避免同目录下的文件互相覆盖:

DestDir := OutRoot "\ " Name if !FileExist(DestDir) { FileCreateDir, %DestDir% if ErrorLevel { Log("Error" " 建目录失败: " DestDir " (Err " A_LastError ")") Continue } }

FileCreateDir背后是 source/lib/file.cpp 的同名函数:先用GetFullPathName把路径规范化(顺带支持正斜杠和..这类相对组件),再交给FileCreateDirRecursive逐级创建父目录。目录已存在不算错误,函数会返回成功——所以脚本里先FileExist判一下,只为跳过创建动作。

命令行调用 7z.exe,用返回码代替窗口判断

不用资源管理器右键菜单,直接走 7-Zip 命令行。7z x的标准返回码:0 成功、1 警告(如文件名重复)、大于 1 才是真失败:

RunWait, "%SevenZip%" x -y -o"%DestDir%" "%ZipPath%", , Hide Code := ErrorLevel

两个细节:-o后面必须紧跟目标路径、中间不能有空格,否则 7z 会把空格当参数分隔;-y表示自动确认覆盖,Hide让命令行窗口全程不弹出。

RunWait会阻塞到进程结束并把它返回码写进ErrorLevel,比WinWait盯窗口标题可靠——后者在中文系统里窗口名还依赖系统语言包。

日志:让 23 个文件的结果可追溯

FileAppend按行追加,失败的文件不需要回头翻 7-Zip 的控制台:

Log(Status, Detail = "") { FormatTime, Ts, , yyyy-MM-dd HH:mm:ss FileAppend, %Ts% [%Status%] %Detail%`n, %LogFile% }

主流程按返回码分三支记录:

if Code = 0 Log("OK" " " Name ".zip 返回码 0") else if Code = 1 Log("Warn" " " Name ".zip 返回码 1(可能有重名文件)") else Log("Error" " " Name ".zip 返回码 " Code)

文件读写底层在 source/TextIO.cpp 的TextFile::_Open,FileAppend对应追加模式打开。

完整可运行脚本

#NoEnv #SingleInstance Force SetWorkingDir, %A_ScriptDir% ; ---- 配置区:只需改这 5 行 ---- InboxDir := "D:\Inbox" OutRoot := "D:\Inbox\Unpacked" SevenZip := "C:\Program Files\7-Zip\7z.exe" LogFile := "D:\Inbox\unzip.log" SkipHidden := True ; ---- 扫描 ---- Log("Info" " 开始扫描 " InboxDir) Loop, Files, %InboxDir%\*.zip, F { ZipPath := A_LoopFileFullPath if (SkipHidden and (A_LoopFileAttr & 2)) Continue ; 跳过隐藏文件 FileGetBaseName, Name, %ZipPath% DestDir := OutRoot "\ " Name ; ---- 建独立目录 ---- if !FileExist(DestDir) { FileCreateDir, %DestDir% if ErrorLevel { Log("Error" " 建目录失败: " DestDir " (Err " A_LastError ")") Continue } } ; ---- 调用 7z 解压,同步等待 ---- RunWait, "%SevenZip%" x -y -o"%DestDir%" "%ZipPath%", , Hide Code := ErrorLevel ; ---- 按返回码记录 ---- if Code = 0 Log("OK" " " Name ".zip 返回码 0") else if Code = 1 Log("Warn" " " Name ".zip 返回码 1(可能有重名文件)") else Log("Error" " " Name ".zip 返回码 " Code) } Log("Info" " 完成") MsgBox, 批量解压结束,详见 %LogFile% ExitApp ; ---- 带时间戳的日志 ---- Log(Status, Detail = "") { FormatTime, Ts, , yyyy-MM-dd HH:mm:ss FileAppend, %Ts% [%Status%] %Detail%`n, %LogFile% }

保存为UnzipBatch.ahk双击运行即可。v2.x 语法不同(命令改成带括号的调用),如果装的是 v2,把配置区改成:=变量赋值后再逐条核对命令形式。

实际运行效果

跑完 3 个测试包,日志长这样:

2026-11-02 10:01:14 [Info] 开始扫描 D:\Inbox 2026-11-02 10:01:14 [OK] 10月流水.zip 返回码 0 2026-11-02 10:01:15 [Warn] 对账单(汇总).zip 返回码 1(可能有重名文件) 2026-11-02 10:01:15 [OK] 报销单.zip 返回码 0 2026-11-02 10:01:15 [Info] 完成

D:\Inbox\Unpacked下的目录结构:

Unpacked\ ├─ 10月流水\ │ ├─ 流水.xlsx │ └─ 明细.txt ├─ 对账单(汇总)\ │ └─ 对账单.xlsx └─ 报销单\ └─ 报销单.pdf

三个容易踩的坑 🛠

-o参数后不能有空格。-o "%DestDir%"会被 7z 当成参数分隔,返回码非 0 但很难看懂报错。正确写法是-o"%DestDir%",路径里带空格才需要引号。

RunWait表达式中的引号别省。%SevenZip%的值是C:\Program Files\7-Zip\7z.exe,中间有空格;AHK 会把RunWait后面的整个表达式当作一条命令行交给ShellExecute,不加引号会在空格处断开,表现为"程序未找到"。

权限问题脚本自己解决不了。脚本以普通用户权限运行时,访问受保护目录(如ProgramData下的共享文件夹)会失败,FileCreateDir返回错误码 5。AHK 不会像 Windows 那样自动弹 UAC,脚本里对ErrorLevel的判断已经能兜住这种情况——记录日志后跳过,而不是卡死或弹框。

脚本文件的编码。旧版 AHK 的 ANSI 构建只按 ANSI 编码读.ahk,脚本里的中文日志会变成乱码。两个办法:文件另存为 ANSI,或者改用 Unicode 构建。

收尾

整件事被拆成四段:Loop, Files扫描、FileCreateDir建目录、RunWait调 7z 同步解压、FileAppend写日志,每段都有明确的输入输出,失败也只影响当前这一个文件。

留一个延伸问题:如果目录里混着 ZIP 和 7Z 两种格式,Loop, Files的通配符要写两次,还是改成按A_LoopFileExt动态选解压参数更省事?

【免费下载链接】AutoHotkeyAutoHotkey - macro-creation and automation-oriented scripting utility for Windows.项目地址: https://gitcode.com/gh_mirrors/au/AutoHotkey

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进