Go 文件处理入门:读写文件、目录遍历和路径操作

Go 标准库已经提供了完整工具。新手最常用的是这几个包:

复制代码
os
io
bufio
path/filepath
errors

这篇文章会从最常见的场景开始,一步一步讲清楚 Go 文件处理。

先记住几个核心包

os

os 包负责和操作系统交互。

常用能力包括:

  • 读文件:os.ReadFile
  • 写文件:os.WriteFile
  • 打开文件:os.Openos.OpenFile
  • 创建文件:os.Create
  • 创建目录:os.Mkdiros.MkdirAll
  • 删除文件:os.Remove
  • 删除目录树:os.RemoveAll
  • 查看文件信息:os.Stat
  • 读取目录:os.ReadDir
  • 创建临时文件/目录:os.CreateTempos.MkdirTemp

io

io 包处理通用输入输出。

最重要的概念是:

复制代码
io.Reader
io.Writer

很多东西都可以是 ReaderWriter

  • 文件
  • 网络连接
  • 字符串读取器
  • bytes.Buffer
  • HTTP 响应体

比如复制文件时常用:

复制代码
io.Copy(dst, src)

bufio

bufio 包提供带缓冲的读写。

常见用途:

  • 按行读取文件:bufio.Scanner
  • 更高效地写入多行内容:bufio.Writer

path/filepath

path/filepath 包负责文件路径处理。

常用能力:

  • 拼接路径:filepath.Join
  • 获取文件名:filepath.Base
  • 获取目录:filepath.Dir
  • 获取扩展名:filepath.Ext
  • 清理路径:filepath.Clean
  • 遍历目录树:filepath.WalkDir

注意:

复制代码
处理本地文件路径时,优先用 path/filepath。
处理 URL 路径时,才更多使用 path。

文件处理的基本原则

新手可以先记住四条:

  1. 小文件可以一次性读写。
  2. 大文件不要一次性全部读进内存。
  3. 打开的文件要关闭。
  4. 错误要处理,不要随手丢掉。

比如:

复制代码
file, err := os.Open("data.txt")
if err != nil {
	return err
}
defer file.Close()

defer file.Close() 的意思是:函数返回前关闭文件。

这样无论后面成功还是出错,文件资源都会被释放。

一、读取整个文件:os.ReadFile

如果文件不大,最简单的读取方式是 os.ReadFile

它会一次性把整个文件读到内存里,返回 []byte

完整例子:

复制代码
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "hello.txt")

	err = os.WriteFile(path, []byte("hello go\n"), 0644)
	if err != nil {
		fmt.Println("write file:", err)
		return
	}

	data, err := os.ReadFile(path)
	if err != nil {
		fmt.Println("read file:", err)
		return
	}

	fmt.Print(string(data))
}

输出:

复制代码
hello go

这里有几个点:

复制代码
data, err := os.ReadFile(path)

data 的类型是 []byte

如果你确定文件内容是文本,可以转成字符串:

复制代码
text := string(data)

如果文件是图片、压缩包、音频等二进制文件,就不要随便转字符串,直接处理 []byte

二、写入整个文件:os.WriteFile

写小文件可以用 os.WriteFile

复制代码
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "note.txt")

	content := []byte("first line\nsecond line\n")

	err = os.WriteFile(path, content, 0644)
	if err != nil {
		fmt.Println("write file:", err)
		return
	}

	data, err := os.ReadFile(path)
	if err != nil {
		fmt.Println("read file:", err)
		return
	}

	fmt.Print(string(data))
}

输出:

复制代码
first line
second line

第三个参数 0644 是文件权限。

可以先这样理解:

复制代码
0644:文件拥有者可读写,其他人只读
0600:只有文件拥有者可读写
0755:常用于目录或可执行文件,拥有者可读写执行,其他人可读执行

注意:

复制代码
权限参数只在创建文件时有意义。
如果文件已经存在,os.WriteFile 会截断原文件内容,再写入新内容。

也就是说,下面这行不是追加,而是覆盖:

复制代码
os.WriteFile(path, []byte("new content"), 0644)

三、追加内容:os.OpenFile

如果你想在文件末尾追加内容,不要用 os.WriteFile

要用 os.OpenFile 搭配标志位。

复制代码
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func appendLine(path string, line string) error {
	file, err := os.OpenFile(
		path,
		os.O_CREATE|os.O_WRONLY|os.O_APPEND,
		0644,
	)
	if err != nil {
		return fmt.Errorf("open file for append: %w", err)
	}
	defer file.Close()

	if _, err := file.WriteString(line + "\n"); err != nil {
		return fmt.Errorf("append line: %w", err)
	}

	return nil
}

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "app.log")

	if err := appendLine(path, "start"); err != nil {
		fmt.Println(err)
		return
	}

	if err := appendLine(path, "finish"); err != nil {
		fmt.Println(err)
		return
	}

	data, err := os.ReadFile(path)
	if err != nil {
		fmt.Println("read file:", err)
		return
	}

	fmt.Print(string(data))
}

输出:

复制代码
start
finish

这里的标志位含义是:

复制代码
os.O_CREATE // 文件不存在就创建
os.O_WRONLY // 只写
os.O_APPEND // 写入时追加到文件末尾

多个标志位用 | 组合:

复制代码
os.O_CREATE | os.O_WRONLY | os.O_APPEND

常见标志位还有:

复制代码
os.O_RDONLY // 只读
os.O_RDWR   // 读写
os.O_TRUNC  // 打开时清空文件
os.O_EXCL   // 和 O_CREATE 一起用,要求文件必须不存在

五、分块读取大文件

如果文件很大,不适合用 os.ReadFile 一次性读入内存。

这时可以用 file.Read 分块读取。

复制代码
package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
)

func readByChunks(path string) error {
	file, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("open file: %w", err)
	}
	defer file.Close()

	buffer := make([]byte, 5)

	for {
		n, err := file.Read(buffer)
		if n > 0 {
			fmt.Printf("chunk: %q\n", buffer[:n])
		}

		if err == io.EOF {
			break
		}

		if err != nil {
			return fmt.Errorf("read file: %w", err)
		}
	}

	return nil
}

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "data.txt")
	err = os.WriteFile(path, []byte("hello gopher"), 0644)
	if err != nil {
		fmt.Println("write file:", err)
		return
	}

	if err := readByChunks(path); err != nil {
		fmt.Println(err)
	}
}

输出:

复制代码
chunk: "hello"
chunk: " goph"
chunk: "er"

这里要注意 io.EOF

io.EOF 表示已经读到文件末尾。

它不是坏事,而是告诉你:

复制代码
没有更多数据了。

所以常见模式是:

复制代码
if err == io.EOF {
	break
}

if err != nil {
	return err
}

六、逐行读取文件:bufio.Scanner

处理日志、CSV、配置文件时,经常要按行读取。

这时可以用 bufio.Scanner

复制代码
package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
)

func printLines(path string) error {
	file, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("open file: %w", err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)

	lineNumber := 1
	for scanner.Scan() {
		fmt.Printf("%d: %s\n", lineNumber, scanner.Text())
		lineNumber++
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("scan file: %w", err)
	}

	return nil
}

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "names.txt")
	err = os.WriteFile(path, []byte("Alice\nBob\nCharlie\n"), 0644)
	if err != nil {
		fmt.Println("write file:", err)
		return
	}

	if err := printLines(path); err != nil {
		fmt.Println(err)
	}
}

输出:

复制代码
1: Alice
2: Bob
3: Charlie

scanner.Scan() 每次读取一行。

scanner.Text() 获取当前行的文本。

循环结束后要检查:

复制代码
scanner.Err()

因为循环结束可能有两种原因:

复制代码
正常读完
读取过程中出错

scanner.Err() 可以帮你区分。

Scanner 的限制

bufio.Scanner 很适合读取普通文本行。

但它有默认 token 大小限制。如果一行特别长,比如超长 JSON、超长日志、超长 base64,可能需要调整缓冲区,或者改用 bufio.Reader

新手阶段先记住:

复制代码
普通按行读取:bufio.Scanner 很方便。
超长行或复杂分隔:考虑 bufio.Reader。

七、带缓冲写入:bufio.Writer

如果你要写很多小片段,直接多次写文件可能不够高效。

可以使用 bufio.Writer 先写到缓冲区,最后 Flush

复制代码
package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
)

func writeLines(path string, lines []string) error {
	file, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("create file: %w", err)
	}
	defer file.Close()

	writer := bufio.NewWriter(file)

	for _, line := range lines {
		if _, err := writer.WriteString(line + "\n"); err != nil {
			return fmt.Errorf("write line: %w", err)
		}
	}

	if err := writer.Flush(); err != nil {
		return fmt.Errorf("flush writer: %w", err)
	}

	return nil
}

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "tasks.txt")

	err = writeLines(path, []string{"learn go", "write code", "read docs"})
	if err != nil {
		fmt.Println(err)
		return
	}

	data, err := os.ReadFile(path)
	if err != nil {
		fmt.Println("read file:", err)
		return
	}

	fmt.Print(string(data))
}

输出:

复制代码
learn go
write code
read docs

这里一定要调用:

复制代码
writer.Flush()

否则最后一部分数据可能还留在缓冲区里,没有真正写入文件。

八、复制文件:io.Copy

复制文件时,不需要自己手写循环。

可以使用 io.Copy

复制代码
package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
)

func copyFile(srcPath, dstPath string) error {
	src, err := os.Open(srcPath)
	if err != nil {
		return fmt.Errorf("open source file: %w", err)
	}
	defer src.Close()

	dst, err := os.Create(dstPath)
	if err != nil {
		return fmt.Errorf("create destination file: %w", err)
	}
	defer dst.Close()

	if _, err := io.Copy(dst, src); err != nil {
		return fmt.Errorf("copy file: %w", err)
	}

	return nil
}

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	src := filepath.Join(dir, "source.txt")
	dst := filepath.Join(dir, "backup.txt")

	err = os.WriteFile(src, []byte("important data\n"), 0644)
	if err != nil {
		fmt.Println("write source:", err)
		return
	}

	if err := copyFile(src, dst); err != nil {
		fmt.Println(err)
		return
	}

	data, err := os.ReadFile(dst)
	if err != nil {
		fmt.Println("read backup:", err)
		return
	}

	fmt.Print(string(data))
}

输出:

复制代码
important data

io.Copy(dst, src) 的意思是:

复制代码
从 src 读数据,写入 dst,直到读完或出错。

它依赖的是接口:

复制代码
io.Reader
io.Writer

所以它不仅能复制文件,也能把网络响应写入文件,把字符串写入 buffer,等等。

九、判断文件是否存在

可以用 os.Stat 获取文件信息。

如果文件不存在,错误可以用 errors.Is(err, os.ErrNotExist) 判断。

复制代码
package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
)

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}

	if errors.Is(err, os.ErrNotExist) {
		return false, nil
	}

	return false, err
}

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "missing.txt")

	ok, err := exists(path)
	if err != nil {
		fmt.Println("check file:", err)
		return
	}

	fmt.Println("exists:", ok)
}

输出:

复制代码
exists: false

不要用字符串判断:

复制代码
strings.Contains(err.Error(), "no such file")

更推荐:

复制代码
errors.Is(err, os.ErrNotExist)

这和前面错误处理文章里的思路一致:用结构化方式判断错误。

十、获取文件信息:os.Stat

os.Stat 可以拿到文件大小、权限、是否目录等信息。

复制代码
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "info.txt")
	err = os.WriteFile(path, []byte("hello"), 0644)
	if err != nil {
		fmt.Println("write file:", err)
		return
	}

	info, err := os.Stat(path)
	if err != nil {
		fmt.Println("stat file:", err)
		return
	}

	fmt.Println("name:", info.Name())
	fmt.Println("size:", info.Size())
	fmt.Println("is dir:", info.IsDir())
	fmt.Println("mode:", info.Mode())
}

可能输出:

复制代码
name: info.txt
size: 5
is dir: false
mode: -rw-r--r--

常用方法:

复制代码
info.Name()  // 文件名
info.Size()  // 文件大小,单位是字节
info.Mode()  // 文件权限和类型
info.IsDir() // 是否目录

十一、创建目录:os.Mkdir 和 os.MkdirAll

创建单层目录可以用 os.Mkdir

创建多层目录更常用 os.MkdirAll

复制代码
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	nested := filepath.Join(dir, "logs", "2026", "06")

	if err := os.MkdirAll(nested, 0755); err != nil {
		fmt.Println("mkdir all:", err)
		return
	}

	fmt.Println("created:", filepath.Base(nested))
}

输出:

复制代码
created: 06

os.MkdirAll 的特点是:

复制代码
中间目录不存在就一起创建。
目录已经存在也不会报错。

这对创建配置目录、缓存目录、日志目录很方便。

十二、读取目录:os.ReadDir

读取一个目录下的文件和子目录,可以用 os.ReadDir

复制代码
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	_ = os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0644)
	_ = os.WriteFile(filepath.Join(dir, "b.txt"), []byte("b"), 0644)
	_ = os.Mkdir(filepath.Join(dir, "logs"), 0755)

	entries, err := os.ReadDir(dir)
	if err != nil {
		fmt.Println("read dir:", err)
		return
	}

	for _, entry := range entries {
		kind := "file"
		if entry.IsDir() {
			kind = "dir"
		}
		fmt.Printf("%s: %s\n", kind, entry.Name())
	}
}

可能输出:

复制代码
file: a.txt
file: b.txt
dir: logs

os.ReadDir 只读取当前目录这一层。

如果你要递归遍历子目录,要用 filepath.WalkDir

十三、递归遍历目录:filepath.WalkDir

filepath.WalkDir 可以递归遍历目录树。

复制代码
package main

import (
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
)

func main() {
	root, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(root)

	_ = os.MkdirAll(filepath.Join(root, "logs", "app"), 0755)
	_ = os.WriteFile(filepath.Join(root, "README.md"), []byte("readme"), 0644)
	_ = os.WriteFile(filepath.Join(root, "logs", "app", "today.log"), []byte("ok"), 0644)

	err = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		rel, err := filepath.Rel(root, path)
		if err != nil {
			return err
		}

		if rel == "." {
			return nil
		}

		fmt.Println(rel)
		return nil
	})
	if err != nil {
		fmt.Println("walk dir:", err)
	}
}

可能输出:

复制代码
README.md
logs
logs/app
logs/app/today.log

回调函数里有三个参数:

复制代码
func(path string, d fs.DirEntry, err error) error

含义是:

  • path:当前路径
  • d:当前目录项信息
  • err:访问当前路径时遇到的错误

如果回调返回错误,遍历会停止并把错误返回给外层。

十四、路径拼接:filepath.Join

不要手动拼接路径。

不推荐:

复制代码
path := dir + "/" + name

更推荐:

复制代码
path := filepath.Join(dir, name)

完整例子:

复制代码
package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	path := filepath.Join("data", "logs", "app.log")

	fmt.Println(path)
	fmt.Println("dir:", filepath.Dir(path))
	fmt.Println("base:", filepath.Base(path))
	fmt.Println("ext:", filepath.Ext(path))
}

在类 Unix 系统上输出:

复制代码
data/logs/app.log
dir: data/logs
base: app.log
ext: .log

filepath.Join 会使用当前操作系统合适的路径分隔符。

所以跨平台处理本地文件路径时,优先用它。

十五、清理和转换路径

filepath.Clean 可以清理路径里的多余部分。

复制代码
package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	raw := "data/./logs/../app.log"

	fmt.Println(filepath.Clean(raw))

	abs, err := filepath.Abs(raw)
	if err != nil {
		fmt.Println("abs path:", err)
		return
	}

	fmt.Println(filepath.Base(abs))
}

可能输出:

复制代码
data/app.log
app.log

常用函数:

复制代码
filepath.Clean(path) // 清理路径
filepath.Abs(path)   // 转成绝对路径
filepath.Rel(base, target) // 计算相对路径

十六、重命名和删除文件

重命名文件使用 os.Rename

删除文件使用 os.Remove

删除目录树使用 os.RemoveAll

复制代码
package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	oldPath := filepath.Join(dir, "old.txt")
	newPath := filepath.Join(dir, "new.txt")

	if err := os.WriteFile(oldPath, []byte("hello"), 0644); err != nil {
		fmt.Println("write file:", err)
		return
	}

	if err := os.Rename(oldPath, newPath); err != nil {
		fmt.Println("rename:", err)
		return
	}

	if err := os.Remove(newPath); err != nil {
		fmt.Println("remove:", err)
		return
	}

	_, err = os.Stat(newPath)
	fmt.Println("removed:", errors.Is(err, os.ErrNotExist))
}

输出:

复制代码
removed: true

注意:

复制代码
os.Remove 删除单个文件或空目录。
os.RemoveAll 会递归删除整个目录树。

使用 os.RemoveAll 时要非常小心,路径算错会删掉不该删的东西。

十七、临时文件和临时目录

写测试、生成中间文件、处理上传内容时,经常需要临时文件。

Go 提供:

复制代码
os.CreateTemp
os.MkdirTemp

示例:

复制代码
package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.CreateTemp("", "report-*.txt")
	if err != nil {
		fmt.Println("create temp file:", err)
		return
	}

	name := file.Name()
	defer os.Remove(name)
	defer file.Close()

	if _, err := file.WriteString("temporary report\n"); err != nil {
		fmt.Println("write temp file:", err)
		return
	}

	fmt.Println("temp file created:", name != "")
}

输出:

复制代码
temp file created: true

os.CreateTemp("", "report-*.txt") 里第一个参数是目录。

传空字符串表示使用系统默认临时目录。

第二个参数是文件名模式,* 会被替换成随机字符串。

十八、关闭文件时的错误

很多时候你会看到:

复制代码
defer file.Close()

这很常见,也够用。

但要知道:

复制代码
Close 本身也可能返回错误。

对于只读文件,忽略 Close 错误通常问题不大。

对于写文件,如果你非常关心数据是否完整落盘,就应该认真处理写入错误、Flush 错误、Close 错误。

例如:

复制代码
func writeText(path string, text string) (err error) {
	file, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("create file: %w", err)
	}

	defer func() {
		closeErr := file.Close()
		if err == nil && closeErr != nil {
			err = fmt.Errorf("close file: %w", closeErr)
		}
	}()

	if _, err := file.WriteString(text); err != nil {
		return fmt.Errorf("write file: %w", err)
	}

	return nil
}

这段代码用了命名返回值:

复制代码
func writeText(path string, text string) (err error)

这样 defer 里可以在函数真正返回前补充关闭文件时的错误。

新手阶段不用每段代码都写这么复杂,但要知道:关键写入场景不能永远无视 Close 的错误。

十九、实战:把文件里的空行去掉

最后写一个小实战。

需求:

复制代码
读取 input.txt
去掉空行
把结果写入 output.txt

完整代码:

复制代码
package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func removeEmptyLines(inputPath, outputPath string) error {
	input, err := os.Open(inputPath)
	if err != nil {
		return fmt.Errorf("open input file: %w", err)
	}
	defer input.Close()

	output, err := os.Create(outputPath)
	if err != nil {
		return fmt.Errorf("create output file: %w", err)
	}
	defer output.Close()

	scanner := bufio.NewScanner(input)
	writer := bufio.NewWriter(output)

	for scanner.Scan() {
		line := scanner.Text()
		if strings.TrimSpace(line) == "" {
			continue
		}

		if _, err := writer.WriteString(line + "\n"); err != nil {
			return fmt.Errorf("write output line: %w", err)
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("scan input file: %w", err)
	}

	if err := writer.Flush(); err != nil {
		return fmt.Errorf("flush output file: %w", err)
	}

	return nil
}

func main() {
	dir, err := os.MkdirTemp("", "go-file-demo-*")
	if err != nil {
		fmt.Println("create temp dir:", err)
		return
	}
	defer os.RemoveAll(dir)

	inputPath := filepath.Join(dir, "input.txt")
	outputPath := filepath.Join(dir, "output.txt")

	content := "apple\n\nbanana\n   \norange\n"
	if err := os.WriteFile(inputPath, []byte(content), 0644); err != nil {
		fmt.Println("write input:", err)
		return
	}

	if err := removeEmptyLines(inputPath, outputPath); err != nil {
		fmt.Println("remove empty lines:", err)
		return
	}

	data, err := os.ReadFile(outputPath)
	if err != nil {
		fmt.Println("read output:", err)
		return
	}

	fmt.Print(string(data))
}

输出:

复制代码
apple
banana
orange

这个例子串起了很多文件处理知识:

  • os.Open 打开输入文件
  • os.Create 创建输出文件
  • defer Close 关闭文件
  • bufio.Scanner 按行读取
  • strings.TrimSpace 判断空行
  • bufio.Writer 缓冲写入
  • writer.Flush 把缓冲内容写入文件
  • 使用 %w 保留底层错误