Go 中文件目录基本操作
本文内容基于 AI 生成结果整理,可能包含不准确信息,仅供参考使用。
本文目标
- 掌握 os 库的目录操作:创建/删除目录、遍历目录等
- 熟练文件读写操作:包括打开/关闭文件、读写内容
- 理解路径操作方法:处理相对/绝对路径、路径拼接等
相对路径时是基于执行时的工作目录,而不是代码文件位置
例如:
/home/user/projects/myproject/file/mian.go 存在代码 os.Create("./test.txt")
当你在 /home/user/projects/myproject/ 执行 file/mian.go 的程序时
会在 /home/user/projects/myproject/ 目录下创建 test.txt 文件
文件操作
创建文件
go
file, err := os.Create("./test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
os.Create()
创建新文件,如果文件已存在则会被截断(清空)- 返回
*os.File
和可能的错误 - 记得使用
defer
关闭文件
打开文件
go
file, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
os.Open()
以只读方式打开文件- 返回
*os.File
和可能的错误
打开文件(带选项)
go
file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
-
os.OpenFile()
可以指定打开模式和权限 -
常用模式:
os.O_RDONLY
:只读os.O_WRONLY
:只写os.O_RDWR
:读写os.O_APPEND
:追加os.O_CREATE
:不存在则创建os.O_TRUNC
:打开时清空文件
写入文件
go
content := []byte("Hello, World!\n")
_, err := file.Write(content)
if err != nil {
log.Fatal(err)
}
Write()
方法写入字节切片- 返回写入的字节数和可能的错误
读取文件
go
data := make([]byte, 100)
count, err := file.Read(data)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Read %d bytes: %q\n", count, data[:count])
Read()
方法读取到字节切片- 返回读取的字节数和可能的错误
文件信息
go
fileInfo, err := os.Stat("test.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("File name:", fileInfo.Name())
fmt.Println("Size:", fileInfo.Size())
fmt.Println("Permissions:", fileInfo.Mode())
fmt.Println("Last modified:", fileInfo.ModTime())
fmt.Println("Is directory:", fileInfo.IsDir())
os.Stat()
获取文件信息- 返回
os.FileInfo
接口,包含文件的各种属性
删除文件
go
err := os.Remove("test.txt")
if err != nil {
log.Fatal(err)
}
os.Remove()
删除文件或空目录
目录操作
创建目录
go
err := os.Mkdir("mydir", 0755)
if err != nil {
log.Fatal(err)
}
-
os.Mkdir()
创建单级目录 -
0755 是 Unix 权限位,表示所有者可读写执行,组和其他可读执行
创建多级目录
go
err := os.MkdirAll("parent/child/grandchild", 0755)
if err != nil {
log.Fatal(err)
}
os.MkdirAll()
创建多级目录
读取目录
go
entries, err := os.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
fmt.Println(entry.Name())
if entry.IsDir() {
fmt.Println(" (directory)")
}
}
os.ReadDir()
读取目录内容- 返回
[]os.DirEntry
,比file.Readdir
更高效
删除目录
go
err := os.Remove("mydir")
if err != nil {
log.Fatal(err)
}
os.Remove()
删除空目录
只会删除空目录,即使里面存在的是空文件。
删除目录及其内容
go
err := os.RemoveAll("parent")
if err != nil {
log.Fatal(err)
}
os.RemoveAll()
递归删除目录及其所有内容
不存在时创建目录
方法 1:使用 os.Stat+ os.Mkdir
go
func ensureDir(dirPath string) error {
// 检查目录是否存在
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
// 不存在则创建,0755 是目录权限(rwxr-xr-x)
err := os.Mkdir(dirPath, 0755)
if err != nil {
return fmt.Errorf("创建目录失败: %v", err)
}
}
return nil
}
方法 2:使用 os.MkdirAll(推荐)
go
func ensureDir(dirPath string) error {
// MkdirAll 会创建所有不存在的父目录
err := os.MkdirAll(dirPath, 0755)
if err != nil {
return fmt.Errorf("创建目录失败: %v", err)
}
return nil
}
路径操作
是开发跨平台文件系统相关程序的必备工具集。
虽然 os 包提供了一些路径操作,但更推荐使用 path/filepath 包。
常用方法
go
package main
import (
"fmt"
"log"
"os"
"path/filepath" // 提供跨平台的文件路径操作
"strings" // 提供字符串处理功能
)
func main() {
// 获取当前工作目录
// Getwd 返回当前进程的工作目录
currentDir, err := os.Getwd()
if err != nil {
log.Fatal("获取当前目录失败:", err)
}
fmt.Println("当前工作目录:", currentDir)
// 切换工作目录
err = os.Chdir("../")
currentDir, err = os.Getwd()
if err != nil {
log.Fatal("获取当前目录失败:", err)
}
fmt.Println("当前工作目录:", currentDir)
// 跨平台路径拼接
// Join 函数会根据操作系统自动使用正确的路径分隔符
// 在 Unix 系统上是"/",在Windows上是"\"
path := filepath.Join("dir1", "dir2", "wl.cool")
fmt.Println("拼接后的路径:", path) // 输出取决于操作系统
// 自动处理冗余路径符号
// Join 会自动处理多余的路径分隔符和"."、".."
fmt.Println("处理冗余分隔符:", filepath.Join("dir1//", "filename")) // dir1/filename
fmt.Println("处理相对路径:", filepath.Join("dir1/../dir1", "filename")) // dir1/filename
// 获取路径组成要素
// Dir 返回路径的目录部分(去掉最后一个元素)
fmt.Println("目录部分:", filepath.Dir(path)) // dir1/dir2
// Base 返回路径的最后一个元素
fmt.Println("文件名部分:", filepath.Base(path)) // wl.cool
// 判断绝对路径
// IsAbs 判断路径是否是绝对路径
fmt.Println("是否是绝对路径:", filepath.IsAbs("dir/file")) // false
fmt.Println("是否是绝对路径:", filepath.IsAbs("/dir/file")) // Unix: true, Windows: false
// 文件扩展名处理
filename := "config.json"
// Ext 返回文件扩展名(包含点)
ext := filepath.Ext(filename)
fmt.Println("文件扩展名:", ext) // .json
// 去除扩展名
fmt.Println("去除扩展名:", strings.TrimSuffix(filename, ext)) // config
// 计算相对路径
// Rel 计算从 base 到 target 的相对路径
rel, err := filepath.Rel("a/b", "a/b/t/file")
if err != nil {
log.Fatal("计算相对路径失败:", err)
}
fmt.Println("相对路径1:", rel) // t/file
rel, err = filepath.Rel("a/b", "a/c/t/file")
if err != nil {
log.Fatal("计算相对路径失败:", err)
}
fmt.Println("相对路径2:", rel) // ../c/t/file
// 获取绝对路径
// Abs 返回路径的绝对表示形式
absPath, err := filepath.Abs("relative/path")
if err != nil {
log.Fatal("获取绝对路径失败:", err)
}
fmt.Println("绝对路径:", absPath)
// 路径分割
// Split 将路径分割为目录和文件名部分
dir, file := filepath.Split("/path/to/file.txt")
fmt.Printf("分割结果 - 目录: %q, 文件名: %q\n", dir, file) // "/path/to/", "file.txt"
// 路径分隔符
// 获取操作系统特定的路径分隔符
fmt.Println("路径分隔符:", string(filepath.Separator))
// 路径列表操作
// 将路径列表用操作系统特定的列表分隔符连接
paths := []string{"path1", "path2", "path3"}
fmt.Println("路径列表:", filepath.Join(paths...))
// 路径清理
// Clean通过纯词法处理返回等价的最短路径名
fmt.Println("清理路径:", filepath.Clean("./path/../to/./file")) // to/file
// 匹配文件名模式
// Match 检查文件名是否匹配 shell 文件名模式
matched, err := filepath.Match("*.go", "main.go")
if err != nil {
log.Fatal("模式匹配失败:", err)
}
fmt.Println("是否匹配:", matched) // true
// 获取临时目录
// TempDir 返回用于临时文件的默认目录
fmt.Println("临时目录:", os.TempDir())
// 获取用户配置目录
// UserConfigDir 返回用户配置目录
configDir, err := os.UserConfigDir()
if err != nil {
log.Fatal("获取用户配置目录失败:", err)
}
fmt.Println("用户配置目录:", configDir)
// 获取用户主目录
// UserHomeDir 返回当前用户的主目录
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal("获取用户主目录失败:", err)
}
fmt.Println("用户主目录:", homeDir)
}
实际应用场景
配置文件路径处理
go
func getConfigPath() string {
return filepath.Join(os.Getenv("HOME"), ".config", "myapp.cfg")
}
Web 服务器静态文件服务
go
func safeJoin(base, userPath string) (string, error) {
// 防止目录穿越攻击
return filepath.Join(base, filepath.Clean("/"+userPath)), nil
}
日志文件轮转
go
func getNextLogPath(path string) string {
ext := filepath.Ext(path)
base := strings.TrimSuffix(path, ext)
return fmt.Sprintf("%s_%d%s", base, time.Now().Unix(), ext)
}
遍历目录
filepath.Walk
功能: 递归遍历目录及其子目录,对每个文件/目录调用回调函数。
语法:
go
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
// 处理逻辑
return nil
})
特点:
- 基于
os.FileInfo
- 回调函数接收
os.FileInfo
接口,包含文件大小、权限、修改时间等元数据。 - 每次调用
os.Lstat
获取文件信息,可能影响性能。
- 深度优先遍历
- 先处理子目录,再处理父目录(除非在回调中跳过)。
- 错误处理
- 如果回调返回 error,遍历会终止并返回该错误。
- 适用场景
- 需要文件详细信息的场景(如统计文件大小、过滤特定类型文件)。
示例:
go
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Println(path, info.Size())
return nil
})
if err != nil {
fmt.Println("遍历出错:", err)
}
}
filepath.WalkDir
Go 1.16+ 推荐方式功能 : 更高效的目录遍历方式,减少 os.Lstat 调用,提升性能。
语法:
go
// 使用 os.ReadDir(非递归)
entries, err := os.ReadDir(dirName)
// 使用 filepath.WalkDir(递归)
err := filepath.WalkDir(rootDir, func(path string, d os.DirEntry, err error) error {
// 处理逻辑
return nil
})
特点 :
- 基于
os.DirEntry
- 相比
os.FileInfo
,os.DirEntry
延迟加载文件信息(如Name()
直接可用,Info()
需额外调用)。 - 减少不必要的
stat
系统调用,性能更高。
- 更细粒度控制
filepath.WalkDir
允许用fs.SkipDir
跳过目录,或用fs.SkipAll
终止遍历。
- 适用场景
- 仅需文件名或文件类型(如遍历目录但不关心文件大小)。
- 高性能要求的场景(如快速查找文件)。
示例:
go
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
)
func main() {
// 方式1: os.ReadDir(单层遍历)
entries, err := os.ReadDir(".")
if err != nil {
panic(err)
}
for _, entry := range entries {
fmt.Println(entry.Name(), entry.IsDir())
}
// 方式2: filepath.WalkDir(递归遍历)
err = filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Println(path, d.IsDir())
return nil
})
}
关键区别总结
特性 | filepath.Walk | os.ReadDir/filepath.WalkDir |
---|---|---|
底层实现 | 每次调用 os.Lstat |
延迟加载文件信息(减少 stat 调用) |
性能 | 较慢(频繁系统调用) | 更快(优化后的元数据访问) |
返回类型 | os.FileInfo |
os.DirEntry |
适用场景 | 需要文件详细信息 | 仅需文件名或类型 |
Go 版本 | 所有版本 | Go 1.16+(推荐) |
技巧
快速获取文件名列表
go
entries, _ := os.ReadDir(".")
for _, entry := range entries {
fmt.Println(entry.Name())
}
跳过子目录(WalkDir 专属)
go
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
if d.IsDir() && d.Name() == "skipme" {
return fs.SkipDir // 跳过该目录
}
fmt.Println(path)
return nil
})
仅统计文件数量
go
count := 0
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() {
count++
}
return nil
})
fmt.Println("文件总数:", count)
临时文件和目录
是开发需要处理临时文件/目录的工具或服务的必备模式。
go
package main
import (
"fmt"
"os"
"path/filepath"
)
// 错误检查辅助函数
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
// 1. 创建临时文件
f, err := os.CreateTemp("", "sample") // 参数1: 目录(空表示默认临时目录),参数2: 文件名前缀
check(err)
fmt.Println("Temp file name:", f.Name()) // 输出完整路径如 /tmp/sample123456
// 确保程序退出时删除临时文件
defer os.Remove(f.Name())
// 2. 写入二进制数据
_, err = f.Write([]byte{1, 2, 3, 4})
check(err)
// 3. 创建临时目录
dname, err := os.MkdirTemp("", "sampledir") // 同样可指定目录和前缀
check(err)
fmt.Println("Temp dir name:", dname) // 输出如 /tmp/sampledir789012
// 确保程序退出时删除整个临时目录
defer os.RemoveAll(dname)
// 4. 在临时目录中创建文件
fname := filepath.Join(dname, "file1")
err = os.WriteFile(fname, []byte{1, 2}, 0666) // 权限-rw-rw-rw-
check(err)
}
实际应用场景
下载临时存储
go
tempFile, _ := os.CreateTemp("", "download-")
defer os.Remove(tempFile.Name())
io.Copy(tempFile, resp.Body) // 从网络下载到临时文件
数据处理中间文件
go
tempDir, _ := os.MkdirTemp("", "process-")
defer os.RemoveAll(tempDir)
// 在tempDir中创建多个中间文件进行处理
单元测试临时空间
go
func TestSomething(t *testing.T) {
tmp := t.TempDir() // 测试专用临时目录(自动清理)
// 创建测试文件...
}
简单案例
文件操作
go
package main
import (
"fmt"
"os"
)
func main() {
// 写入文件
data := []byte("WangLei so cool!")
// Deprecated: As of Go 1.16, this function simply calls [os.WriteFile].
//err := ioutil.WriteFile("test.txt", data, 0644)
err := os.WriteFile("test.txt", data, 0644)
if err != nil {
fmt.Println("写入文件错误:", err)
return
}
fmt.Println("文件写入成功")
// 读取文件
// Deprecated: As of Go 1.16, this function simply calls [os.ReadFile].
//content, err := ioutil.ReadFile("test.txt")
content, err := os.ReadFile("test.txt")
if err != nil {
fmt.Println("读取文件错误:", err)
return
}
fmt.Println("文件内容:", string(content))
// 检查文件是否存在
if _, err := os.Stat("test.txt"); os.IsNotExist(err) {
fmt.Println("文件不存在")
} else {
fmt.Println("文件存在")
}
// 删除文件
err = os.Remove("test.txt")
if err != nil {
fmt.Println("删除文件错误:", err)
return
}
fmt.Println("文件删除成功")
}
目录操作
go
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
)
// 错误检查辅助函数
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
// 1. 创建单个目录
err := os.Mkdir("subdir", 0755) // 755权限:rwxr-xr-x
check(err)
// 确保最终删除整个目录
defer os.RemoveAll("subdir")
// 2. 创建空文件的辅助函数
createEmptyFile := func(name string) {
d := []byte("")
check(os.WriteFile(name, d, 0644)) // 644权限:rw-r--r--
}
// 3. 创建目录结构
createEmptyFile("subdir/file1")
err = os.MkdirAll("subdir/parent/child", 0755) // 递归创建目录
check(err)
createEmptyFile("subdir/parent/file2")
createEmptyFile("subdir/parent/file3")
createEmptyFile("subdir/parent/child/file4")
// 4. 读取目录内容
c, err := os.ReadDir("subdir/parent")
check(err)
fmt.Println("Listing subdir/parent")
for _, entry := range c {
fmt.Println(" ", entry.Name(), entry.IsDir()) // 输出文件名和是否是目录
}
// 5. 切换工作目录
err = os.Chdir("subdir/parent/child")
check(err)
c, err = os.ReadDir(".") // 读取当前目录
check(err)
fmt.Println("Listing subdir/parent/child")
for _, entry := range c {
fmt.Println(" ", entry.Name(), entry.IsDir())
}
// 6. 返回原始目录
err = os.Chdir("../../..")
check(err)
// 7. 递归遍历目录
fmt.Println("Visiting subdir")
err = filepath.WalkDir("subdir", visit) // 使用自定义visit函数
check(err)
}
// 目录遍历回调函数
func visit(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Println(" ", path, d.IsDir())
return nil
}
文件复制
go
package main
import (
"fmt"
"io"
"os"
)
func copyFile(src, dst string) (int64, error) {
source, err := os.Open(src)
if err != nil {
return 0, err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destination.Close()
nBytes, err := io.Copy(destination, source)
return nBytes, err
}
func main() {
if len(os.Args) != 3 {
fmt.Println("用法: filecopy <源文件> <目标文件>")
return
}
src := os.Args[1]
dst := os.Args[2]
bytesCopied, err := copyFile(src, dst)
if err != nil {
fmt.Println("复制文件错误:", err)
return
}
fmt.Printf("成功复制 %d 字节从 %s 到 %s\n", bytesCopied, src, dst)
}
CSV 文件读写
go
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
)
type Employee struct {
Name string
Age int
Salary float64
}
func readCSV(filename string) ([]Employee, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
return nil, err
}
var employees []Employee
for i, record := range records {
if i == 0 {
continue // 跳过标题行
}
age, err := strconv.Atoi(record[1])
if err != nil {
return nil, err
}
salary, err := strconv.ParseFloat(record[2], 64)
if err != nil {
return nil, err
}
employees = append(employees, Employee{
Name: record[0],
Age: age,
Salary: salary,
})
}
return employees, nil
}
func writeCSV(filename string, employees []Employee) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// 写入标题行
if err := writer.Write([]string{"Name", "Age", "Salary"}); err != nil {
return err
}
// 写入数据
for _, emp := range employees {
record := []string{
emp.Name,
strconv.Itoa(emp.Age),
strconv.FormatFloat(emp.Salary, 'f', 2, 64),
}
if err := writer.Write(record); err != nil {
return err
}
}
return nil
}
func filterEmployees(employees []Employee, minSalary float64) []Employee {
var result []Employee
for _, emp := range employees {
if emp.Salary >= minSalary {
result = append(result, emp)
}
}
return result
}
func main() {
// 读取CSV文件
employees, err := readCSV("employees.csv")
if err != nil {
fmt.Printf("读取CSV错误: %v\n", err)
return
}
fmt.Println("所有员工:")
for _, emp := range employees {
fmt.Printf("%s, %d岁, 薪资: %.2f\n", emp.Name, emp.Age, emp.Salary)
}
// 过滤高薪员工
highPaid := filterEmployees(employees, 50000)
fmt.Println("\n高薪员工(>=50000):")
for _, emp := range highPaid {
fmt.Printf("%s, %d岁, 薪资: %.2f\n", emp.Name, emp.Age, emp.Salary)
}
// 写入新的CSV文件
if err := writeCSV("high_paid_employees.csv", highPaid); err != nil {
fmt.Printf("写入CSV错误: %v\n", err)
return
}
fmt.Println("\n高薪员工已保存到: high_paid_employees.csv")
}
文件加密与解密工具
go
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
"os"
)
func encryptFile(inputFile, outputFile string, key []byte) error {
// 读取原始文件
plaintext, err := os.ReadFile(inputFile)
if err != nil {
return err
}
// 创建加密块
block, err := aes.NewCipher(key)
if err != nil {
return err
}
// 创建GCM模式的加密器
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
// 创建随机nonce
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return err
}
// 加密数据
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
// 写入加密文件
return os.WriteFile(outputFile, ciphertext, 0644)
}
func decryptFile(inputFile, outputFile string, key []byte) error {
// 读取加密文件
ciphertext, err := os.ReadFile(inputFile)
if err != nil {
return err
}
// 创建加密块
block, err := aes.NewCipher(key)
if err != nil {
return err
}
// 创建GCM模式的解密器
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
// 提取nonce
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return errors.New("加密文件已损坏")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
// 解密数据
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return err
}
// 写入解密文件
return os.WriteFile(outputFile, plaintext, 0644)
}
func generateKey() ([]byte, error) {
key := make([]byte, 32) // AES-256
if _, err := rand.Read(key); err != nil {
return nil, err
}
return key, nil
}
func main() {
// 生成加密密钥
key, err := generateKey()
if err != nil {
fmt.Printf("生成密钥错误: %v\n", err)
return
}
// 保存密钥到文件(实际应用中应该安全存储)
if err := os.WriteFile("secret.key", key, 0644); err != nil {
fmt.Printf("保存密钥错误: %v\n", err)
return
}
inputFile := "test.txt"
encryptedFile := "test.enc"
decryptedFile := "test.dec"
// 创建测试文件
if err := os.WriteFile(inputFile, []byte("这是一个需要加密的秘密文件"), 0644); err != nil {
fmt.Printf("创建测试文件错误: %v\n", err)
return
}
// 加密文件
fmt.Println("正在加密文件...")
if err := encryptFile(inputFile, encryptedFile, key); err != nil {
fmt.Printf("加密错误: %v\n", err)
return
}
// 解密文件
fmt.Println("正在解密文件...")
if err := decryptFile(encryptedFile, decryptedFile, key); err != nil {
fmt.Printf("解密错误: %v\n", err)
return
}
fmt.Println("操作完成!")
fmt.Println("原始文件:", inputFile)
fmt.Println("加密文件:", encryptedFile)
fmt.Println("解密文件:", decryptedFile)
fmt.Println("加密密钥已保存到: secret.key")
}
配置文件管理工具
go
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
type Config struct {
Server struct {
Host string `json:"host"`
Port int `json:"port"`
} `json:"server"`
Database struct {
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
} `json:"database"`
Logging struct {
Level string `json:"level"`
FilePath string `json:"file_path"`
} `json:"logging"`
}
func loadConfig(configPath string) (*Config, error) {
// 获取绝对路径
absPath, err := filepath.Abs(configPath)
if err != nil {
return nil, err
}
// 读取文件
data, err := os.ReadFile(absPath)
if err != nil {
return nil, err
}
// 解析JSON
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
func saveConfig(configPath string, config *Config) error {
// 序列化为JSON
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
// 写入文件
return os.WriteFile(configPath, data, 0644)
}
func createDefaultConfig() *Config {
var config Config
// 设置默认服务器配置
config.Server.Host = "localhost"
config.Server.Port = 8080
// 设置默认数据库配置
config.Database.Username = "admin"
config.Database.Password = "password"
config.Database.Name = "appdb"
// 设置默认日志配置
config.Logging.Level = "info"
config.Logging.FilePath = "./app.log"
return &config
}
func main() {
configFile := "config.json"
// 检查配置文件是否存在
if _, err := os.Stat(configFile); os.IsNotExist(err) {
fmt.Println("配置文件不存在,创建默认配置...")
defaultConfig := createDefaultConfig()
if err := saveConfig(configFile, defaultConfig); err != nil {
fmt.Printf("创建配置文件错误: %v\n", err)
return
}
fmt.Println("默认配置文件已创建:", configFile)
}
// 加载配置文件
config, err := loadConfig(configFile)
if err != nil {
fmt.Printf("加载配置文件错误: %v\n", err)
return
}
// 打印配置信息
fmt.Println("当前配置:")
fmt.Printf("服务器: %s:%d\n", config.Server.Host, config.Server.Port)
fmt.Printf("数据库: %s@%s/%s\n", config.Database.Username, "******", config.Database.Name)
fmt.Printf("日志级别: %s, 路径: %s\n", config.Logging.Level, config.Logging.FilePath)
// 修改配置示例
fmt.Println("\n修改服务器端口为 9090...")
config.Server.Port = 9090
if err := saveConfig(configFile, config); err != nil {
fmt.Printf("保存配置错误: %v\n", err)
return
}
fmt.Println("配置已更新!")
}
文件系统使用统计工具
go
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
type FileStats struct {
TotalFiles int
TotalSize int64
ByExt map[string]struct {
Count int
Size int64
}
mu sync.Mutex
}
func NewFileStats() *FileStats {
return &FileStats{
ByExt: make(map[string]struct {
Count int
Size int64
}),
}
}
func (fs *FileStats) AddFile(path string, info os.FileInfo) {
fs.mu.Lock()
defer fs.mu.Unlock()
ext := filepath.Ext(path)
if ext == "" {
ext = "无扩展名"
}
entry := fs.ByExt[ext]
entry.Count++
entry.Size += info.Size()
fs.ByExt[ext] = entry
fs.TotalFiles++
fs.TotalSize += info.Size()
}
func (fs *FileStats) Print() {
fmt.Printf("总文件数: %d\n", fs.TotalFiles)
fmt.Printf("总大小: %.2f MB\n", float64(fs.TotalSize)/(1024*1024))
fmt.Println("\n按扩展名统计:")
for ext, stat := range fs.ByExt {
fmt.Printf("%s: %d 个文件, %.2f MB\n",
ext,
stat.Count,
float64(stat.Size)/(1024*1024))
}
}
func walkDir(dir string, fileStats *FileStats, wg *sync.WaitGroup) {
defer wg.Done()
entries, err := os.ReadDir(dir)
if err != nil {
return
}
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
if entry.IsDir() {
wg.Add(1)
go walkDir(fullPath, fileStats, wg)
} else {
info, err := entry.Info()
if err == nil {
fileStats.AddFile(fullPath, info)
}
}
}
}
func main() {
if len(os.Args) < 2 {
fmt.Println("用法: filestats <目录>")
return
}
rootDir := os.Args[1]
startTime := time.Now()
fileStats := NewFileStats()
var wg sync.WaitGroup
wg.Add(1)
go walkDir(rootDir, fileStats, &wg)
wg.Wait()
fmt.Printf("\n扫描完成, 耗时: %.2f 秒\n", time.Since(startTime).Seconds())
fileStats.Print()
}
文件内容替换工具
go
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
type ReplaceTask struct {
FilePath string
OldText string
NewText string
}
func replaceInFile(task ReplaceTask) error {
// 读取文件内容
content, err := os.ReadFile(task.FilePath)
if err != nil {
return err
}
// 替换内容
newContent := strings.ReplaceAll(string(content), task.OldText, task.NewText)
// 如果内容没有变化,直接返回
if string(content) == newContent {
return nil
}
// 创建备份文件
backupPath := task.FilePath + ".bak"
if err := os.WriteFile(backupPath, content, 0644); err != nil {
return err
}
// 写入新内容
if err := os.WriteFile(task.FilePath, []byte(newContent), 0644); err != nil {
// 如果出错,尝试恢复备份
os.WriteFile(task.FilePath, content, 0644)
os.Remove(backupPath)
return err
}
return nil
}
func processFiles(rootDir, oldText, newText string) error {
var wg sync.WaitGroup
taskChan := make(chan ReplaceTask, 100)
errorChan := make(chan error, 10)
// 启动工作池
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range taskChan {
if err := replaceInFile(task); err != nil {
errorChan <- fmt.Errorf("%s: %v", task.FilePath, err)
}
}
}()
}
// 遍历目录并发送任务
go func() {
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 跳过所有以点号开头的文件和目录
if strings.HasPrefix(info.Name(), ".") {
if info.IsDir() {
return filepath.SkipDir // 如果是目录,跳过整个目录
}
return nil // 如果是文件,跳过这个文件
}
if !info.IsDir() && !strings.HasSuffix(path, ".bak") {
taskChan <- ReplaceTask{
FilePath: path,
OldText: oldText,
NewText: newText,
}
}
return nil
})
if err != nil {
errorChan <- err
}
close(taskChan)
}()
// 等待所有工作完成
go func() {
wg.Wait()
close(errorChan)
}()
// 收集错误
var errors []string
for err := range errorChan {
errors = append(errors, err.Error())
}
if len(errors) > 0 {
return fmt.Errorf("遇到 %d 个错误:\n%s", len(errors), strings.Join(errors, "\n"))
}
return nil
}
func main() {
if len(os.Args) != 4 {
fmt.Println("用法: filereplace <目录> <旧文本> <新文本>")
fmt.Println("示例: filereplace ./src \"旧版本\" \"新版本\"")
return
}
rootDir := os.Args[1]
oldText := os.Args[2]
newText := os.Args[3]
fmt.Printf("将在目录 %s 中替换 \"%s\" 为 \"%s\"\n", rootDir, oldText, newText)
fmt.Print("确定要继续吗? (y/n): ")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if strings.ToLower(scanner.Text()) != "y" {
fmt.Println("操作已取消")
return
}
if err := processFiles(rootDir, oldText, newText); err != nil {
fmt.Printf("替换过程中出错: %v\n", err)
} else {
fmt.Println("替换完成!")
}
}
文件同步工具(单向同步)
go
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
)
type SyncFile struct {
Path string
Size int64
ModTime time.Time
}
func collectFiles(dir string) (map[string]SyncFile, error) {
files := make(map[string]SyncFile)
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
relPath, err := filepath.Rel(dir, path)
if err != nil {
return err
}
files[relPath] = SyncFile{
Path: path,
Size: info.Size(),
ModTime: info.ModTime(),
}
}
return nil
})
return files, err
}
func syncFiles(sourceDir, targetDir string, dryRun bool) error {
// 收集源文件和目标文件
sourceFiles, err := collectFiles(sourceDir)
if err != nil {
return fmt.Errorf("收集源文件错误: %v", err)
}
targetFiles, err := collectFiles(targetDir)
if err != nil {
return fmt.Errorf("收集目标文件错误: %v", err)
}
// 创建目标目录中不存在的子目录
for relPath := range sourceFiles {
dir := filepath.Dir(relPath)
if dir != "." {
targetSubDir := filepath.Join(targetDir, dir)
if _, err := os.Stat(targetSubDir); os.IsNotExist(err) {
if !dryRun {
if err := os.MkdirAll(targetSubDir, 0755); err != nil {
return fmt.Errorf("创建目录错误: %v", err)
}
}
fmt.Printf("创建目录: %s\n", targetSubDir)
}
}
}
// 比较并同步文件
for relPath, sourceFile := range sourceFiles {
targetFile, exists := targetFiles[relPath]
targetPath := filepath.Join(targetDir, relPath)
if !exists {
// 文件在目标目录中不存在,需要复制
fmt.Printf("新增文件: %s\n", relPath)
if !dryRun {
if err := copyFile(sourceFile.Path, targetPath); err != nil {
return fmt.Errorf("复制文件错误: %v", err)
}
}
} else {
// 文件已存在,检查是否需要更新
if sourceFile.ModTime.After(targetFile.ModTime) || sourceFile.Size != targetFile.Size {
fmt.Printf("更新文件: %s (源: %s, 目标: %s)\n",
relPath,
sourceFile.ModTime.Format("2006-01-02 15:04:05"),
targetFile.ModTime.Format("2006-01-02 15:04:05"))
if !dryRun {
if err := copyFile(sourceFile.Path, targetPath); err != nil {
return fmt.Errorf("复制文件错误: %v", err)
}
}
}
}
}
// 检查目标目录中有而源目录中没有的文件
for relPath := range targetFiles {
if _, exists := sourceFiles[relPath]; !exists {
targetPath := filepath.Join(targetDir, relPath)
fmt.Printf("删除文件: %s\n", relPath)
if !dryRun {
if err := os.Remove(targetPath); err != nil {
return fmt.Errorf("删除文件错误: %v", err)
}
}
}
}
return nil
}
func copyFile(src, dst string) error {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return err
}
// 同步文件权限和修改时间
sourceInfo, err := os.Stat(src)
if err != nil {
return err
}
if err := os.Chmod(dst, sourceInfo.Mode()); err != nil {
return err
}
if err := os.Chtimes(dst, sourceInfo.ModTime(), sourceInfo.ModTime()); err != nil {
return err
}
return nil
}
func ensureDir(dirPath string) error {
// MkdirAll 会创建所有不存在的父目录
err := os.MkdirAll(dirPath, 0755)
if err != nil {
return fmt.Errorf("创建目录失败: %v", err)
}
return nil
}
func main() {
if len(os.Args) < 3 {
fmt.Println("用法: filesync <源目录> <目标目录> [--dry-run]")
return
}
sourceDir := os.Args[1]
targetDir := os.Args[2]
dryRun := len(os.Args) > 3 && os.Args[3] == "--dry-run"
if dryRun {
fmt.Println("!!! 干运行模式,不会实际修改文件 !!!")
}
// 如果目标目录不存在 则创建
if err := ensureDir(targetDir); err != nil {
fmt.Println("目标目录创建失败")
return
}
fmt.Printf("同步 %s -> %s\n", sourceDir, targetDir)
if err := syncFiles(sourceDir, targetDir, dryRun); err != nil {
fmt.Printf("同步错误: %v\n", err)
} else {
fmt.Println("同步完成!")
}
}
文件监控
go
package main
import (
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"github.com/fsnotify/fsnotify"
)
func watchDir(path string) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("创建监控器失败: %v", err)
}
defer watcher.Close()
// 使用 context 或信号来控制退出
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// 递归添加监控
err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
if err := watcher.Add(path); err != nil {
return fmt.Errorf("添加监控失败: %s: %v", path, err)
}
log.Printf("正在监控目录: %s", path)
}
return nil
})
if err != nil {
return fmt.Errorf("遍历目录失败: %v", err)
}
// 事件处理循环
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return nil // 通道关闭
}
switch {
case event.Op&fsnotify.Write == fsnotify.Write:
log.Printf("文件修改: %s", event.Name)
case event.Op&fsnotify.Create == fsnotify.Create:
log.Printf("文件创建: %s", event.Name)
// 如果是新目录,自动添加监控
if info, err := os.Stat(event.Name); err == nil && info.IsDir() {
if err := watcher.Add(event.Name); err != nil {
log.Printf("添加新目录监控失败: %s: %v", event.Name, err)
}
}
case event.Op&fsnotify.Remove == fsnotify.Remove:
log.Printf("文件删除: %s", event.Name)
case event.Op&fsnotify.Rename == fsnotify.Rename:
log.Printf("文件重命名: %s", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return nil
}
log.Printf("监控错误: %v", err)
case <-quit:
log.Println("收到退出信号,停止监控")
return nil
}
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println("用法: filewatch <目录路径>")
os.Exit(1)
}
path := os.Args[1]
if _, err := os.Stat(path); os.IsNotExist(err) {
log.Fatalf("目录不存在: %s", path)
}
log.Printf("开始监控目录: %s", path)
if err := watchDir(path); err != nil {
log.Fatal(err)
}
}
文件系统监控与自动备份
go
package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"io"
"log"
"os"
"path/filepath"
)
type BackupManager struct {
SourceDir string
BackupDir string
Watcher *fsnotify.Watcher
}
func NewBackupManager(source, backup string) (*BackupManager, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
// 确保备份目录存在
if err := os.MkdirAll(backup, 0755); err != nil {
return nil, err
}
return &BackupManager{
SourceDir: source,
BackupDir: backup,
Watcher: watcher,
}, nil
}
func (bm *BackupManager) Start() error {
// 初始完整备份
if err := bm.fullBackup(); err != nil {
return err
}
// 启动监控
if err := bm.Watcher.Add(bm.SourceDir); err != nil {
return err
}
go bm.watchChanges()
return nil
}
func (bm *BackupManager) fullBackup() error {
return filepath.Walk(bm.SourceDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(bm.SourceDir, path)
if err != nil {
return err
}
backupPath := filepath.Join(bm.BackupDir, relPath)
if info.IsDir() {
return os.MkdirAll(backupPath, info.Mode())
}
return bm.backupFile(path, backupPath)
})
}
func (bm *BackupManager) backupFile(source, dest string) error {
srcFile, err := os.Open(source)
if err != nil {
return err
}
defer srcFile.Close()
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return err
}
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
return err
}
func (bm *BackupManager) watchChanges() {
for {
select {
case event, ok := <-bm.Watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
relPath, err := filepath.Rel(bm.SourceDir, event.Name)
if err != nil {
log.Printf("获取相对路径错误: %v", err)
continue
}
backupPath := filepath.Join(bm.BackupDir, relPath)
if err := bm.backupFile(event.Name, backupPath); err != nil {
log.Printf("备份文件 %s 错误: %v", event.Name, err)
} else {
log.Printf("已备份: %s -> %s", event.Name, backupPath)
}
}
case err, ok := <-bm.Watcher.Errors:
if !ok {
return
}
log.Printf("监控错误: %v", err)
}
}
}
func (bm *BackupManager) Stop() {
bm.Watcher.Close()
}
func main() {
if len(os.Args) != 3 {
fmt.Println("用法: autobackup <源目录> <备份目录>")
return
}
sourceDir := os.Args[1]
backupDir := os.Args[2]
backupManager, err := NewBackupManager(sourceDir, backupDir)
if err != nil {
log.Fatalf("创建备份管理器失败: %v", err)
}
fmt.Printf("启动自动备份服务,源目录: %s, 备份目录: %s\n", sourceDir, backupDir)
fmt.Println("按Ctrl+C停止服务...")
if err := backupManager.Start(); err != nil {
log.Fatalf("启动备份失败: %v", err)
}
// 保持程序运行
select {}
}
压缩与解压缩
go
package main
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
)
func compressFiles(zipName string, paths []string) error {
zipFile, err := os.Create(zipName)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
for _, path := range paths {
err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 创建文件头信息
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// 设置压缩方法
header.Method = zip.Deflate
// 调整路径,去掉最前面的路径部分
relPath, err := filepath.Rel(filepath.Dir(path), filePath)
if err != nil {
return err
}
header.Name = relPath
// 如果是目录,只需要创建目录条目
if info.IsDir() {
header.Name += "/" // 确保目录名以/结尾
_, err = zipWriter.CreateHeader(header)
return err
}
// 普通文件
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
// 打开并写入文件内容
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
})
if err != nil {
return err
}
}
return nil
}
func decompressZip(zipName, destDir string) error {
reader, err := zip.OpenReader(zipName)
if err != nil {
return err
}
defer reader.Close()
for _, file := range reader.File {
path := filepath.Join(destDir, file.Name)
if file.FileInfo().IsDir() {
os.MkdirAll(path, file.Mode())
continue
}
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return err
}
defer outFile.Close()
zipFile, err := file.Open()
if err != nil {
return err
}
defer zipFile.Close()
if _, err := io.Copy(outFile, zipFile); err != nil {
return err
}
}
return nil
}
func main() {
// 压缩示例
filesToCompress := []string{"file1.md", "file2.md", "gorm"}
err := compressFiles("archive.zip", filesToCompress)
if err != nil {
fmt.Println("压缩失败:", err)
} else {
fmt.Println("文件压缩成功")
}
// 解压示例
err = decompressZip("archive.zip", "extracted")
if err != nil {
fmt.Println("解压失败:", err)
} else {
fmt.Println("文件解压成功")
}
}
文件差异比较工具
go
package main
import (
"bufio"
"fmt"
"os"
)
// FileDiff 表示文件差异的结构体
type FileDiff struct {
LineNum int // 行号
Left string // 第一个文件的内容
Right string // 第二个文件的内容
}
// compareFiles 比较两个文件的内容差异
func compareFiles(file1, file2 string) ([]FileDiff, error) {
// 同时打开两个文件
f1, err := os.Open(file1)
if err != nil {
return nil, fmt.Errorf("打开文件 %s 失败: %w", file1, err)
}
defer f1.Close()
f2, err := os.Open(file2)
if err != nil {
return nil, fmt.Errorf("打开文件 %s 失败: %w", file2, err)
}
defer f2.Close()
var diffs []FileDiff
scanner1 := bufio.NewScanner(f1)
scanner2 := bufio.NewScanner(f2)
lineNum := 1
// 使用更简洁的循环控制
for {
hasLine1 := scanner1.Scan()
hasLine2 := scanner2.Scan()
// 两个文件都读取完毕时退出循环
if !hasLine1 && !hasLine2 {
break
}
text1 := ""
if hasLine1 {
text1 = scanner1.Text()
}
text2 := ""
if hasLine2 {
text2 = scanner2.Text()
}
if text1 != text2 {
diffs = append(diffs, FileDiff{
LineNum: lineNum,
Left: text1,
Right: text2,
})
}
lineNum++
}
// 检查扫描错误
if err := scanner1.Err(); err != nil {
return nil, fmt.Errorf("读取文件 %s 失败: %w", file1, err)
}
if err := scanner2.Err(); err != nil {
return nil, fmt.Errorf("读取文件 %s 失败: %w", file2, err)
}
return diffs, nil
}
// printDiff 打印文件差异
func printDiff(diffs []FileDiff) {
if len(diffs) == 0 {
fmt.Println("文件内容相同")
return
}
fmt.Printf("发现 %d 处差异:\n", len(diffs))
for _, diff := range diffs {
fmt.Printf("行号 %d:\n", diff.LineNum)
if diff.Left != "" {
fmt.Printf("< %s\n", diff.Left)
} else {
fmt.Println("< (空行或文件结束)")
}
if diff.Right != "" {
fmt.Printf("> %s\n", diff.Right)
} else {
fmt.Println("> (空行或文件结束)")
}
fmt.Println("---")
}
}
func main() {
if len(os.Args) != 3 {
fmt.Printf("用法: %s <文件1> <文件2>\n", os.Args[0])
os.Exit(1)
}
file1 := os.Args[1]
file2 := os.Args[2]
diffs, err := compareFiles(file1, file2)
if err != nil {
fmt.Printf("错误: %v\n", err)
os.Exit(1)
}
printDiff(diffs)
}
文件搜索工具
go
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
type SearchResult struct {
Path string
Error error
}
func searchInFile(path string, searchTerm string, results chan<- SearchResult, wg *sync.WaitGroup) {
defer wg.Done()
content, err := os.ReadFile(path)
if err != nil {
results <- SearchResult{Path: path, Error: err}
return
}
if strings.Contains(string(content), searchTerm) {
results <- SearchResult{Path: path, Error: nil}
}
}
func fileSearch(rootDir string, searchTerm string) {
var wg sync.WaitGroup
results := make(chan SearchResult, 100)
// 遍历目录并启动搜索goroutine
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && !strings.HasPrefix(filepath.Base(path), ".") {
wg.Add(1)
go searchInFile(path, searchTerm, results, &wg)
}
return nil
})
if err != nil {
fmt.Printf("遍历目录错误: %v\n", err)
return
}
// 等待所有goroutine完成
go func() {
wg.Wait()
close(results)
}()
// 打印结果
fmt.Printf("在 '%s' 中搜索 '%s' 的结果:\n", rootDir, searchTerm)
found := false
for result := range results {
if result.Error != nil {
fmt.Printf("%s: 搜索错误: %v\n", result.Path, result.Error)
} else {
fmt.Println(result.Path)
found = true
}
}
if !found {
fmt.Println("未找到匹配的文件")
}
}
func main() {
if len(os.Args) != 3 {
fmt.Println("用法: filesearch <目录> <搜索词>")
return
}
rootDir := os.Args[1]
searchTerm := os.Args[2]
fileSearch(rootDir, searchTerm)
}
大文件分割与合并
go
package main
import (
"fmt"
"io"
"os"
"path/filepath"
)
const chunkSize = 5 * 1024 * 1024 // 5MB
func splitFile(sourceFile string) error {
file, err := os.Open(sourceFile)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
fileSize := fileInfo.Size()
totalParts := int(fileSize/chunkSize) + 1
buffer := make([]byte, chunkSize)
for i := 0; i < totalParts; i++ {
partFileName := fmt.Sprintf("%s.part%d", sourceFile, i)
partFile, err := os.Create(partFileName)
if err != nil {
return err
}
bytesRead, err := file.Read(buffer)
if err != nil && err != io.EOF {
partFile.Close()
return err
}
_, err = partFile.Write(buffer[:bytesRead])
partFile.Close()
if err != nil {
return err
}
fmt.Printf("分割完成: %s (%.2f%%)\n", partFileName, float64(i+1)*100/float64(totalParts))
}
return nil
}
func mergeFiles(originalName string) error {
dir := filepath.Dir(originalName)
base := filepath.Base(originalName)
destFile, err := os.Create(originalName)
if err != nil {
return err
}
defer destFile.Close()
i := 0
for {
partFileName := filepath.Join(dir, fmt.Sprintf("%s.part%d", base, i))
partFile, err := os.Open(partFileName)
if err != nil {
if os.IsNotExist(err) {
break
}
return err
}
_, err = io.Copy(destFile, partFile)
partFile.Close()
if err != nil {
return err
}
fmt.Printf("合并完成: %s\n", partFileName)
i++
}
return nil
}
func cleanupParts(originalName string) error {
dir := filepath.Dir(originalName)
base := filepath.Base(originalName)
i := 0
for {
partFileName := filepath.Join(dir, fmt.Sprintf("%s.part%d", base, i))
if _, err := os.Stat(partFileName); os.IsNotExist(err) {
break
}
if err := os.Remove(partFileName); err != nil {
return err
}
i++
}
return nil
}
func main() {
if len(os.Args) < 3 {
fmt.Println("用法:")
fmt.Println("分割文件: fileparts split <文件名>")
fmt.Println("合并文件: fileparts merge <文件名>")
fmt.Println("清理碎片: fileparts cleanup <文件名>")
return
}
command := os.Args[1]
filename := os.Args[2]
switch command {
case "split":
fmt.Printf("开始分割文件: %s\n", filename)
if err := splitFile(filename); err != nil {
fmt.Printf("分割文件错误: %v\n", err)
} else {
fmt.Println("文件分割完成")
}
case "merge":
fmt.Printf("开始合并文件: %s\n", filename)
if err := mergeFiles(filename); err != nil {
fmt.Printf("合并文件错误: %v\n", err)
} else {
fmt.Println("文件合并完成")
}
case "cleanup":
fmt.Printf("开始清理碎片文件: %s\n", filename)
if err := cleanupParts(filename); err != nil {
fmt.Printf("清理碎片错误: %v\n", err)
} else {
fmt.Println("碎片清理完成")
}
default:
fmt.Println("未知命令")
}
}
日志文件轮转工具
go
package main
import (
"fmt"
"os"
"time"
)
type LogRotator struct {
FilePath string
MaxSize int64 // 最大文件大小(字节)
BackupCount int // 保留的备份文件数量
}
// Rotate 执行日志轮转
func (lr *LogRotator) Rotate() error {
// 检查文件是否存在及大小
info, err := os.Stat(lr.FilePath)
if os.IsNotExist(err) {
return nil // 文件不存在,无需轮转
}
if err != nil {
return fmt.Errorf("获取文件信息失败: %w", err)
}
// 检查文件大小是否达到轮转阈值
if info.Size() < lr.MaxSize {
return nil
}
// 1. 先删除最旧的备份文件(如果存在)
if lr.BackupCount > 0 {
oldestBackup := lr.backupFileName(lr.BackupCount)
if _, err := os.Stat(oldestBackup); err == nil {
if err := os.Remove(oldestBackup); err != nil {
return fmt.Errorf("删除旧备份文件失败: %w", err)
}
}
}
// 2. 依次将备份文件向后移动一位
for i := lr.BackupCount - 1; i >= 1; i-- {
oldName := lr.backupFileName(i)
newName := lr.backupFileName(i + 1)
if _, err := os.Stat(oldName); err == nil {
if err := os.Rename(oldName, newName); err != nil {
return fmt.Errorf("重命名备份文件失败: %w", err)
}
}
}
// 3. 将当前日志文件重命名为第一个备份
if err := os.Rename(lr.FilePath, lr.backupFileName(1)); err != nil {
return fmt.Errorf("重命名当前日志文件失败: %w", err)
}
return nil
}
// backupFileName 生成备份文件名
// index: 备份序号,从1开始(1表示最新备份)
func (lr *LogRotator) backupFileName(index int) string {
return fmt.Sprintf("%s.%d", lr.FilePath, index)
}
// Write 写入日志并处理轮转
func (lr *LogRotator) Write(p []byte) (n int, err error) {
if err := lr.Rotate(); err != nil {
return 0, fmt.Errorf("日志轮转失败: %w", err)
}
file, err := os.OpenFile(lr.FilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return 0, fmt.Errorf("打开日志文件失败: %w", err)
}
defer file.Close()
return file.Write(p)
}
func main() {
// 创建日志轮转器
rotator := &LogRotator{
FilePath: "app.log",
MaxSize: 1024, // 1KB 测试用
BackupCount: 3, // 保留3个备份文件
}
// 模拟日志写入
for i := 0; i < 100; i++ {
logEntry := fmt.Sprintf("%s - Log entry %d\n", time.Now().Format(time.RFC3339), i)
if _, err := rotator.Write([]byte(logEntry)); err != nil {
fmt.Printf("写入日志错误: %v\n", err)
}
time.Sleep(100 * time.Millisecond) // 添加延迟以便观察
}
fmt.Println("日志轮转完成,检查当前目录下的 app.log* 文件")
}
并发文件哈希计算
go
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"sync"
)
func calculateFileHash(path string, wg *sync.WaitGroup, results chan<- string) {
defer wg.Done()
file, err := os.Open(path)
if err != nil {
results <- fmt.Sprintf("%s: 打开文件错误: %v", path, err)
return
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
results <- fmt.Sprintf("%s: 计算哈希错误: %v", path, err)
return
}
hashInBytes := hash.Sum(nil)
results <- fmt.Sprintf("%s: %s", path, hex.EncodeToString(hashInBytes))
}
func main() {
if len(os.Args) < 2 {
fmt.Println("用法: filehash <目录路径>")
return
}
root := os.Args[1]
var wg sync.WaitGroup
results := make(chan string, 10)
// 遍历目录并启动 goroutine 计算文件哈希
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
wg.Add(1)
go calculateFileHash(path, &wg, results)
}
return nil
})
if err != nil {
fmt.Printf("遍历目录错误: %v\n", err)
return
}
// 等待所有goroutine完成
go func() {
wg.Wait()
close(results)
}()
// 打印结果
for result := range results {
fmt.Println(result)
}
}
文件上传服务(模拟)
go
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
)
const uploadDir = "./uploads"
const maxUploadSize = 10 * 1024 * 1024 // 10MB
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 解析multipart表单
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
http.Error(w, "文件太大", http.StatusBadRequest)
return
}
// 获取上传的文件
file, handler, err := r.FormFile("uploadFile")
if err != nil {
http.Error(w, "读取文件错误", http.StatusBadRequest)
return
}
defer file.Close()
// 创建上传目录
if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil {
http.Error(w, "服务器错误", http.StatusInternalServerError)
return
}
// 创建目标文件
dstPath := filepath.Join(uploadDir, handler.Filename)
dst, err := os.Create(dstPath)
if err != nil {
http.Error(w, "服务器错误", http.StatusInternalServerError)
return
}
defer dst.Close()
// 复制文件内容
if _, err := io.Copy(dst, file); err != nil {
http.Error(w, "写入文件错误", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "文件上传成功: %s (%d bytes)", handler.Filename, handler.Size)
}
func listFilesHandler(w http.ResponseWriter, r *http.Request) {
files, err := os.ReadDir(uploadDir)
if err != nil {
http.Error(w, "服务器错误", http.StatusInternalServerError)
return
}
fmt.Fprintln(w, "已上传的文件:")
for _, file := range files {
fmt.Fprintf(w, "- %s\n", file.Name())
}
}
func main() {
// 设置路由
http.HandleFunc("/upload", uploadHandler)
http.HandleFunc("/list", listFilesHandler)
// 创建上传目录
if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil {
fmt.Printf("创建上传目录错误: %v\n", err)
return
}
fmt.Println("文件上传服务已启动,访问 http://localhost:8083")
fmt.Println("使用 /upload 端点上传文件")
fmt.Println("使用 /list 端点查看已上传文件")
// 启动服务器
if err := http.ListenAndServe(":8083", nil); err != nil {
fmt.Printf("服务器错误: %v\n", err)
}
}