我用 Golang 和 go-winres 做了一个文件大搬运
背景
某天,一位小学教育界一线的朋友找我咨询如何开发一款 windows 应用程序,他说已自学 Python,但在某些细节上不甚清楚。我感叹其学习劲头,有种“编程要从娃娃抓起”的感慨。
经过沟通,他想做一个能将指定文件转移到目标位置的程序,以节省整理资料的时间而更多的将其投入到学生教育中去。我大受感动,说:小学教育界已经这么卷了吗……
为了朋友能更好的完成教育使命,我于是连夜开发了一个简易文件操作程序。该软件被命名为“大搬运”,以纪念搬砖工作者的辛苦付出和各岗位卷王们的不懈努力。
程序设计
-
程序主体使用 Golang 开发
-
使用 go-winres 进行简易封装(更多使用方法需自行了解)
源码
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"sync/atomic"
)
var from = flag.String("from", "", "操作目录名,例:c:/a/b")
var to = flag.String("to", "", "目标目录名,例:c:/a/b/c")
var name = flag.String("name", "", "完整文件名或片段")
var mh = flag.Bool("mh", true, "是否开启模糊匹配,默认为 true")
var child = flag.Bool("child", false, "是否开启子目录检查,默认为 false")
var cover = flag.Bool("cover", false, "是否开启同名文件覆盖,关闭覆盖时将自动增补序号,默认为 false")
// main 启动入口
func main() {
// 解析命令
flag.Parse()
// 运行前检查
beforeCheck()
// 读取操作目录信息
fromDir, err := os.ReadDir(*from)
if err != nil {
log.Fatalf("检查操作目录失败 err: %s", err.Error())
}
// 判断是否需要执行子目录任务
if *child {
doWithChild(*from, fromDir)
} else {
doCurrent(*from, fromDir)
}
}
// beforeCheck 前置检查
func beforeCheck() {
// 操作目录检查
if *from == "" {
log.Fatalf("须指定操作目录 from")
}
// 目标目录检查
if *to == "" {
log.Fatalf("须指定目标目录 to")
}
if !isFolderExists(*to) {
err := os.MkdirAll(*to, os.ModePerm)
if err != nil {
log.Fatalf("创建目标目录失败 err: %s", err.Error())
}
}
}
// doCurrent 操作当前目录
func doCurrent(dir string, fromDir []os.DirEntry) {
for _, f := range fromDir {
// 跳过子目录
if f.IsDir() {
continue
}
// 操作文件
moveFile(dir, f)
}
}
// doWithChild 操作包含子目录
func doWithChild(dir string, fromDir []os.DirEntry) {
for _, f := range fromDir {
// 检查子目录
if f.IsDir() {
childDir := filepath.Join(dir, f.Name())
fromChildDir, err := os.ReadDir(childDir)
if err != nil {
log.Printf("[ERROR] 读取目录[%s]失败 err: %s", f.Name(), err.Error())
continue
}
// 优先处理子目录
doWithChild(childDir, fromChildDir)
continue
}
// 操作文件
moveFile(dir, f)
}
}
// moveFile 移动文件
func moveFile(dir string, f os.DirEntry) {
// 仅处理文件,此为双重校验
if f.IsDir() {
return
}
// 文件名匹配
fileName := f.Name()
if *name != "" {
if *mh {
if !strings.Contains(fileName, *name) {
return
}
} else {
if fileName != *name {
return
}
}
}
// 移动文件
srcFilePathName := filepath.Join(dir, fileName)
srcFile, err := os.Open(srcFilePathName)
if err != nil {
log.Printf("[ERROR] 读取文件[%s]失败 err: %s", srcFilePathName, err.Error())
return
}
defer srcFile.Close() // release
// 创建目标文件
dstFilePathName := filepath.Join(*to, fileName)
// 非覆盖模式,需检查序号
if !(*cover) {
var index int32
dirs, err := os.ReadDir(*to)
if err != nil {
log.Printf("[ERROR] 读取目标文件[%s]失败 err: %s", *to, err.Error())
return
}
// 检查该目录下文件,确定增补序号
for _, dir := range dirs {
if dir.Name() == fileName {
atomic.AddInt32(&index, 1)
}
}
if index > 0 {
ns := strings.Split(fileName, ".")
baseName := ns[0]
var baseExt string
if len(ns) > 1 { // for non ext file
baseExt = ns[1]
}
dstFilePathName = filepath.Join(*to, fmt.Sprintf("%s(%d).%s", baseName, index, baseExt))
}
}
// 复制文件并删除原文件
dstFile, err := os.Create(dstFilePathName)
if err != nil {
log.Printf("[ERROR] 创建目标文件[%s]失败 err: %s", dstFilePathName, err.Error())
return
}
defer dstFile.Close() // release
_, err = io.Copy(dstFile, srcFile)
if err != nil {
log.Printf("[ERROR] 复制文件内容[%s]失败 err: %s", srcFilePathName, err.Error())
return
}
err = dstFile.Sync()
if err != nil {
log.Printf("[ERROR] 移动文件[%s]失败 err: %s", srcFilePathName, err.Error())
return
}
}
// isFolderExists 检查目录是否存在
func isFolderExists(folderName string) bool {
info, err := os.Stat(folderName)
if os.IsNotExist(err) {
return false
}
if err != nil {
return false
}
return info.IsDir()
}
编译脚本
- 安装 go-winres
go install github.com/tc-hib/go-winres@latest
- 初始化 go-winres 配置
进入项目根目录,创建 go-winres 初始化配置,该步骤将创建一个 winres 目录,其中包含两个图片文件和一个名为 winres.json 的配置文件。
go-winres init
- 进入 winres 目录视情况修改 winres.json 内配置,常用配置如下。
CompanyName 公司名称
FileDescription 文件描述
FileVersion 文件版本
ProductVersion 程序版本
LegalCopyright 版权信息
OriginalFilename 原始文件名
ProductName 程序名称
- 编译资源
在 winres 同级目录下执行编译资源命令
go-winres make
- 编译 Golang 程序
go mod tidy
go clean
go env -w CGO_ENABLED=1
SET GOOS=windows
SET GOARCH=amd64
go build -v -ldflags="-w -s" -o bigmove.exe .
操作说明
示例
1. 查看全部命令
命令:bigmove.exe -help
输出:
-from string
操作目录名,例:c:/a/b
-to string
目标目录名,例:c:/a/b/c
-name string
完整文件名或片段
-mh
是否开启模糊匹配,默认为 true (default true)
-child
是否开启子目录检查,默认为 false
-cover
是否开启同名文件覆盖,关闭覆盖时将自动增补序号,默认为 false
2. 将目录 FolderA 下的名为 Name 的文件复制到目录 FolderB,
文件名模糊匹配、增补序号
命令:bigmove.exe -from=FolderA -to=FolderB -name=Name
3. 将目录 FolderA 下的名为 Name 的文件复制到目录 FolderB,
文件名精确匹配、增补序号
命令:bigmove.exe -from=FolderA -to=FolderB -name=Name -mh=false
4. 将目录 FolderA 下的名为 Name 的全部文件复制到目录 FolderB,
子目录检查、文件名模糊匹配、增补序号
命令:bigmove.exe -from=FolderA -to=FolderB -name=Name -child=true
5. 将目录 FolderA 下的名为 Name 的全部文件复制到目录 FolderB,并对已存在文件进行覆盖
子目录检查、文件名模糊匹配、文件覆盖
命令:bigmove.exe -from=FolderA -to=FolderB -name=Name -child=true -cover=true
后来
朋友在软件的帮助下节省了大量整理文件时间,并被告知今年很有可能被评为先进工作者。我们都很高兴。