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

Go 怎样保留 ZIP 文件权限信息

来源:17golang原创

时间:2026-10-17 18:08:37 219浏览 收藏

Go 打包 ZIP 时要保留文件权限,不能只把内容写进 Writer.Create(name)。应先从源文件的 FileInfo 建立 FileHeader,或调用 SetMode 明确设置模式,再用 CreateHeader 创建条目。这样保存的是归档元数据;解压工具是否恢复执行位,是另一件事。

假设要给同事分发一个原本可执行的 launch.sh:脚本内容完全一样,但 ZIP 中没有按预期记录模式,接收方就可能只拿到普通文本文件。这里用这个假设场景拆解写入方式,不把示例当作已发生的线上故障。

真正要补上的,是条目头而不是压缩参数

这里不是一条新版本公告,而是把现有标准库能力接入打包代码。压缩方式决定内容怎样编码,文件模式决定条目描述什么类型、携带什么权限;设置 Deflate 不会替代 SetMode。官方文档入口:https://pkg.go.dev/archive/zip。

旧代码如果只有 Create 加 io.Copy,只有内容来源,没有向写入器提供源文件 FileInfo 的环节。迁移时应把“按名称创建”换成“按头创建”,并保留正确的内容复制与归档关闭逻辑,而不是只改一个文件名后缀。

文件模式与ZIP条目头的静态关系
图1:源文件信息提供模式,头部同时关联名称与压缩方式;关系线是静态字段关系,不是打包执行流程。

从源文件模式创建一个完整条目

package main

import (
    "archive/zip"
    "bytes"
    "fmt"
    "io"
    "os"
)

// PackOne 只打包一个普通文件;返回完整ZIP字节,不写磁盘。
func PackOne(source string) ([]byte, error) {
    f, err := os.Open(source)
    if err != nil { return nil, err }
    defer f.Close() // 本函数负责关闭源文件。

    info, err := f.Stat()
    if err != nil { return nil, err }
    if !info.Mode().IsRegular() {
        return nil, fmt.Errorf("只支持普通文件")
    }
    h, err := zip.FileInfoHeader(info) // 带入源文件模式信息。
    if err != nil { return nil, err }
    h.Name = "tools/launch.sh" // ZIP内部相对名称,使用正斜杠。
    h.Method = zip.Deflate    // 明确选择压缩算法。

    var buf bytes.Buffer
    zw := zip.NewWriter(&buf)
    entry, err := zw.CreateHeader(h) // 此后不再改动h。
    if err != nil {
        _ = zw.Close()
        return nil, err
    }
    if _, err = io.Copy(entry, f); err != nil {
        _ = zw.Close() // 释放写入状态;失败结果不交付。
        return nil, err
    }
    if err := zw.Close(); err != nil { // 必须完成归档并处理最终错误。
        return nil, err
    }
    return buf.Bytes(), nil
}

func main() {
    data, err := PackOne("launch.sh") // 假设输入为可信的小型普通文件。
    if err != nil { fmt.Fprintln(os.Stderr, err); return }
    zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
    if err != nil { fmt.Fprintln(os.Stderr, err); return }
    for _, entry := range zr.File {
        // Perm仅展示九个普通权限位,不会对磁盘文件chmod。
        fmt.Printf("%s %04o\n", entry.Name, entry.Mode().Perm())
    }
}

这个程序假设当前目录已有一个可信、体积较小的普通 launch.sh;所有 ZIP 字节保存在内存。若源模式的普通权限位为0755,按代码语义,预期显示 tools/launch.sh 0755;若源模式是0644,预期就是0644。本文未执行程序,未声称已经取得运行结果。

源文件在 Windows 等环境中能提供的模式信息可能不同,不能从脚本扩展名推断它应当拥有 Unix 执行位。保留源模式与统一分发权限是两种不同目标,应由发布策略选择。

Name、Method 与 SetMode 应在交付头部前确定

FileInfoHeader 会生成一个部分填充的头;FileInfo.Name 通常只含基础文件名,因此示例主动改成归档内部名称 tools/launch.sh。这里的名称不是源文件系统绝对路径,ZIP 内使用正斜杠;不要把机器上的私有路径带进包名。

FileInfoHeader 默认没有替你选择需要的压缩算法,所以示例单独设置 Method 为 Deflate。头部已经带入源模式时,不需要再无意义地重复设置。若内容是程序生成、没有源文件信息,可以单独声明头部模式:

// 没有源文件FileInfo时,为生成的普通脚本声明权限策略。
h := &zip.FileHeader{Name: "tools/generated.sh", Method: zip.Deflate}
h.SetMode(0755) // 写入头部模式,不改变任何源文件权限。
// 接着将h交给zw.CreateHeader,向返回的writer写脚本内容。

这个片段位于已有 ZIP Writer 的函数中,复用 archive/zip 导入;它只展示头部创建方式,不是第二个完整打包器。SetMode 会处理 ZIP 中与模式表示相关的字段,通常比手工移位写 ExternalAttrs 更清楚。实现参考:https://go.dev/src/archive/zip/struct.go。

CreateHeader 接管传入的头并可能改动其字段,调用后不要再修改它,也不要把同一份头不断改名复用给多个条目。应在调用前一次确定该条目的名称、方法与模式。

内容写入完成,还不等于 ZIP 已经完成

io.Copy 成功只是该条目内容已经交给 writer。zip.Writer.Close 还要完成归档收尾,返回错误必须传回调用方;示例不在关闭成功前交付内存字节。条目返回的 io.Writer 不是一个需要额外 Close 的文件对象。

示例借助内存缓冲区避免写出失败产物,但不适合无上限大文件。实际改成输出文件或网络流时,ZIP Writer 与底层输出的关闭责任要分别安排;关闭 ZIP Writer 不等于关闭传入的文件句柄。本文只说明单个普通文件,不把目录、符号链接和设备文件混入同一个分支处理。

归档模式记录与解压恢复的责任边界
图2:归档内的模式、读取展示和目标环境恢复是不同责任;图中未宣称任意解压工具都会恢复权限。

最小核对要区分头部记录与落盘结果

完整示例重新读取内存归档,通过条目的 Mode().Perm() 展示普通权限位。这种检查可以帮助读者确认自己的写入代码是否带入模式,但不等于已经在另一个操作系统完成解压。本文没有执行这一步,也没有将它作为发布前校验。

关心的问题观察对象不要混淆
ZIP记录了什么FileHeader.Mode不是当前磁盘模式
执行位有没有Mode().Perm()中的对应位不是扩展名
解压后怎样使用目标文件系统与解压策略不是写入器能单独保证

采用时至少明确三项:保留源模式还是设置固定策略;头部交付后是否仍被修改;归档关闭错误是否会被吞掉。不要为了让脚本可执行而统一给所有文件0777,也不要把来自不可信压缩包的特殊权限直接应用到系统。

两个常见疑问

SetMode 会给源文件增加执行权限吗? 不会。它修改 FileHeader,不对源文件调用 chmod。

这能完整保留所有权限元数据吗? 本文讨论 Go FileMode 对应的模式信息,不承诺 UID/GID、ACL、扩展属性或不同系统的安全描述符完整往返。即使归档记录正确,接收端仍可能按自己的安全策略限制恢复。

要解决“ZIP里丢了脚本执行位”,先把 FileHeader 接入条目创建,再讨论目标环境如何恢复;内容、模式和恢复策略应分别判断。

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