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

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 这样的权限数字判断成员类型。

Header字段与目录普通文件和链接类别的静态对应关系
图1:tar Header 类型结构说明图。Typeflag 表示类别,Name 和 Linkname 提供不同元数据,不是解包执行流程。

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。输出端错误也会向上传递。

Reader Next与Header元数据和错误输出的静态关系
图2:只读清单接口结构说明图。Reader 提供成员 Header,清单读取 Typeflag;EOF 和读取错误分别处理,不是运行截图。

读取器返回的标志,不一定等于磁盘上的原始字节

旧格式的 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为0Size为0就是目录
TypeSymlink成员符号链接,保留Linkname链接目标存在或可访问
TypeLink成员硬链接,保留Linkname目标内容可以直接从该成员读取
损坏归档或输出端失败返回错误并停止已成功完整列出全包

为什么 IsDir 为 false 仍不能直接读成文件?

因为非目录包含链接、设备及其他类型。需要普通文件时应明确判断 TypeReg;对稀疏文件等扩展类别是否支持,应另外定义业务策略。

为什么循环里没有 io.Copy?

任务只是读取元数据,Next 会负责跨过剩余正文。需要消费成员内容时才在成功取得 Header 后读取当前 Reader,并处理该成员读取错误。

tar.gz 可以直接传给这个程序吗?

不可以,当前输入约定是未压缩 tar。压缩容器需要先由相应解压读取层提供 tar 字节流,本例不把压缩格式识别与成员分类混在一起。

最终选择可以很简短:只问“是不是目录”用 IsDir 或 TypeDir;需要完整 tar 类别用 Typeflag;名称后缀只作为观察信息。让分类、内容读取和落盘操作分别承担自己的职责,清单逻辑就不会被解包副作用拖复杂。

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