Skip to content

Commit 07d661f

Browse files
lujun9972claude
andcommitted
blog: 添加 ZIP Extra Field 跨平台兼容性博文
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent eafa356 commit 07d661f

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#+TITLE: 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field
2+
#+AUTHOR: lujun9972
3+
#+TAGS: linux和它的小伙伴,zip,兼容性
4+
#+DATE: [2026-04-15 二 22:00]
5+
#+LANGUAGE: zh-CN
6+
#+OPTIONS: H:6 num:nil toc:t \n:nil ::t |:t ^:nil -:nil f:t *:t <:nil
7+
8+
在 Linux 下用 =zip -r something.zip something/= 打包文件分享给朋友,结果对方在 iOS 上怎么也打不开——文件明明没损坏,换个人用 macOS 又能正常解压。
9+
10+
这背后的原因是 ZIP 格式中的 /Extra Field/ (扩展字段)。
11+
12+
* 什么是 Extra Field
13+
14+
ZIP 格式规范中,每个文件条目除了基本的文件名、压缩数据之外,还可以携带一组可选的"扩展字段"(Extra Field)。这些字段用来存储标准 ZIP 头部没有覆盖的元数据。
15+
16+
Info-ZIP(Linux 上最常用的 =zip= 命令)默认会利用这个机制保存 Unix 特有的文件属性,包括:
17+
- Unix 文件权限(owner/group/other 的读写执行位)
18+
- UID 和 GID
19+
- 修改时间、访问时间等时间戳
20+
21+
按 ZIP 规范的设计,实现应该忽略自己不认识的 Extra Field,只处理能理解的。但现实中,某些平台的 ZIP 实现(特别是 iOS 上的某些解压工具)遇到包含 Extra Field 的 ZIP 文件时会直接报错,而不是优雅地忽略它们。
22+
23+
* 用 zipinfo 观察差异
24+
25+
我们可以用 =zipinfo= 命令直观地看到有无 Extra Field 的区别。先创建一个测试目录:
26+
27+
#+BEGIN_SRC shell :exports both :results value code
28+
mkdir -p /tmp/zip-test/demo
29+
echo "hello world" > /tmp/zip-test/demo/hello.txt
30+
chmod 755 /tmp/zip-test/demo/hello.txt
31+
#+END_SRC
32+
33+
分别用默认方式和 =-X= 选项打包:
34+
35+
#+BEGIN_SRC shell :exports both :results output
36+
cd /tmp/zip-test
37+
zip -r with-extra.zip demo/
38+
zip -rX without-extra.zip demo/
39+
#+END_SRC
40+
41+
#+RESULTS:
42+
: adding: demo/ (stored 0%)
43+
: adding: demo/hello.txt (stored 0%)
44+
: adding: demo/ (stored 0%)
45+
: adding: demo/hello.txt (stored 0%)
46+
47+
用 =zipinfo= 查看包含 Extra Field 的版本:
48+
49+
#+BEGIN_SRC shell :exports both :results output
50+
zipinfo /tmp/zip-test/with-extra.zip
51+
#+END_SRC
52+
53+
#+RESULTS:
54+
: Archive: /tmp/zip-test/with-extra.zip
55+
: Zip file size: 328 bytes, number of entries: 2
56+
: drwxr-xr-x 3.0 unx 0 bx stor 26-Apr-15 23:01 demo/
57+
: -rwxr-xr-x 3.0 unx 12 tx stor 26-Apr-15 23:01 demo/hello.txt
58+
: 2 files, 12 bytes uncompressed, 12 bytes compressed: 0.0%
59+
60+
注意输出中的 =unx= 和 =bx= / =tx= : =unx= 表示 Unix 格式的条目, =bx= / =tx= 中的 =x= 表示文件保留了可执行权限。
61+
62+
再看不含 Extra Field 的版本:
63+
64+
#+BEGIN_SRC shell :exports both :results output
65+
zipinfo /tmp/zip-test/without-extra.zip
66+
#+END_SRC
67+
68+
#+RESULTS:
69+
: Archive: /tmp/zip-test/without-extra.zip
70+
: Zip file size: 224 bytes, number of entries: 2
71+
: drwxr-xr-x 3.0 unx 0 b- stor 26-Apr-15 23:01 demo/
72+
: -rwxr-xr-x 3.0 unx 12 t- stor 26-Apr-15 23:01 demo/hello.txt
73+
: 2 files, 12 bytes uncompressed, 12 bytes compressed: 0.0%
74+
75+
用 =zipinfo -v= 可以看到更详细的差异:
76+
77+
#+BEGIN_SRC shell :exports both :results output
78+
zipinfo -v /tmp/zip-test/with-extra.zip | grep -E "(length of extra field|central-directory extra field|subfield with ID)"
79+
#+END_SRC
80+
81+
#+RESULTS:
82+
: length of extra field: 24 bytes
83+
: The central-directory extra field contains:
84+
: - A subfield with ID 0x5455 (universal time) and 5 data bytes.
85+
: - A subfield with ID 0x7875 (Unix UID/GID (any size)) and 11 data bytes:
86+
: length of extra field: 24 bytes
87+
: The central-directory extra field contains:
88+
: - A subfield with ID 0x5455 (universal time) and 5 data bytes.
89+
: - A subfield with ID 0x7875 (Unix UID/GID (any size)) and 11 data bytes:
90+
91+
对比不含 Extra Field 的版本:
92+
93+
#+BEGIN_SRC shell :exports both :results output
94+
zipinfo -v /tmp/zip-test/without-extra.zip | grep "extra field"
95+
#+END_SRC
96+
97+
#+RESULTS:
98+
: length of extra field: 0 bytes
99+
: length of extra field: 0 bytes
100+
101+
文件大小的差异也很明显:328 bytes vs 224 bytes,差了 104 bytes,正好是两个条目各 24 bytes 的 Extra Field(包含 Universal Time 和 Unix UID/GID)加上头部开销。
102+
103+
* 解决方案
104+
105+
用 =-X= (或其长选项 =--no-extra= )排除 Extra Field:
106+
107+
#+BEGIN_SRC shell
108+
zip -rX something.zip something/
109+
#+END_SRC
110+
111+
这样生成的 ZIP 文件就是纯标准格式,兼容性最好。
112+
113+
如果你的项目使用 Python 打包, =zipfile= 模块默认 *不会* 写入 Extra Field,所以无需特殊处理。但如果你显式设置了 =external_attr= 来保存 Unix 权限,则需要注意这个问题。
114+
115+
* 什么时候该保留 Extra Field
116+
117+
=zip -rX= 并不是万能的最佳实践。以下场景 *应该* 保留 Extra Field:
118+
119+
- *系统备份* :需要保留完整的 Unix 权限、所有者信息
120+
- *在 Linux 之间传输* :两端都支持 Extra Field,保留元数据有好处
121+
122+
而以下场景 *应该排除* Extra Field (用 =-X= ):
123+
124+
- *跨平台分享文件* :收件人可能在 macOS、Windows、iOS 等平台
125+
- *上传到网站/网盘* :不确定下载方会用什么工具解压
126+
- *只需文件内容* :不关心文件权限等元数据
127+
128+
一句话总结:分享给不确定的接收方时,用 =zip -rX= ;只在 Linux 内部流转时,保留默认行为即可。

0 commit comments

Comments
 (0)