AHK V2自己做的正则表达式工具(源代码)

板块:网站服务 | 回复:0 | 浏览:202 | 发布时间:2025-11-30 17:09:54

yuanscn 2025-11-30 17:09:54 楼主
#Requires AutoHotkey v2.0
#SingleInstance Force

; 全局变量
CurrentDir := A_WorkingDir
BackupFiles := [] ; 存储备份文件信息的数组

; 创建GUI窗口
MyGui := Gui()
MyGui.Title := "正则表达式批量替换工具-yzy"
MyGui.SetFont("s10", "Arial")

; 目录选择区域
MyGui.Add("Text", "w400 Section", "当前目录:")
DirEdit := MyGui.Add("Edit", "xs w350 vCurrentDir", CurrentDir)
BrowseBtn := MyGui.Add("Button", "x+5 yp w40", "浏览")
MyGui.Add("Text", "xs w400", "正则表达式(如果直接输入文本则为普通文本查找替换):")
RegExEdit := MyGui.Add("Edit", "xs w400 r2 vRegExPattern")
MyGui.Add("Text", "xs w400", "替换为:")
ReplaceEdit := MyGui.Add("Edit", "xs w400 r2 vReplaceText")

; 文件名筛选区域 - 改为垂直布局,各占一行
MyGui.Add("Text", "xs w400", "文件筛选条件:")

; 主文件名特征(单独一行)
MyGui.Add("Text", "xs w400", "主文件名特征 (支持*通配符,如: title*):")
FileNamePatternEdit := MyGui.Add("Edit", "xs w400 vFileNamePattern")

; 文件扩展名(单独一行)
MyGui.Add("Text", "xs w400", "文件扩展名 (多个用逗号分隔,如: txt,log,ini):")
ExtEdit := MyGui.Add("Edit", "xs w400 vFileExtensions", "txt")

; 按钮区域
BtnSection := MyGui.Add("Text", "xs w400 Section")
ProcessBtn := MyGui.Add("Button", "xs w100 h30", "开始处理")
UndoBtn := MyGui.Add("Button", "x+5 yp w100 h30", "撤消操作")
HelpBtn := MyGui.Add("Button", "x+5 yp w100 h30", "帮助")
ExitBtn := MyGui.Add("Button", "x+5 yp w100 h30", "退出程序")

; 结果显示区域
ResultText := MyGui.Add("Edit", "xs w400 r10 ReadOnly vResultDisplay")

; 显示窗口
MyGui.Show()

; 按钮事件
BrowseBtn.OnEvent("Click", BrowseDirectory)
ProcessBtn.OnEvent("Click", ProcessFiles)
UndoBtn.OnEvent("Click", UndoOperation)
HelpBtn.OnEvent("Click", ShowHelp)
ExitBtn.OnEvent("Click", (*) => ExitApp())

BrowseDirectory(*) {
global CurrentDir
; 选择文件夹对话框
SelectedDir := DirSelect("*" CurrentDir, 3, "选择要处理的目录")
if (SelectedDir != "") {
CurrentDir := SelectedDir
DirEdit.Value := CurrentDir
}
}

ProcessFiles(*) {
global CurrentDir, BackupFiles
; 清空备份文件记录
BackupFiles := []

; 获取用户输入
RegExPattern := RegExEdit.Value
ReplaceText := ReplaceEdit.Value
FileNamePattern := FileNamePatternEdit.Value
FileExts := ExtEdit.Value
ResultText.Value := "" ; 清空结果显示

; 验证输入
if (RegExPattern = "") {
MsgBox "请输入正则表达式!", "错误", 0x10
return
}

if (FileExts = "") {
MsgBox "请输入文件扩展名!", "错误", 0x10
return
}

if (!DirExist(CurrentDir)) {
MsgBox "目录不存在:" CurrentDir, "错误", 0x10
return
}

; 保存当前工作目录,处理完成后恢复
OriginalWorkingDir := A_WorkingDir
try {
SetWorkingDir(CurrentDir)
} catch as e {
MsgBox "无法切换到目录:" CurrentDir "`n错误:" e.Message, "错误", 0x10
return
}

; 统计变量
totalFiles := 0
totalReplacements := 0
processedFiles := 0

; 分割文件扩展名并转换为小写
extArray := StrSplit(FileExts, ",")
; 去除空格并转换为小写
loop extArray.Length {
extArray[A_Index] := Trim(extArray[A_Index])
extArray[A_Index] := LTrim(extArray[A_Index], ".")
extArray[A_Index] := StrLower(extArray[A_Index])
}

; 首先统计匹配的文件数量
loop files, "*.*", "F" {
fileName := A_LoopFileName
fileExt := GetFileExtension(fileName)
fileExt := StrLower(fileExt)

; 检查文件扩展名和主文件名特征
if (HasValue(extArray, fileExt) && MatchFileNamePattern(fileName, FileNamePattern)) {
totalFiles++
}
}

if (totalFiles = 0) {
ResultText.Value := "在目录 " CurrentDir " 中未找到匹配的文件!`n"
. "主文件名特征: " (FileNamePattern != "" ? FileNamePattern : "*") "`n"
. "文件扩展名: " FileExts
SetWorkingDir(OriginalWorkingDir) ; 恢复原始目录
return
}

; 处理文件
loop files, "*.*", "F" {
fileName := A_LoopFileName
fileExt := GetFileExtension(fileName)
fileExt := StrLower(fileExt)

; 检查文件扩展名和主文件名特征
if (HasValue(extArray, fileExt) && MatchFileNamePattern(fileName, FileNamePattern)) {
processedFiles++
fileReplacements := ProcessSingleFile(A_LoopFileFullPath, RegExPattern, ReplaceText)
totalReplacements += fileReplacements

; 记录备份文件信息
if (fileReplacements > 0) {
BackupFiles.Push(A_LoopFileFullPath)
}

; 更新进度显示
progress := Round((processedFiles / totalFiles) * 100)
ResultText.Value := "正在处理文件: " A_LoopFileName "`n"
. "进度: " processedFiles "/" totalFiles " (" progress "%)`n"
. "已替换: " totalReplacements " 处`n"
. "目录: " CurrentDir "`n`n"
. ResultText.Value

; 强制刷新显示
Sleep 10
}
}

; 显示最终结果
undoInfo := BackupFiles.Length > 0 ? "(有 " BackupFiles.Length " 个文件可撤消)" : "(无可撤消的文件)"
ResultText.Value := "处理完成!" undoInfo "`n"
. "处理目录: " CurrentDir "`n"
. "主文件名特征: " (FileNamePattern != "" ? FileNamePattern : "*") "`n"
. "文件扩展名: " FileExts "`n"
. "共处理文件: " totalFiles " 个`n"
. "总替换次数: " totalReplacements " 次`n`n"
. ResultText.Value

; 恢复原始工作目录
SetWorkingDir(OriginalWorkingDir)
}

; 新增函数:检查文件名是否匹配模式
MatchFileNamePattern(fileName, pattern) {
; 如果模式为空,则匹配所有文件
if (pattern = "") {
return true
}

; 获取主文件名(不含扩展名)
dotPos := InStr(fileName, ".", , -1)
if (dotPos > 0) {
baseName := SubStr(fileName, 1, dotPos - 1)
} else {
baseName := fileName ; 如果没有扩展名,则整个文件名作为主文件名
}

; 将通配符模式转换为正则表达式
regexPattern := ConvertWildcardToRegex(pattern)

; 使用正则表达式进行匹配(不区分大小写)
return RegExMatch(baseName, "i)^" regexPattern "$")
}

; 新增函数:将通配符模式转换为正则表达式
ConvertWildcardToRegex(pattern) {
; 转义正则表达式中的特殊字符
pattern := RegExReplace(pattern, "[.+?^${}()|[\]\\]", "\$0")

; 将通配符 * 转换为 .*
pattern := StrReplace(pattern, "*", ".*")

return pattern
}

ProcessSingleFile(filePath, pattern, replacement) {
replacements := 0

try {
; 读取文件内容
fileContent := FileRead(filePath)
originalContent := fileContent

; 使用正则表达式替换
if (pattern != "") {
fileContent := RegExReplace(fileContent, pattern, replacement, &count)
replacements := count
}

; 如果内容有变化,则写回文件
if (fileContent != originalContent && replacements > 0) {
; 创建备份文件
backupPath := filePath ".bak"
if !FileExist(backupPath) {
FileCopy(filePath, backupPath)
}

; 写入新内容
try {
fileObj := FileOpen(filePath, "w", "UTF-8")
fileObj.Write(fileContent)
fileObj.Close()
} catch as e {
MsgBox "写入文件失败: " filePath "`n错误: " e.Message, "错误", 0x10
return 0
}
}

return replacements

} catch as e {
MsgBox "处理文件失败: " filePath "`n错误: " e.Message, "错误", 0x10
return 0
}
}

; 撤消操作函数
UndoOperation(*) {
global BackupFiles, CurrentDir

if (BackupFiles.Length = 0) {
MsgBox "没有可撤消的操作!", "提示", 0x40
return
}

; 确认对话框
result := MsgBox("确定要撤消最近的操作吗?`n这将恢复 " BackupFiles.Length " 个文件的原始内容。", "确认撤消", 0x34)
if (result != "Yes") {
return
}

restoredCount := 0
OriginalWorkingDir := A_WorkingDir

try {
SetWorkingDir(CurrentDir)
} catch as e {
MsgBox "无法切换到目录:" CurrentDir "`n错误:" e.Message, "错误", 0x10
return
}

; 恢复备份文件
for filePath in BackupFiles {
backupPath := filePath ".bak"

if (FileExist(backupPath)) {
try {
; 用备份文件覆盖原文件
FileCopy(backupPath, filePath, 1)
; 删除备份文件
FileDelete(backupPath)
restoredCount++
} catch as e {
MsgBox "恢复文件失败: " filePath "`n错误: " e.Message, "错误", 0x10
}
}
}

; 清空备份文件记录
BackupFiles := []

; 显示结果
ResultText.Value := "撤消操作完成!`n"
. "成功恢复: " restoredCount " 个文件`n"
. "目录: " CurrentDir "`n`n"
. ResultText.Value

SetWorkingDir(OriginalWorkingDir)
}

; 显示帮助信息
ShowHelp(*) {
; 创建帮助窗口
HelpGui := Gui("+Resize +MinSize600x400", "正则表达式语法帮助")
HelpGui.SetFont("s10", "Consolas")

; 创建选项卡控件
TabCtrl := HelpGui.Add("Tab3", "x10 y10 w580 h380", ["正则表达式语法", "常用示例", "使用说明", "文件名匹配"])

; 第一个选项卡:正则表达式语法
TabCtrl.UseTab(1)
HelpText1 :=
"
(
==================== 正则表达式语法速查 ====================

【基本字符】
. 匹配任意单个字符(除了换行符)
\d 匹配数字 [0-9]
\D 匹配非数字 [^0-9]
\w 匹配单词字符 [a-zA-Z0-9_]
\W 匹配非单词字符 [^a-zA-Z0-9_]
\s 匹配空白字符(空格、制表符等)
\S 匹配非空白字符

【字符类】
[abc] 匹配 a、b 或 c 中的任意一个字符
[^abc] 匹配除了 a、b、c 之外的任意字符
[a-z] 匹配 a 到 z 范围内的任意字符
[0-9] 匹配 0 到 9 范围内的数字

【量词】
* 匹配前一个元素零次或多次
+ 匹配前一个元素一次或多次
? 匹配前一个元素零次或一次
{n} 匹配前一个元素恰好 n 次
{n,} 匹配前一个元素至少 n 次
{n,m} 匹配前一个元素至少 n 次,最多 m 次

【锚点】
^ 匹配字符串的开始
$ 匹配字符串的结束
\b 匹配单词边界
\B 匹配非单词边界

【分组和引用】
(abc) 捕获分组
(?:abc) 非捕获分组
| 或操作,匹配左边或右边的表达式

【转义字符】
\. 匹配点号(.)
\\ 匹配反斜杠(\)
\* 匹配星号(*)
\+ 匹配加号(+)
\? 匹配问号(?)
)"

HelpGui.Add("Edit", "x20 y40 w560 h320 ReadOnly", HelpText1)

; 第二个选项卡:常用示例
TabCtrl.UseTab(2)
HelpText2 :=
"
(
==================== 常用正则表达式示例 ====================

【匹配数字】
\d+ 匹配一个或多个数字
\d{3}-\d{4} 匹配电话号码格式(如 123-4567)
\d{4} 匹配4位数字(如年份)

【匹配邮箱】
\w+@\w+\.\w+ 简单邮箱匹配
[\w.-]+@[\w.-]+\.\w+ 更精确的邮箱匹配

【匹配URL】
https?://[^\s]+ 匹配http或https开头的URL

【匹配中文】
[\u4e00-\u9fa5]+ 匹配中文字符

【匹配日期】
\d{4}-\d{2}-\d{2} 匹配YYYY-MM-DD格式日期
\d{2}/\d{2}/\d{4} 匹配MM/DD/YYYY格式日期

【替换示例】
查找:(\d+)-(\d+) 替换:$2-$1 将'123-456'替换为'456-123'
查找:Mr\. (.*) 替换:Dr. $1 将'Mr. Smith'替换为'Dr. Smith'

【删除空行】
查找:^\s*$\r?\n 替换:(空) 删除所有空行

【提取内容】
Name: (.*) 使用$1引用捕获的姓名
)"

HelpGui.Add("Edit", "x20 y40 w560 h320 ReadOnly", HelpText2)

; 第三个选项卡:使用说明
TabCtrl.UseTab(3)
HelpText3 :=
"
(
==================== 使用说明 ====================

【基本操作流程】
1. 选择要处理的目录
2. 输入正则表达式或普通文本
3. 输入替换内容
4. 设置主文件名特征和文件扩展名
5. 点击'开始处理'执行替换

【文件筛选条件说明】
• 主文件名特征:支持*通配符,匹配文件名(不含扩展名)
• 文件扩展名:多个扩展名用逗号分隔
• 两者结合使用可精确筛选目标文件

【功能特点】
• 支持正则表达式和普通文本两种模式
• 支持主文件名特征匹配(支持*通配符)
• 自动创建备份文件(.bak)
• 支持撤消操作,可恢复原始文件
• 实时显示处理进度和结果

【注意事项】
• 处理前会自动创建备份文件,安全可靠
• 撤消操作会删除备份文件,请谨慎使用
• 支持UTF-8编码的文件处理
• 可同时处理多种文件类型

【快捷键】
• Enter键:在输入框中按Enter可快速开始处理
• Esc键:关闭帮助窗口或取消操作

【正则表达式提示】
• 普通文本查找:直接输入要查找的文字
• 正则表达式:使用上述语法进行复杂匹配
• 测试正则:建议先在少量文件上测试
)"

HelpGui.Add("Edit", "x20 y40 w560 h320 ReadOnly", HelpText3)

; 第四个选项卡:文件名匹配说明(新增)
TabCtrl.UseTab(4)
HelpText4 :=
"
(
==================== 主文件名特征匹配说明 ====================

【通配符说明】
* 匹配任意数量的任意字符(包括零个字符)

【使用示例】
title* 匹配以'title'开头的所有文件
例如:title.txt, title01.txt, title_backup.txt

*backup 匹配以'backup'结尾的所有文件
例如:file_backup.txt, data_backup.log

title*bak 匹配以'title'开头,以'bak'结尾的文件
例如:title001bak.txt, title_final_bak.log

report*2024* 匹配包含'report'和'2024'的文件
例如:report_2024.txt, monthly_report_2024_final.doc

【注意事项】
• 主文件名特征只匹配文件名(不含扩展名)
• 留空表示匹配所有文件名
• 匹配不区分大小写
• 可以结合文件扩展名进行精确筛选

【组合使用示例】
要匹配所有以'title'开头的txt文件:
主文件名特征:title*
文件扩展名:txt

要匹配所有备份文件:
主文件名特征:*backup
文件扩展名:txt,log,doc

【高级技巧】
可以使用多个*进行复杂匹配:
*test*data* 匹配包含'test'和'data'的文件名
data*2024 * 01* 匹配包含'data'、'2024'和'01'的文件名
)"

HelpGui.Add("Edit", "x20 y40 w560 h320 ReadOnly", HelpText4)

TabCtrl.UseTab() ; 结束选项卡

; 添加关闭按钮
HelpGui.Add("Button", "x250 y400 w100 h30", "关闭").OnEvent("Click", (*) => HelpGui.Destroy())

HelpGui.Show()
}

; 检查数组中是否包含某个值的函数
HasValue(arr, value) {
for index, arrValue in arr {
if (arrValue = value) {
return true
}
}
return false
}

; 获取文件扩展名的函数
GetFileExtension(fileName) {
dotPos := InStr(fileName, ".", , -1)
if (dotPos > 0) {
return SubStr(fileName, dotPos + 1)
}
return ""
}
返回列表