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 的环节。迁移时应把“按名称创建”换成“按头创建”,并保留正确的内容复制与归档关闭逻辑,而不是只改一个文件名后缀。

从源文件模式创建一个完整条目
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 不等于关闭传入的文件句柄。本文只说明单个普通文件,不把目录、符号链接和设备文件混入同一个分支处理。

最小核对要区分头部记录与落盘结果
完整示例重新读取内存归档,通过条目的 Mode().Perm() 展示普通权限位。这种检查可以帮助读者确认自己的写入代码是否带入模式,但不等于已经在另一个操作系统完成解压。本文没有执行这一步,也没有将它作为发布前校验。
| 关心的问题 | 观察对象 | 不要混淆 |
|---|---|---|
| ZIP记录了什么 | FileHeader.Mode | 不是当前磁盘模式 |
| 执行位有没有 | Mode().Perm()中的对应位 | 不是扩展名 |
| 解压后怎样使用 | 目标文件系统与解压策略 | 不是写入器能单独保证 |
采用时至少明确三项:保留源模式还是设置固定策略;头部交付后是否仍被修改;归档关闭错误是否会被吞掉。不要为了让脚本可执行而统一给所有文件0777,也不要把来自不可信压缩包的特殊权限直接应用到系统。
两个常见疑问
SetMode 会给源文件增加执行权限吗? 不会。它修改 FileHeader,不对源文件调用 chmod。
这能完整保留所有权限元数据吗? 本文讨论 Go FileMode 对应的模式信息,不承诺 UID/GID、ACL、扩展属性或不同系统的安全描述符完整往返。即使归档记录正确,接收端仍可能按自己的安全策略限制恢复。
要解决“ZIP里丢了脚本执行位”,先把 FileHeader 接入条目创建,再讨论目标环境如何恢复;内容、模式和恢复策略应分别判断。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
403 收藏
-
209 收藏
-
313 收藏
-
468 收藏
-
460 收藏
-
408 收藏
-
376 收藏
-
495 收藏
-
412 收藏
-
309 收藏
-
127 收藏
-
122 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习