使用Go開發(fā)一個文件同步小工具(附源碼)
"你復(fù)制,我粘貼;你改了,我跟著動。" —— FileSync 的座右銘
大家好!今天我要給大家介紹一個我最近寫的小玩具——FileSync。它不是什么高大上的分布式同步系統(tǒng),也不是什么帶 GUI 界面的炫酷軟件,而是一個樸實無華、靠命令行吃飯、還帶點"佛系"氣質(zhì)的 Go 語言小工具。
它的任務(wù)很簡單:把 A 文件夾里的內(nèi)容,原封不動地同步到 B 文件夾里,并且自動跳過日志目錄(比如 \logs\、\log\、\runlog\),避免把一堆沒用的日志也搬過去占地方。
最重要的是——它還能優(yōu)雅退出!只要你在終端敲個 exit,它就會乖乖收工,不鬧脾氣
它是怎么工作的
FileSync 的邏輯其實非常直白:
- 啟動主線程:開始遍歷源目錄(fromDir)。
- 智能過濾:遇到名字里帶 log 的文件夾?直接跳過!我們不關(guān)心日志,只關(guān)心"正經(jīng) 文件"。
- 同步操作:
- 如果是目錄 → 在目標位置創(chuàng)建同名目錄。
- 如果是文件 → 比較修改時間!只有源文件比目標新(或目標不存在),才復(fù)制過去,并保留原始修改時間。
- 每小時掃一次:干完一輪活,就去"打坐"一小時(其實是 sleep 3600秒),然后繼續(xù)巡邏。
- 隨時聽令退出:主 goroutine 在后臺干活,主線程監(jiān)聽用戶輸入。一旦你說"exit",它立刻收手,等所有任務(wù)結(jié)束再退出,絕不拖泥帶水。
是不是有點像一個勤懇又聽話的數(shù)字園???
為什么說它"佛系"
- 它不會瘋狂刷屏告訴你"我又復(fù)制了一個文件!"(雖然現(xiàn)在會打印路徑,但你可以輕松注釋掉)。
- 它不爭不搶,每小時才工作一次,其余時間都在"冥想"。
- 它尊重文件的"前世今生"——連修改時間都要原樣保留,生怕打擾了文件的情緒。
- 你說"走",它絕不賴著不走,還會禮貌地說一句:"FileSync soft exit"。
這哪是程序?分明是個修行千年的老和尚寫的代碼
使用須知(別踩坑)
必須傳兩個參數(shù):./FileSync /path/to/source /path/to/target
路徑分隔符注意:代碼里用了 \\ 來判斷 Windows 風格的日志路徑。如果你在 Linux/macOS 上跑,可能需要改成 /logs/。不過 filepath.Walk 是跨平臺的,所以實際運行沒問題,只是過濾邏輯可能失效(因為 Linux 路徑不含反斜杠)。建議改成 / 或使用 filepath.Separator 更健壯。
權(quán)限問題:確保程序有讀源目錄、寫目標目錄的權(quán)限。
大文件警告:目前是全量復(fù)制,沒做增量或斷點續(xù)傳,超大文件可能會卡一下。
改進建議(留給未來的你)
用 fsnotify 監(jiān)聽文件變化,實時同步,告別"每小時打坐"。
支持配置文件,自定義忽略規(guī)則。
加日志輸出開關(guān),別總往 stdout 打。
支持雙向同步 or 增量備份模式。
給它起個更酷的名字,比如 ZenSync?
源碼奉上
下面就是這個"佛系同步器"的完整源碼,Go 語言編寫,簡潔明了,歡迎拿去魔改!
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
var exit = false // 退出狀態(tài)控制
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 {
// 修改時間相同,跳過復(fù)制
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
}
結(jié)語
雖然這個小工具簡單,但它體現(xiàn)了 Go 語言的并發(fā)之美(一個 goroutine 干活,主線程監(jiān)聽退出)、文件操作的便捷性,以及——一點點程序員的幽默感。
下次當你需要一個輕量、可控、不吵不鬧的同步腳本時,不妨試試這個"佛系 FileSync"。說不定,它還能幫你悟出點編程禪意呢
代碼已開源,心法自悟。
P.S. 如果你在 macOS/Linux 上使用,請記得把 \\log\\ 這類判斷改成 /log/,或者更優(yōu)雅地用 filepath.Join("log") 來處理路徑分隔符哦!
到此這篇關(guān)于使用Go開發(fā)一個文件同步小工具(附源碼)的文章就介紹到這了,更多相關(guān)Go文件同步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go實現(xiàn)map并發(fā)安全的3種方式總結(jié)
Go的原生map不是并發(fā)安全的,在多協(xié)程讀寫同一個map的時候,安全性無法得到保障,這篇文章主要給大家總結(jié)介紹了關(guān)于Go實現(xiàn)map并發(fā)安全的3種方式,需要的朋友可以參考下2023-10-10
基于Go語言開發(fā)篇一個命令行進程監(jiān)控工具
在生產(chǎn)和開發(fā)環(huán)境中,監(jiān)控關(guān)鍵進程的存活與資源使用是非常常見的需求,本篇將開發(fā)一個輕量級的命令行進程監(jiān)控工具,感興趣的小伙伴可以了解下2025-09-09
golang中strconv.ParseInt函數(shù)用法示例
這篇文章主要介紹了golang中strconv.ParseInt函數(shù)用法,實例分析了strconv.ParseInt函數(shù)將字符串轉(zhuǎn)換為數(shù)字的簡單使用方法,需要的朋友可以參考下2016-07-07
Go創(chuàng)建一個包并使用(導(dǎo)入本地包和注意事項)
有時候需要自己寫一個包方便多次使用,但是在導(dǎo)入自己寫的包時遇到了問題,本文主要介紹了Go創(chuàng)建一個包并使用(導(dǎo)入本地包和注意事項),感興趣的可以了解一下2023-11-11

