登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go archive/tar 怎样写入目录和文件模式

来源:17golang原创

时间:2026-11-02 20:59:26 254浏览 收藏

Go archive/tar 写目录时,明确设置 Typeflag: tar.TypeDir、目录条目名和零大小;写普通文件时用 tar.TypeReg,把八进制权限写入 Header.Mode,并让 Size 等于内容的字节数。目录类型与权限由不同字段表达,不能把整个 Go 文件模式强转后直接塞入 Mode。

官方文档:https://pkg.go.dev/archive/tar

遇到空目录丢失或脚本没有执行位,先看头部字段

部署包展开后少了 cache 空目录,先确认归档里有没有独立目录条目。只写子文件路径可以表达层级,却无法代表没有文件的目录。脚本无法执行则要区分归档记录的模式和解包后的实际权限,避免直接把全部文件改成 0777。

本文用一个小型交付包串起判断、处理与失败回退。代码为原创示例,尚未执行;图片是静态说明图,不代表运行结果。

条目类型ModeSize
bundle/TypeDir07550
bundle/cache/TypeDir07500
bundle/settings.txtTypeReg0640正文的字节数
bundle/start.shTypeReg0750脚本的字节数

快速判断:类型、权限和大小分别约束什么

Typeflag 表示条目类型,Mode 表示权限及模式位,Size 表示普通文件的逻辑大小。0750 的前导零表示八进制,也可以写成 0o750;十进制 750 是另一个值。目录执行位控制路径遍历,不能机械套用配置文件的 0640。

Go 的 fs.FileMode 把类型标记放在自己的高位,直接转换整个值并不等于 tar 的 Unix 模式编码。手工创建 Header 时,把类型放到 Typeflag,普通读写执行位放到 Mode。特殊位和链接需要单独策略;本例只写目录与普通文件。

tar头部字段与目录及文件体的关系
静态说明图:类型、权限和大小属于条目元数据,分别约束不同内容属性。

处理步骤:给空目录写头,给文件写足字节

下例先在内存中完成归档,再排他创建输出文件,便于把条目错误和外层文件错误分开。它适合小归档;较大产物应流式写入独立临时文件,避免内存随包体积增长。将代码保存为 main.go 后,可在独立工作目录运行;已有 mode-demo.tar 时程序会拒绝覆盖。

package main

import (
    "archive/tar"
    "bytes"
    "fmt"
    "os"
)

func build() ([]byte, error) {
    var buf bytes.Buffer
    tw := tar.NewWriter(&buf)
    entries := []struct {
        name string
        kind byte
        mode int64
        body []byte
    }{
        {"bundle/", tar.TypeDir, 0755, nil},
        {"bundle/cache/", tar.TypeDir, 0750, nil},
        {"bundle/settings.txt", tar.TypeReg, 0640, []byte("zone=lab\n")},
        {"bundle/start.sh", tar.TypeReg, 0750,
            []byte("#!/bin/sh\n# 示例脚本只打印状态\nprintf 'ready\\n'\n")},
    }
    for _, e := range entries {
        // 类型和权限分别设置;目录不携带文件体。
        h := &tar.Header{Name: e.name, Typeflag: e.kind,
            Mode: e.mode, Size: int64(len(e.body))}
        if e.kind == tar.TypeDir { h.Size = 0 }
        if err := tw.WriteHeader(h); err != nil {
            return nil, fmt.Errorf("条目头 %s: %w", e.name, err)
        }
        if e.kind == tar.TypeReg {
            if _, err := tw.Write(e.body); err != nil {
                return nil, fmt.Errorf("文件体 %s: %w", e.name, err)
            }
        }
    }
    // 检查归档结束错误;tar.Close 不负责关闭底层文件。
    if err := tw.Close(); err != nil { return nil, err }
    return buf.Bytes(), nil
}

func main() {
    data, err := build()
    if err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) }
    // 排他创建,避免覆盖已有包;这里只演示小归档。
    f, err := os.OpenFile("mode-demo.tar", os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
    if err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) }
    _, writeErr := f.Write(data)
    closeErr := f.Close()
    if writeErr != nil || closeErr != nil {
        fmt.Fprintln(os.Stderr, "输出失败,请隔离未完成的 mode-demo.tar", writeErr, closeErr)
        os.Exit(1)
    }
    fmt.Println("已写入 mode-demo.tar")
}

两个目录都有独立头部,cache 即使为空也有条目。目录不写文件体;普通文件先写头再写对应内容。含中文的数据也要按 UTF-8 字节数声明,示例对 []byte 求长度,而非按字符数估算。

写入超过声明大小会返回 ErrWriteTooLong;不足则在后续写头或结束归档时暴露错误。必须处理 tar.Writer.Close 的返回值,不能让被忽略的 defer 吞掉归档结束错误。tar.Close 完成填充和尾部,并不代替底层文件 Close。若再组合 gzip,应先结束 tar,再结束 gzip,最后关闭文件,各层错误都要处理。

真实文件的模式转换,需要明确保留策略

从文件信息创建头部可用 FileInfoHeader。它生成的 Name 通常只有基名,目录会加斜杠;需要保留 bundle/ 下的结构时,应设置自己的归档内名称。下列为函数内部片段,fi 和 tw 由调用方提供:

// fi 为已确认的普通文件信息,tw 为调用方创建的 tar.Writer。
h, err := tar.FileInfoHeader(fi, "")
if err != nil { return err }
h.Name = "bundle/settings.txt" // 设置归档内相对路径,避免只留下基名。
h.Mode = int64(fi.Mode().Perm()) // 本策略仅保留普通权限,舍弃特殊位。
if err := tw.WriteHeader(h); err != nil { return err }

这里主动用 Perm 只保留普通九个权限位,特殊模式位会被舍弃。如果要求保留 setuid、setgid 或 sticky,不能把 Perm 当成完整保留所有模式;FileInfoHeader 本身提供类型及模式映射,是否覆盖应服从交付规则。

片段只适合已确认的普通文件。符号链接应把实际链接目标传给 FileInfoHeader 的第二个参数,不能传空字符串后继续当作普通文件处理。源文件在读取元数据后还可能变化,生产任务应使用稳定构建产物或快照;读取长度不一致时终止本包,不把半成品交给下游。

失败回退:隔离新包,保留旧交付物

不要直接截断正在使用的旧归档。使用独立临时文件名写入新包,全部写入和关闭成功后,再进入交付系统已有的切换环节。任何错误都保留旧版本,隔离未完成文件并记录条目名和失败阶段。文件已经存在,并不代表打包成功。

完整示例采用 O_EXCL 拒绝覆盖旧文件。若输出失败,操作者先确认 mode-demo.tar 属于本次未完成产物,再做清理。实际大包可沿用这个回退原则,同时为每次构建记录独立产物名,避免误删其他任务输出。

归档内容和资源关闭的边界结构
静态结构图:目录和文件属于归档内容,tar尾部与底层文件关闭是不同资源责任。

交付确认与复盘项

记录条目名、类型、八进制模式、声明大小、写入错误及关闭错误,有助于把字段错误、源文件变化和输出失败区分开。解包后的权限还受解包工具、目标文件系统及运行环境影响,不能仅凭 Header.Mode 承诺跨平台完全相同。

  • 空目录在条目清单中独立出现,不只统计非空文件。
  • 目录、配置与脚本采用不同权限策略,避免全包统一放宽。
  • 归档内路径使用预期的相对名称,不把不受约束的外部路径直接写入。
  • 只有完整写入与各层关闭成功才报告产物完成,失败时保留旧包。

目录名一定要有尾斜杠吗?显式 TypeDir 已经表达目录类型,尾斜杠是清晰的命名约定;不要依赖零值 Typeflag 的隐式推断。tar 会自动压缩吗?不会,本例是归档,压缩需另外组合 gzip 等写入器。Header.Mode 和 OpenFile 的 0600 一样吗?前者属于归档内条目,后者属于外层 mode-demo.tar,不能互相替代。

文件模式接口参考:https://pkg.go.dev/io/fs#FileMode。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>