要求 Go 版本 >= 1.23;GcsFs 与 SftpFs 仍为实验性,S3、MinIO 等云后端需另装第三方包;io/fs 只覆盖只读场景,需要写操作时用 afero 而非标准库接口
go get github.com/spf13/aferoREADME 未说明,纯 Go 库无特殊硬件要求
Go 的文件系统抽象库,用统一接口替换 os 包,让存储后端可插拔、测试无需磁盘
Go 语言的通用文件系统抽象层,用统一接口操作本地磁盘、内存、网络等多种文件系统,方便测试和替换存储后端。
核心能力
- 用 afero.Fs 统一接口替换 os 包调用
- MemMapFs 提供并发安全的内存文件系统用于测试
- CopyOnWriteFs 与 CacheOnReadFs 支持分层组合
- BasePathFs 把访问限制在子目录内做 chroot
- zipfs 与 tarfs 直接把归档当文件系统读
容易踩的坑
- v1.15.0最新正式版2025-09-08 · 2025-09-08 16:41 UTC
What's Changed
- Bump golangci/golangci-lint-action from 6.5.1 to 6.5.2 by @dependabot[bot] in https://github.com/spf13/afero/pull/479
- Lint by @sagikazarmark in https://github.com/spf13/afero/pull/492
- build(deps): bump github/codeql-action from 2.13.4 to 3.28.15 by @dependabot[bot] in https://github.com/spf13/afero/pull/494
- build(deps): bump actions/dependency-review-action from 4.5.0 to 4.6.0 by @dependabot[bot] in https://github.com/spf13/afero/pull/493
- Bump actions/setup-go from 5.3.0 to 5.4.0 by @dependabot[bot] in https://github.com/spf13/afero/pull/480
- build(deps): bump github/codeql-action from 3.28.15 to 3.28.16 by @dependabot[bot] in https://github.com/spf13/afero/pull/496
- support aliyun oss storage with third-party link by @messikiller in https://github.com/spf13/afero/pull/491
- build(deps): bump github/codeql-action from 3.28.16 to 3.28.17 by @dependabot[bot] in https://github.com/spf13/afero/pull/497
- chore: update x/test by @sagikazarmark in https://github.com/spf13/afero/pull/502
- build(deps): bump actions/setup-go from 5.4.0 to 5.5.0 by @dependabot[bot] in https://github.com/spf13/afero/pull/503
- build(deps): bump actions/dependency-review-action from 4.6.0 to 4.7.1 by @dependabot[bot] in https://github.com/spf13/afero/pull/505
- build(deps): bump github/codeql-action from 3.28.17 to 3.28.18 by @dependabot[bot] in https://github.com/spf13/afero/pull/506
- fix(gcsfs): update object not exist check logic by @ahkui in https://github.com/spf13/afero/pull/485
- build(deps): bump ossf/scorecard-action from 2.4.1 to 2.4.2 by @dependabot[bot] in https://github.com/spf13/afero/pull/508
- build(deps): bump github/codeql-action from 3.28.18 to 3.29.4 by @dependabot[bot] in https://github.com/spf13/afero/pull/519
- build(deps): bump github/codeql-action from 3.29.4 to 3.29.7 by @dependabot[bot] in https://github.com/spf13/afero/pull/524
- build(deps): bump github/codeql-action from 3.29.7 to 3.30.1 by @dependabot[bot] in https://github.com/spf13/afero/pull/537
- build(deps): bump actions/setup-go from 5.5.0 to 6.0.0 by @dependabot[bot] in https://github.com/spf13/afero/pull/536
- build(deps): bump actions/dependency-review-action from 4.7.1 to 4.7.3 by @dependabot[bot] in https://github.com/spf13/afero/pull/533
- build(deps): bump actions/checkout from 4.2.2 to 5.0.0 by @dependabot[bot] in https://github.com/spf13/afero/pull/527
- chore: update deps by @sagikazarmark in https://github.com/spf13/afero/pull/538
- fix: spelling errors (excpected -> expected, iself -> itself) by @MarkRosemaker in https://github.com/spf13/afero/pull/523
New Contributors
- @messikiller made their first contribution in https://github.com/spf13/afero/pull/491
- @ahkui made their first contribution in https://github.com/spf13/afero/pull/485
- @MarkRosemaker made their first contribution in https://github.com/spf13/afero/pull/523
Full Changelog: https://github.com/spf13/afero/compare/v1.14.0...v1.15.0
- v1.14.02025-03-15 · 2025-03-15 14:11 UTC
查看更新说明
What's Changed
- Split gcsfs and sftpfs into separate modules by @sagikazarmark in https://github.com/spf13/afero/pull/462
Full Changelog: https://github.com/spf13/afero/compare/v1.13.0...v1.14.0
- v1.13.02025-03-15 · 2025-03-15 01:21 UTC
查看更新说明
What's Changed
- Bump actions/setup-go from 5.2.0 to 5.3.0 by @dependabot in https://github.com/spf13/afero/pull/446
- Bump golangci/golangci-lint-action from 6.1.1 to 6.3.0 by @dependabot in https://github.com/spf13/afero/pull/451
- Bump golang.org/x/text from 0.21.0 to 0.22.0 by @dependabot in https://github.com/spf13/afero/pull/452
- Bump golang.org/x/oauth2 from 0.25.0 to 0.26.0 by @dependabot in https://github.com/spf13/afero/pull/453
- Bump golangci/golangci-lint-action from 6.3.0 to 6.3.3 by @dependabot in https://github.com/spf13/afero/pull/459
- Bump golang.org/x/crypto from 0.32.0 to 0.33.0 by @dependabot in https://github.com/spf13/afero/pull/455
- Bump golangci/golangci-lint-action from 6.3.3 to 6.5.0 by @dependabot in https://github.com/spf13/afero/pull/460
- ci: add Go 1.24 to the test matrix by @sagikazarmark in https://github.com/spf13/afero/pull/461
- Bump golangci/golangci-lint-action from 6.5.0 to 6.5.1 by @dependabot in https://github.com/spf13/afero/pull/473
- Update dependencies by @sagikazarmark in https://github.com/spf13/afero/pull/477
New Contributors
- @dependabot made their first contribution in https://github.com/spf13/afero/pull/446
Full Changelog: https://github.com/spf13/afero/compare/v1.12.0...v1.13.0
- v1.12.02025-01-09 · 2025-01-09 11:03 UTC
查看更新说明
What's Changed
- improve ci by @sagikazarmark in https://github.com/spf13/afero/pull/438
- Update dependencies by @sagikazarmark in https://github.com/spf13/afero/pull/439
- add dependabot by @sagikazarmark in https://github.com/spf13/afero/pull/440
- Update README.md by @sean9999 in https://github.com/spf13/afero/pull/434
- fix lint violations by @sagikazarmark in https://github.com/spf13/afero/pull/441
New Contributors
- @sagikazarmark made their first contribution in https://github.com/spf13/afero/pull/438
- @sean9999 made their first contribution in https://github.com/spf13/afero/pull/434
Full Changelog: https://github.com/spf13/afero/compare/v1.11.0...v1.12.0
- v1.11.02023-11-28 · 2023-11-28 09:51 UTC
查看更新说明
What's Changed
- deps: Update all direct dependencies by @bep in https://github.com/spf13/afero/pull/410
Full Changelog: https://github.com/spf13/afero/compare/v1.10.0...v1.11.0
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Afero: The Universal Filesystem Abstraction for Go
Afero is a filesystem abstraction library for Go that works as a drop-in replacement for the standard os package. Adopt it with minimal code changes, then swap any backend—local disk, in-memory, cloud, or archive—without touching your application logic.
Two headline benefits: testing becomes trivial (replace the OS with a fast in-memory filesystem in one line, no disk cleanup required), and your code stays portable across any storage backend through a single afero.Fs interface.
Why Afero?
Afero elevates filesystem interaction beyond simple file reading and writing, offering solutions for testability, flexibility, and advanced architecture.
🔑 Key Features:
- Universal API: Write your code once. Run it against the local OS, in-memory storage, ZIP/TAR archives, or remote systems (SFTP, GCS).
- Ultimate Testability: Utilize
MemMapFs, a fully concurrent-safe, read/write in-memory filesystem. Write fast, isolated, and reliable unit tests without touching the physical disk or worrying about cleanup. - Powerful Composition: Afero's hidden superpower. Layer filesystems on top of each other to create sophisticated behaviors:
- Sandboxing: Use
CopyOnWriteFsto create temporary scratch spaces that isolate changes from the base filesystem. - Caching: Use
CacheOnReadFsto automatically layer a fast cache (like memory) over a slow backend (like a network drive). - Security Jails: Use
BasePathFsto restrict application access to a specific subdirectory (chroot).
- Sandboxing: Use
osPackage Compatibility: Afero mirrors the functions in the standardospackage, making adoption and refactoring seamless.io/fsCompatibility: Fully compatible with the Go standard library'sio/fsinterfaces.
Installation
go get github.com/spf13/afero
import "github.com/spf13/afero"
Backend Reference
| Type | Backend | Constructor | Description | Status |
|---|---|---|---|---|
| Core | OsFs | afero.NewOsFs() | Interacts with the real operating system filesystem. Use in production. | ✅ Official |
| MemMapFs | afero.NewMemMapFs() | A fast, atomic, concurrent-safe, in-memory filesystem. Ideal for testing. | ✅ Official | |
| Composition | CopyOnWriteFs | afero.NewCopyOnWriteFs(base, overlay) | A read-only base with a writable overlay. Ideal for sandboxing. | ✅ Official |
| CacheOnReadFs | afero.NewCacheOnReadFs(base, cache, ttl) | Lazily caches files from a slow base into a fast layer on first read. | ✅ Official | |
| BasePathFs | afero.NewBasePathFs(source, path) | Restricts operations to a subdirectory (chroot/jail). | ✅ Official | |
| ReadOnlyFs | afero.NewReadOnlyFs(source) | Provides a read-only view, preventing any modifications. | ✅ Official | |
| RegexpFs | afero.NewRegexpFs(source, regexp) | Filters a filesystem, only showing files that match a regex. | ✅ Official | |
| Utility | HttpFs | afero.NewHttpFs(source) | Wraps any Afero filesystem to be served via http.FileServer. | ✅ Official |
| Archives | ZipFs | zipfs.New(zipReader) | Read-only access to files within a ZIP archive. | ✅ Official |
| TarFs | tarfs.New(tarReader) | Read-only access to files within a TAR archive. | ✅ Official | |
| Network | GcsFs | gcsfs.NewGcsFs(...) | Google Cloud Storage backend. | ⚡ Experimental |
| SftpFs | sftpfs.New(...) | SFTP backend. | ⚡ Experimental | |
| 3rd Party Cloud | S3Fs | fclairamb/afero-s3 | Production-ready S3 backend built on official AWS SDK. | 🔹 3rd Party |
| MinioFs | cpyun/afero-minio | MinIO object storage backend with S3 compatibility. | 🔹 3rd Party | |
| DriveFs | fclairamb/afero-gdrive | Google Drive backend with streaming support. | 🔹 3rd Party | |
| DropboxFs | fclairamb/afero-dropbox | Dropbox backend with streaming support. | 🔹 3rd Party | |
| 3rd Party Specialized | GitFs | tobiash/go-gitfs | Git repository filesystem (read-only, Afero compatible). | 🔹 3rd Party |
| DockerFs | unmango/aferox | Docker container filesystem access. | 🔹 3rd Party | |
| GitHubFs | unmango/aferox | GitHub repository and releases filesystem. | 🔹 3rd Party | |
| FilterFs | unmango/aferox | Filesystem filtering with predicates. | 🔹 3rd Party | |
| IgnoreFs | unmango/aferox | .gitignore-aware filtering filesystem. | 🔹 3rd Party | |
| FUSEFs | JakWai01/sile-fystem | Generic FUSE implementation using any Afero backend. | 🔹 3rd Party |
Quick Start: The Power of Abstraction
The core of Afero is the afero.Fs interface. By designing your functions to accept this interface rather than calling os.* functions directly, your code instantly becomes more flexible and testable.
1. Refactor Your Code
Change functions that rely on the os package to accept afero.Fs.
// Before: Coupled to the OS and difficult to test
// func ProcessConfiguration(path string) error {
// data, err := os.ReadFile(path)
// ...
// }
import "github.com/spf13/afero"
// After: Decoupled, flexible, and testable
func ProcessConfiguration(fs afero.Fs, path string) error {
// Use Afero utility functions which mirror os/ioutil
data, err := afero.ReadFile(fs, path)
// ... process the data
return err
}
2. Usage in Production
In your production environment, inject the OsFs backend, which wraps the standard operating system calls.
func main() {
// Use the real OS filesystem
AppFs := afero.NewOsFs()
ProcessConfiguration(AppFs, "/etc/myapp.conf")
}
3. Usage in Testing
Steps 1 and 2 represent the entire production footprint: accept afero.Fs instead of calling os directly, then inject OsFs at the call site. That's it. Swap OsFs for MemMapFs and the whole thing runs in memory, with no disk access and nothing to clean up:
func TestProcessConfiguration(t *testing.T) {
// Use the in-memory filesystem
AppFs := afero.NewMemMapFs()
// Pre-populate the memory filesystem for the test
configPath := "/test/config.json"
afero.WriteFile(AppFs, configPath, []byte(`{"feature": true}`), 0644)
// Run the test entirely in memory
err := ProcessConfiguration(AppFs, configPath)
if err != nil {
t.Fatal(err)
}
}
Afero's Superpower: Composition
Afero's most unique feature is its ability to combine filesystems. This allows you to build complex behaviors out of simple components, keeping your application logic clean.
Example 1: Sandboxing with Copy-on-Write
Problem: You want integration tests that exercise code against real configuration files without any risk of accidentally modifying them. Or you need to let untrusted code "modify" system files in a fully contained environment.
All writes are captured in the in-memory overlay; the base is never touched:
// 1. The base layer is the real OS, made read-only for safety.
baseFs := afero.NewReadOnlyFs(afero.NewOsFs())
// 2. The overlay layer is a temporary in-memory filesystem for changes.
overlayFs := afero.NewMemMapFs()
// 3. Combine them. Reads fall through to the base; writes only hit the overlay.
sandboxFs := afero.NewCopyOnWriteFs(baseFs, overlayFs)
// The application can now "modify" /etc/hosts, but the changes are isolated in memory.
afero.WriteFile(sandboxFs, "/etc/hosts", []byte("127.0.0.1 sandboxed-app"), 0644)
// The real /etc/hosts on disk is untouched.
Example 2: Caching a Slow Filesystem
Problem: Your application reads the same files repeatedly from a slow backend network storage, cloud, SFTP. You want instant repeated reads without rewriting a single line of file-access code.
Layer a fast in-memory cache in front; the first read fetches and caches, every subsequent read is instant:
import "time"
// Assume 'remoteFs' is a slow backend (e.g., SFTP or GCS)
var remoteFs afero.Fs
// 'cacheFs' is a fast in-memory backend
cacheFs := afero.NewMemMapFs()
// Create the caching layer. Cache items for 5 minutes upon first read.
cachedFs := afero.NewCacheOnReadFs(remoteFs, cacheFs, 5*time.Minute)
// The first read is slow (fetches from remote, then caches)
data1, _ := afero.ReadFile(cachedFs, "data.json")
// The second read is instant (serves from memory cache)
data2, _ := afero.ReadFile(cachedFs, "data.json")
Example 3: Security Jails (chroot)
Problem: You need to expose filesystem access to a plugin, template engine, or user-submitted process but directory traversal must be impossible. It should only ever see a single rooted directory.
osFs := afero.NewOsFs()
// Create a filesystem rooted at /home/user/public
// The application cannot access anything above this directory.
jailedFs := afero.NewBasePathFs(osFs, "/home/user/public")
// To the application, this is reading "/"
// In reality, it's reading "/home/user/public/"
dirInfo, err := afero.ReadDir(jailedFs, "/")
// Attempts to access parent directories fail
_, err = jailedFs.Open("../secrets.txt") // Returns an error
Real-World Use Cases
Build Cloud-Agnostic Applications
Write applications that seamlessly work with different storage backends:
type DocumentProcessor struct {
fs afero.Fs
}
func NewDocumentProcessor(fs afero.Fs) *DocumentProcessor {
return &DocumentProcessor{fs: fs}
}
func (p *DocumentProcessor) Process(inputPath, outputPath string) error {
// This code works whether fs is local disk, cloud storage, or memory
content, err := afero.ReadFile(p.fs, inputPath)
if err != nil {
return err
}
processed := processContent(content)
return afero.WriteFile(p.fs, outputPath, processed, 0644)
}
// Use with local filesystem
processor := NewDocumentProcessor(afero.NewOsFs())
// Use with Google Cloud Storage
processor := NewDocumentProcessor(gcsFS)
// Use with in-memory filesystem for testing
processor := NewDocumentProcessor(afero.NewMemMapFs())
Treating Archives as Filesystems
ZIP and TAR archives are directories in disguise. Afero exposes them as a standard afero.Fs—which means any code that already accepts afero.Fs works unchanged against an archive, with no extraction, no temp directory, and no cleanup required.
This is particularly useful when:
- Processing uploaded ZIP files in a web handler without writing decompressed content to disk
- Reading fixture data bundled into a binary without
//go:embed - Writing archive inspection or transformation tools that reuse the same traversal logic as your real filesystem code
- Running tests against a canned, read-only snapshot of a directory tree shipped as a
.zip
import (
"archive/zip"
"github.com/spf13/afero/zipfs"
)
// Open any .zip — from disk, an HTTP response body, or memory
zipFile, _ := zip.OpenReader("bundle.zip")
defer zipFile.Close()
// Treat the archive as a filesystem — no extraction needed
archiveFS := zipfs.New(&zipFile.Reader)
// The same code that works with OsFs or MemMapFs works here unchanged
content, err := afero.ReadFile(archiveFS, "docs/readme.md")
entries, err := afero.ReadDir(archiveFS, "configs/")
Serving Any Filesystem over HTTP
Use HttpFs to expose any Afero filesystem—even one created dynamically in memory—through a standard Go web server.
import (
"net/http"
"github.com/spf13/afero"
)
func main() {
memFS := afero.NewMemMapFs()
afero.WriteFile(memFS, "index.html", []byte("<h1>Hello from Memory!</h1>"), 0644)
// Wrap the memory filesystem to make it compatible with http.FileServer.
httpFS := afero.NewHttpFs(memFS)
http.Handle("/", http.FileServer(httpFS.Dir("/")))
http.ListenAndServe(":8080", nil)
}
Testing Made Simple
One of Afero's greatest strengths is making filesystem-dependent code easily testable:
func SaveUserData(fs afero.Fs, userID string, data []byte) error {
filename := fmt.Sprintf("users/%s.json", userID)
return afero.WriteFile(fs, filename, data, 0644)
}
func TestSaveUserData(t *testing.T) {
// Create a clean, fast, in-memory filesystem for testing
testFS := afero.NewMemMapFs()
userData := []byte(`{"name": "John", "email": "[email protected]"}`)
err := SaveUserData(testFS, "123", userData)
if err != nil {
t.Fatalf("SaveUserData failed: %v", err)
}
// Verify the file was saved correctly
saved, err := afero.ReadFile(testFS, "users/123.json")
if err != nil {
t.Fatalf("Failed to read saved file: %v", err)
}
if string(saved) != string(userData) {
t.Errorf("Data mismatch: got %s, want %s", saved, userData)
}
}
Benefits of testing with Afero:
- ⚡ Fast - No disk I/O, tests run in memory
- 🔄 Reliable - Each test starts with a clean slate
- 🧹 No cleanup - Memory is automatically freed
- 🔒 Safe - Can't accidentally modify real files
- 🏃 Parallel - Tests can run concurrently without conflicts
Afero vs. io/fs (Go 1.16+)
Go 1.16 introduced the io/fs package, which provides a standard abstraction for read-only filesystems.
Afero complements io/fs by focusing on different needs:
- Use
io/fswhen: You only need to read files and want to conform strictly to the standard library interfaces. - Use Afero when:
- Your application needs to create, write, modify, or delete files.
- You need to test complex read/write interactions (e.g., renaming, concurrent writes).
- You need advanced compositional features (Copy-on-Write, Caching, etc.).
Afero is fully compatible with io/fs. You can wrap any Afero filesystem to satisfy the fs.FS interface using afero.NewIOFS:
import "io/fs"
// Create an Afero filesystem (writable)
var myAferoFs afero.Fs = afero.NewMemMapFs()
// Convert it to a standard library fs.FS (read-only view)
var myIoFs fs.FS = afero.NewIOFS(myAferoFs)
Third-Party Backends & Ecosystem
The Afero community has developed numerous backends and tools that extend the library's capabilities. Below are curated, well-maintained options organized by maturity and reliability.
Featured Community Backends
These are mature, reliable backends that we can confidently recommend for production use:
Amazon S3 - fclairamb/afero-s3
Production-ready S3 backend built on the official AWS SDK for Go.
import "github.com/fclairamb/afero-s3"
s3fs := s3.NewFs(bucket, session)
MinIO - cpyun/afero-minio
MinIO object storage backend providing S3-compatible object storage with deduplication and optimization features.
import "github.com/cpyun/afero-minio"
minioFs := miniofs.NewMinioFs(ctx, "minio://endpoint/bucket")
Community & Specialized Backends
Cloud Storage
-
Google Drive -
fclairamb/afero-gdrive
Streaming support; no write-seeking or POSIX permissions; no files listing cache -
Dropbox -
fclairamb/afero-dropbox
Streaming support; no write-seeking or POSIX permissions
Version Control Systems
- Git Repositories -
tobiash/go-gitfs
Read-only filesystem abstraction for Git repositories. Works with bare repositories and provides filesystem view of any git reference. Uses go-git for repository access.
Container and Remote Systems
-
Docker Containers -
unmango/aferox
Access Docker container filesystems as if they were local filesystems -
GitHub API -
unmango/aferox
Turn GitHub repositories, releases, and assets into browsable filesystems
FUSE Integration
- Generic FUSE -
JakWai01/sile-fystem
Mount any Afero filesystem as a FUSE filesystem, allowing any Afero backend to be used as a real mounted filesystem
Specialized Filesystems
- FAT32 Support -
aligator/GoFAT
Pure Go FAT filesystem implementation (currently read-only)
Interface Adapters & Utilities
Cross-Interface Compatibility:
jfontan/go-billy-desfacer- Adapter between Afero and go-billy interfaces (for go-git compatibility)Maldris/go-billy-afero- Alternative wrapper for using Afero with go-billyc4milo/afero2billy- Another Afero to billy filesystem adapter
Working Directory Management:
carolynvs/aferox- Working directory-aware filesystem wrapper
Advanced Filtering:
unmango/aferoxincludes multiple specialized filesystems:- FilterFs - Predicate-based file filtering
- IgnoreFs - .gitignore-aware filtering
- WriterFs - Dump writes to io.Writer for debugging
Developer Tools & Utilities
nhatthm Utility Suite - Essential tools for Afero development:
nhatthm/aferocopy- Copy files between any Afero filesystemsnhatthm/aferomock- Mocking toolkit for testingnhatthm/aferoassert- Assertion helpers for filesystem testing
Ecosystem Showcase
Windows Virtual Drives - balazsgrill/potatodrive
Mount any Afero filesystem as a Windows drive letter. Brilliant demonstration of Afero's power!
Modern Asset Embedding (Go 1.16+)
Instead of third-party tools, use Go's native //go:embed with Afero:
import (
"embed"
"github.com/spf13/afero"
)
//go:embed assets/*
var assetsFS embed.FS
func main() {
// Convert embedded files to Afero filesystem
fs := afero.FromIOFS(assetsFS)
// Use like any other Afero filesystem
content, _ := afero.ReadFile(fs, "assets/config.json")
}
Contributing
We welcome contributions! The project is mature, but we are actively looking for contributors to help implement and stabilize network/cloud backends.
- 🔥 Microsoft Azure Blob Storage
- 🔒 Modern Encryption Backend - Built on secure, contemporary crypto (not legacy EncFS)
- 🐙 Canonical go-git Adapter - Unified solution for Git integration
- 📡 SSH/SCP Backend - Secure remote file operations
- Stabilization of existing experimental backends (GCS, SFTP)
To contribute:
- Fork the repository
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
📄 License
Afero is released under the Apache 2.0 license. See LICENSE.txt for details.
🔗 Additional Resources
Afero comes from the Latin roots Ad-Facere, meaning "to make" or "to do" - fitting for a library that empowers you to make and do amazing things with filesystems.