强大的跨平台归档工具:Archiver 4.0
Archiver 4.0 是一款卓越的跨平台、多格式存档工具和Go语言库,为您提供了一个通用的替代方案,可以取代多种特定于平台或格式的存档工具。尽管当前处于ALPHA阶段,但其核心库API已经相当稳定。
功能概览
- 基于流的API设计
- 自动识别档案和压缩格式:
- 通过文件名
- 通过头部信息
- 使用统一的方式遍历目录、存档文件和任何其他作为
io/fs
文件系统操作的对象:DirFS
FileFS
ArchiveFS
- 文件压缩和解压
- 创建和提取档案文件
- 深入到档案文件中进行遍历
- 从档案中仅提取所需文件
- 向.tar和.zip档案插入(追加)内容
- 读取受密码保护的7-Zip文件
- 支持多种存档和压缩格式
- 可扩展性(只需注册即可添加更多格式)
- 跨平台静态二进制
- 纯Go实现(无cgo)
- 多线程Gzip
- 调整压缩级别
- 在不重新压缩的情况下将已压缩文件添加到zip档案
- 打开密码保护的RAR档案
支持的压缩格式
- brotli (.br)
- bzip2 (.bz2)
- flate (.zip)
- gzip (.gz)
- lz4 (.lz4)
- lzip (.lz)
- snappy (.sz)
- xz (.xz)
- zlib (.zz)
- zstandard (.zst)
支持的档案格式
- .zip
- .tar(包括任何压缩变体如.tar.gz)
- .rar(只读)
- .7z(只读)
tar文件可以选择使用任何压缩格式进行压缩。
命令行使用
即将在v4版本提供。现在您可以查看最后一个v3版本的文档以了解更多信息。
库使用
要获取Archiver 4.0库,请执行以下命令:
$ go get github.com/mholt/archiver/v4
创建归档
创建归档时,您不需要实际的磁盘或存储设备,只需要一个File
结构体列表。不过,从磁盘上创建归档非常常见,您可以使用FilesFromDisk()
函数从磁盘映射文件到归档中的路径。然后定义并自定义格式类型。
例如,我们将4个文件和一个目录(及其所有内容递归地包含)添加到.tar.gz文件中:
// 映射磁盘上的文件到归档中的路径
files, err := archiver.FilesFromDisk(nil, map[string]string{
"/path/on/disk/file1.txt": "file1.txt",
"/path/on/disk/file2.txt": "subfolder/file2.txt",
"/path/on/disk/file3.txt": "", // 放在根目录下为file3.txt
"/path/on/disk/file4.txt": "subfolder/", // 放在子文件夹内为file4.txt
"/path/on/disk/folder": "Custom Folder", // 内容递归添加
})
if err != nil {
return err
}
// 创建输出文件
out, err := os.Create("example.tar.gz")
if err != nil {
return err
}
defer out.Close()
// 使用CompressedArchive类型来gzip一个tarball
// (压缩不是必需的;你可以直接使用Tar)
format := archiver.CompressedArchive{
Compression: archiver.Gz{},
Archival: archiver.Tar{},
}
// 创建归档
err = format.Archive(context.Background(), out, files)
if err != nil {
return err
}
FilesFromDisk()
的第一个参数是一个可选的选项结构,用于定制如何添加文件。
提取归档
提取归档、从归档中提取以及遍历归档都使用相同的功能。
只需使用您的格式类型(如Zip
)调用Extract()
。传入上下文(用于取消),输入流,您希望从归档中提取的文件列表,以及处理每个文件的回调函数。
如果要提取所有文件,将文件路径列表设置为nil。
// 将要用来读取输入流的类型
format := archiver.Zip{}
// 我们想从归档中提取哪些文件;
// 目录会包含所有内容,除非我们的处理器返回fs.SkipDir
// (将其设置为nil以从归档中遍历所有文件)
fileList := []string{"file1.txt", "subfolder"}
handler := func(ctx context.Context, f archiver.File) error {
// 对文件进行操作
return nil
}
err := format.Extract(ctx, input, fileList, handler)
if err != nil {
return err
}
格式识别
对于具有未知内容的输入流,无需担心。Archiver可以为您识别。它基于文件名和/或头信息进行匹配:
format, input, err := archiver.Identify("filename.tar.zst", input)
if err != nil {
return err
}
// 现在可以根据需要断言format;
// 请确保使用返回的流重新读取Identify期间消耗的字节
// 想要提取某些东西吗?
if ex, ok := format.(archiver.Extractor); ok {
// ... 继续提取
}
// 或者可能是压缩的,你想解压缩?
if decom, ok := format.(archiver.Decompressor); ok {
rc, err := decom.OpenReader(unknownFile)
if err != nil {
return err
}
defer rc.Close()
// 从rc读取以获得解压缩数据
}
Identify()
通过从流的开头读取任意数量的字节(足以检查文件头)。它缓冲这些字节,并返回一个新的读取器,让您重新读取它们。
虚拟文件系统
这是我最喜欢的功能。
假设您有一个文件。它可以是磁盘上的一个目录,一个归档,一个压缩归档,或者一个普通的文件。您并不关心它的具体类型,只想统一地使用它。
只需使用Archiver创建一个文件系统:
// filename可以是:
// - 目录("/home/you/Desktop")
// - 归档("example.zip")
// - 压缩归档("example.tar.gz")
// - 普通文件("example.txt")
// - 压缩的普通文件("example.txt.gz")
fsys, err := archiver.FileSystem(filename)
if err != nil {
return err
}
这是一个完整的fs.FS
,因此无论输入是什么类型的文件,您都可以打开文件和读取目录。
例如,打开特定文件:
f, err := fsys.Open("file")
if err != nil {
return err
}
defer f.Close()
如果是常规文件,可以从它那里读取。如果是压缩文件,读取会自动解压缩。
如果是目录,可以列出其内容:
if dir, ok := f.(fs.ReadDirFile); ok {
// 0获取所有条目,但可以传> 0进行分页
entries, err := dir.ReadDir(0)
if err != nil {
return err
}
for _, e := range entries {
fmt.Println(e.Name())
}
}
或者通过这种方式获取目录列表:
entries, err := fsys.ReadDir("Playlists")
if err != nil {
return err
}
for _, e := range entries {
fmt.Println(e.Name())
}
或者,如果您想要遍历整个文件系统,但跳过名为.git
的目录:
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == ".git" {
return fs.SkipDir
}
fmt.Println("Walking:", path, "Dir?", d.IsDir())
return nil
})
if err != nil {
return err
}
结合http.FileServer
使用
它可以与http.FileServer一起使用,在浏览器中浏览归档和目录。但是,由于http.FileServer的工作方式,直接使用http.FileServer与压缩文件一起使用是不可行的;应像下面这样包裹:
fileServer := http.FileServer(http.FS(archiveFS))
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
// 禁用范围请求
writer.Header().Set("Accept-Ranges", "none")
request.Header.Del("Range")
// 禁用内容类型嗅探
ctype :=