Go archive/tar 怎样读取 tar 目录项的类型标志
来源:17golang原创
时间:2026-10-18 06:36:04 433浏览 收藏
Go 读取 tar 成员类型时,先调用 tar.Reader.Next() 取得 Header,再比较 hdr.Typeflag 与 tar.TypeDir、tar.TypeReg 等常量。如果这里只想判断“是不是目录”,可以使用 hdr.FileInfo().IsDir();需要区分普通文件、链接或其他类型时,直接 switch Typeflag 更合适。
这里的“目录项”指 tar 归档中的成员,不是本机目录扫描返回的 os.DirEntry。本文只生成归档清单,不解包、不创建目录,也不根据链接目标继续读取磁盘。
官方文档:https://pkg.go.dev/archive/tar
做归档清单时,先明确要区分几种成员
仅统计目录数量,比较 TypeDir 就够了;需要把归档内容交给后续业务,最好保留细分类别。普通文件、硬链接和符号链接都可能“不是目录”,但三者的数据和目标语义并不相同。把所有非目录项都当成普通文件,会在链接或特殊成员上做出错误选择。
// hdr必须来自一次成功的Next调用,不能忽略其错误。
if hdr.Typeflag == tar.TypeDir {
// 此处只处理归档内的目录成员,不触碰磁盘。
fmt.Printf("目录:%q\n", hdr.Name)
}
上面是需放进读取循环的片段,不是独立程序。TypeDir 的值是字符 '5',不是整数 5;应使用具名常量避免混淆。Typeflag 也不是 Mode 权限位,不要用 0755 这样的权限数字判断成员类型。

Typeflag、IsDir 与名称后缀怎样选择
| 方式 | 适合的任务 | 不足 |
|---|---|---|
| switch hdr.Typeflag | 区分目录、普通文件、硬链接、符号链接及其他成员 | 需要保留未知类型分支 |
| hdr.FileInfo().IsDir() | 只需要目录布尔值的通用文件信息接口 | false不能说明一定是普通文件 |
| 检查 Name 是否以 / 结尾 | 观察命名约定或研究旧格式 | 不是完整的tar类型判断方案 |
建议把 Typeflag 作为 tar 语义分类入口,FileInfo 作为需要 fs.FileInfo 时的适配。FileInfo 来源仍是 Header,不会去本地文件系统判断路径是否存在,也不会跟随归档里的符号链接检查目标是不是目录。
名称末尾的斜杠与目录有关联,但不要据此覆盖读取器给出的类别。即使某个生产工具始终在目录名后加斜杠,这也是该工具的输出习惯,不是你可以忽略 Typeflag 的理由。
一段只读清单程序,未知标志也不丢失
下面完整程序从标准输入读取未压缩 tar 流,逐项输出名称、类型、原始返回标志、Size 和 Linkname。代码未实际运行,展示的是接口使用方法,不含运行日志。
package main
import (
"archive/tar"
"fmt"
"io"
"os"
)
// entryKind 保留不同成员类型,不把非目录统称为普通文件。
func entryKind(flag byte) string {
switch flag {
case tar.TypeDir:
return "目录"
case tar.TypeReg, tar.TypeRegA:
// TypeRegA是旧式值,Reader通常已将它规范化。
return "普通文件"
case tar.TypeLink:
return "硬链接"
case tar.TypeSymlink:
return "符号链接"
case tar.TypeChar:
return "字符设备"
case tar.TypeBlock:
return "块设备"
case tar.TypeFifo:
return "FIFO"
default:
return "其他类型"
}
}
// listEntries 只读取元数据,不创建文件,不跟随链接。
func listEntries(r io.Reader, w io.Writer) error {
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
return nil // 这是归档结束,不是单个成员内容结束。
}
if err != nil {
return fmt.Errorf("读取tar成员: %w", err)
}
// %q转义名称中的换行等字符;十六进制标志便于保留未知类型。
if _, err := fmt.Fprintf(w, "%q 类型=%s 标志=0x%02x 大小=%d 链接=%q\n",
hdr.Name, entryKind(hdr.Typeflag), hdr.Typeflag, hdr.Size, hdr.Linkname); err != nil {
return fmt.Errorf("输出清单: %w", err)
}
// 不读取正文;下一次Next会丢弃当前成员未读的数据。
}
}
func main() {
// 标准输入、输出由调用方管理;本程序不关闭它们。
if err := listEntries(os.Stdin, os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
读者可将完整程序保存为 main.go,并自行准备可信的 sample.tar。下面命令只用于读取,不会提取成员:
# 将未压缩tar送入清单程序,不会写出归档成员。 go run main.go
输出中的 0x35 对应字符 '5',0x30 对应字符 '0'。将标志打印成十六进制,比打印不可见的零字节更容易观察。名称和链接使用 %q,避免其中的换行或控制字符直接破坏清单展示。
Next 返回 io.EOF 表示归档已结束;其他错误直接返回,不能用 continue 掩盖。当前程序对包括 ErrInsecurePath 在内的非 EOF 错误采用停止策略,不使用带错误返回的 Header。输出端错误也会向上传递。

读取器返回的标志,不一定等于磁盘上的原始字节
旧格式的 TypeRegA 是零字节,当前文档已将该常量标为弃用,编写新 Header 应使用 TypeReg。Go 的读取器实现会处理这种旧式标志:名称以 / 结尾时改为 TypeDir,否则改为 TypeReg。因此,正常经过 Reader.Next 后,通常不需要依靠 TypeRegA 分支猜目录。
清单程序仍显式兼容 TypeRegA,是为了让 entryKind 对单独构造的 Header 值也有可读解释;它不意味着 Next 一定会原样暴露旧字节。如果任务是取证式逐字节查看归档头,本例这种逻辑清单接口并不适合。
PAX 扩展记录、GNU 长名称记录等也会由读取器处理,不能假定每次 Next 都对应一个完全未经解释的原始头块。这里不扩展实现二进制解析器,只依据 Next 提供的 Header 解释成员。
读取器实现:https://go.dev/src/archive/tar/reader.go
链接不是目标内容,目录也不是空普通文件
tar.TypeLink 表示硬链接,tar.TypeSymlink 表示符号链接,链接目标保存在 Linkname。目标字符串不是本程序应该打开的磁盘路径;只做清单时,保留它即可。只看 Size 是否为零无法区分目录、空普通文件与链接。
对于 TypeDir、TypeLink、TypeSymlink 等头信息类型,Reader.Read 不会因此提供目标文件内容。清单程序无需把正文读到内存;Next 会自动丢弃前一成员尚未读取的数据。但底层流仍可能发生读取与跳过,不能将“没复制正文”理解成零I/O,也不能据此宣称极大归档处理没有资源代价。
未知标志使用“其他类型”并保留标志值。这个输出策略只适合观察;如果未来增加解包功能,应明确允许哪些类别,不能把 default 自动变成普通文件写入。Typeflag 分类本身也不解决路径约束、链接处理和大小上限问题。
用几种小成员复现分类,再决定接口
下列是供读者自行构造样本时核对的预期,不是已执行测试报告:
| 输入成员语义 | 清单应关注的值 | 不能据此推断 |
|---|---|---|
| 显式TypeDir目录 | Typeflag为TypeDir | 磁盘上已创建目录 |
| 零长度TypeReg文件 | 普通文件,Size为0 | Size为0就是目录 |
| TypeSymlink成员 | 符号链接,保留Linkname | 链接目标存在或可访问 |
| TypeLink成员 | 硬链接,保留Linkname | 目标内容可以直接从该成员读取 |
| 损坏归档或输出端失败 | 返回错误并停止 | 已成功完整列出全包 |
为什么 IsDir 为 false 仍不能直接读成文件?
因为非目录包含链接、设备及其他类型。需要普通文件时应明确判断 TypeReg;对稀疏文件等扩展类别是否支持,应另外定义业务策略。
为什么循环里没有 io.Copy?
任务只是读取元数据,Next 会负责跨过剩余正文。需要消费成员内容时才在成功取得 Header 后读取当前 Reader,并处理该成员读取错误。
tar.gz 可以直接传给这个程序吗?
不可以,当前输入约定是未压缩 tar。压缩容器需要先由相应解压读取层提供 tar 字节流,本例不把压缩格式识别与成员分类混在一起。
最终选择可以很简短:只问“是不是目录”用 IsDir 或 TypeDir;需要完整 tar 类别用 Typeflag;名称后缀只作为观察信息。让分类、内容读取和落盘操作分别承担自己的职责,清单逻辑就不会被解包副作用拖复杂。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
252 收藏
-
Golang · Go教程 | 5分钟前 | 错误处理 · Go教程 · database/sql · Go commit 事务 rollback database/sql sql.Tx BeginTx249 收藏
-
171 收藏
-
Golang · Go教程 | 10分钟前 | Go教程 · database/sql · Go database/sql rows.Next Rows.Scan sql.RawBytes 二进制字段159 收藏
-
477 收藏
-
485 收藏
-
335 收藏
-
366 收藏
-
287 收藏
-
200 收藏
-
480 收藏
-
173 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习