使用Go开发一个文件同步小工具(附源码)

 更新时间:2025年11月18日 09:51:19   作者:Mgx  
这篇文章主要为大家详细介绍了如何使用Go开发一个文件同步小工具并附上源码,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

"你复制,我粘贴;你改了,我跟着动。" —— FileSync 的座右铭

大家好!今天我要给大家介绍一个我最近写的小玩具——FileSync。它不是什么高大上的分布式同步系统,也不是什么带 GUI 界面的炫酷软件,而是一个朴实无华、靠命令行吃饭、还带点"佛系"气质的 Go 语言小工具。

它的任务很简单:把 A 文件夹里的内容,原封不动地同步到 B 文件夹里,并且自动跳过日志目录(比如 \logs\、\log\、\runlog\),避免把一堆没用的日志也搬过去占地方。

最重要的是——它还能优雅退出!只要你在终端敲个 exit,它就会乖乖收工,不闹脾气 

它是怎么工作的

FileSync 的逻辑其实非常直白:

  • 启动主线程:开始遍历源目录(fromDir)。
  • 智能过滤:遇到名字里带 log 的文件夹?直接跳过!我们不关心日志,只关心"正经 文件"。
  • 同步操作:
    • 如果是目录 → 在目标位置创建同名目录。
    • 如果是文件 → 比较修改时间!只有源文件比目标新(或目标不存在),才复制过去,并保留原始修改时间。
  • 每小时扫一次:干完一轮活,就去"打坐"一小时(其实是 sleep 3600秒),然后继续巡逻。
  • 随时听令退出:主 goroutine 在后台干活,主线程监听用户输入。一旦你说"exit",它立刻收手,等所有任务结束再退出,绝不拖泥带水。

是不是有点像一个勤恳又听话的数字园丁?

为什么说它"佛系"

  • 它不会疯狂刷屏告诉你"我又复制了一个文件!"(虽然现在会打印路径,但你可以轻松注释掉)。
  • 它不争不抢,每小时才工作一次,其余时间都在"冥想"。
  • 它尊重文件的"前世今生"——连修改时间都要原样保留,生怕打扰了文件的情绪。
  • 你说"走",它绝不赖着不走,还会礼貌地说一句:"FileSync soft exit"。

这哪是程序?分明是个修行千年的老和尚写的代码 

使用须知(别踩坑)

必须传两个参数:./FileSync /path/to/source /path/to/target

路径分隔符注意:代码里用了 \\ 来判断 Windows 风格的日志路径。如果你在 Linux/macOS 上跑,可能需要改成 /logs/。不过 filepath.Walk 是跨平台的,所以实际运行没问题,只是过滤逻辑可能失效(因为 Linux 路径不含反斜杠)。建议改成 / 或使用 filepath.Separator 更健壮。

权限问题:确保程序有读源目录、写目标目录的权限。

大文件警告:目前是全量复制,没做增量或断点续传,超大文件可能会卡一下。

改进建议(留给未来的你)

用 fsnotify 监听文件变化,实时同步,告别"每小时打坐"。

支持配置文件,自定义忽略规则。

加日志输出开关,别总往 stdout 打。

支持双向同步 or 增量备份模式。

给它起个更酷的名字,比如 ZenSync?

源码奉上

下面就是这个"佛系同步器"的完整源码,Go 语言编写,简洁明了,欢迎拿去魔改!

package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

var exit = false           // 退出状态控制
var wg sync.WaitGroup      // 保证正常退出

func main() {
	fmt.Println("FileSync soft run")
	if len(os.Args) < 3 {
		fmt.Println("Usage: FileSync <source_dir> <target_dir>")
		return
	}
	fromDir := os.Args[1]
	toDir := os.Args[2]
	wg.Add(1)
	go mainRun(fromDir, toDir) // 启动工作主线程

	for {
		var cmd string
		fmt.Scanln(&cmd)
		fmt.Println("cmd:", cmd)
		if cmd == "exit" {
			exit = true
			break
		} else {
			fmt.Println("unknow command")
			fmt.Println("exit exit soft")
		}
	}
	wg.Wait()
	fmt.Println("FileSync soft exit")
}

func mainRun(fromDir, toDir string) {
	defer wg.Done()
	for !exit {
		filepath.Walk(fromDir, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return nil
			}
			fmt.Println(path)
			p := strings.ToLower(path)
			// 跳过常见日志目录(注意:Windows风格路径)
			if strings.Contains(p, "\\runlog\\") || 
			   strings.Contains(p, "\\logs\\") || 
			   strings.Contains(p, "\\log\\") {
				return nil
			}
			if info.IsDir() {
				syncDir(strings.Replace(path, fromDir, toDir, 1))
			} else {
				syncFile(path, strings.Replace(path, fromDir, toDir, 1))
			}
			return nil
		})
		// 每小时同步一次
		for i := 0; i < 60*60; i++ {
			time.Sleep(time.Second)
			if exit {
				break
			}
		}
	}
}

func syncDir(dirname string) {
	if err := os.MkdirAll(dirname, 0777); err != nil {
		fmt.Println(err)
	}
}

func syncFile(f, t string) {
	CopyFile(f, t)
}

func CopyFile(f, t string) (written int64, err error) {
	src, err := os.Open(f)
	if err != nil {
		return
	}
	defer src.Close()
	st, _ := src.Stat()
	mt := st.ModTime()

	var dst *os.File
	if ft, exist := checkFileIsExist(t); exist {
		// 修改时间相同,跳过复制
		if st.ModTime().UnixNano() == ft.UnixNano() {
			fmt.Println("修改时间相同,放弃")
			return
		}
		dst, err = os.OpenFile(t, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0777)
		if err != nil {
			fmt.Println(err)
			return
		}
	} else {
		dst, err = os.Create(t)
		if err != nil {
			fmt.Println(err)
			return
		}
	}
	l, err := io.Copy(dst, src)
	dst.Close()
	if err == nil {
		// 保留原始修改时间
		err := os.Chtimes(t, mt, mt)
		if err != nil {
			fmt.Println(err)
		}
	}
	return l, err
}

func checkFileIsExist(filename string) (time.Time, bool) {
	fi, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return time.Now(), false
	} else if err != nil {
		return time.Now(), false
	}
	return fi.ModTime(), true
}

结语

虽然这个小工具简单,但它体现了 Go 语言的并发之美(一个 goroutine 干活,主线程监听退出)、文件操作的便捷性,以及——一点点程序员的幽默感。

下次当你需要一个轻量、可控、不吵不闹的同步脚本时,不妨试试这个"佛系 FileSync"。说不定,它还能帮你悟出点编程禅意呢 

代码已开源,心法自悟。

P.S. 如果你在 macOS/Linux 上使用,请记得把 \\log\\ 这类判断改成 /log/,或者更优雅地用 filepath.Join("log") 来处理路径分隔符哦!

到此这篇关于使用Go开发一个文件同步小工具(附源码)的文章就介绍到这了,更多相关Go文件同步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang循环变量捕获问题​​的解决

    golang循环变量捕获问题​​的解决

    本在Go语言中,循环中启动协程时,直接在协程闭包中引用循环变量会导致所有协程共享同一个变量,从而引发变量捕获问题,本文就来介绍一下该问题的解决,感兴趣的可以了解一下
    2025-11-11
  • Go语言基础教程之函数和方法详解

    Go语言基础教程之函数和方法详解

    在Go语言中,函数和方法在声明方式上存在显著差异,理解这一点是正确解读文档的关键,这篇文章主要介绍了Go语言基础教程之函数和方法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-10-10
  • Goland使用Go Modules创建/管理项目的操作

    Goland使用Go Modules创建/管理项目的操作

    这篇文章主要介绍了Goland使用Go Modules创建/管理项目的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Go语言题解LeetCode599两个列表的最小索引总和

    Go语言题解LeetCode599两个列表的最小索引总和

    这篇文章主要为大家介绍了Go语言题解LeetCode599两个列表的最小索引总和示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • 解析golang 标准库template的代码生成方法

    解析golang 标准库template的代码生成方法

    这个项目的自动生成代码都是基于 golang 的标准库 template 的,所以这篇文章也算是对使用 template 库的一次总结,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2021-11-11
  • GoLang BoltDB数据库详解

    GoLang BoltDB数据库详解

    这篇文章主要介绍了GoLang BoltDB数据库,boltdb是使用Go语言编写的开源的键值对数据库,boltdb存储数据时 key和value都要求是字节数据,此处需要使用到 序列化和反序列化
    2023-02-02
  • Go常问的一些面试题汇总(附答案)

    Go常问的一些面试题汇总(附答案)

    通常我们去面试肯定会有些不错的Golang的面试题目的,所以总结下,让其他Golang开发者也可以查看到,同时也用来检测自己的能力和提醒自己的不足之处,这篇文章主要给大家介绍了关于Go常问的一些面试题以及答案的相关资料,需要的朋友可以参考下
    2023-10-10
  • Go Singleflight导致死锁问题解决分析

    Go Singleflight导致死锁问题解决分析

    这篇文章主要为大家介绍了Go Singleflight导致死锁问题解决分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Go使用sync.Map来解决map的并发操作问题

    Go使用sync.Map来解决map的并发操作问题

    在 Golang 中 map 不是并发安全的,sync.Map 的引入确实解决了 map 的并发安全问题,本文就详细的介绍一下如何使用,感兴趣的可以了解一下
    2021-10-10
  • Go defer 去掉闭包函数及用法分析

    Go defer 去掉闭包函数及用法分析

    这篇文章主要为大家介绍了Go defer 去掉闭包函数及用法分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07

最新评论