6

如何将文件附加到 Go 中现有的 tar 存档?我在文档中看不到有关如何执行此操作的任何明显内容。

我有一个已经创建的 tar 文件,我想在它关闭后添加更多内容。

编辑

更改文档中的示例并按照给出的答案,我仍然没有得到预期的结果。前三个文件正在写入 tar 但是当我关闭并再次打开文件以写入它时,新文件永远不会被写入。代码运行良好。我不知道我错过了什么。

以下代码为我提供了一个 tar 文件,其中包含三个文件:readme.txt、gopher.txt、todo.txt。foo.bar 永远不会被写入。

package main

import (
    "archive/tar"
    "log"
    "os"
)

func main() {
    f, err := os.Create("/home/jeff/Desktop/test.tar")
    if err != nil {
        log.Fatalln(err)
    }

    tw := tar.NewWriter(f)

    var files = []struct {
        Name, Body string
    }{
        {"readme.txt", "This archive contains some text files."},
        {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
        {"todo.txt", "Get animal handling licence."},
    }
    for _, file := range files {
        hdr := &tar.Header{
            Name: file.Name,
            Size: int64(len(file.Body)),
        }
        if err := tw.WriteHeader(hdr); err != nil {
            log.Fatalln(err)
        }
        if _, err := tw.Write([]byte(file.Body)); err != nil {
            log.Fatalln(err)
        }
    }
    if err := tw.Close(); err != nil {
        log.Fatalln(err)
    }
    f.Close()

    // Open up the file and append more things to it

    f, err = os.OpenFile("/home/jeff/Desktop/test.tar", os.O_APPEND|os.O_WRONLY, os.ModePerm)
    if err != nil {
        log.Fatalln(err)
    }
    tw = tar.NewWriter(f)

    test := "this is a test"

    hdr := &tar.Header{
        Name: "foo.bar",
        Size: int64(len(test)),
    }

    if err := tw.WriteHeader(hdr); err != nil {
        log.Fatalln(err)
    }

    if _, err := tw.Write([]byte(test)); err != nil {
        log.Fatalln(err)
    }

    if err := tw.Close(); err != nil {
        log.Fatalln(err)
    }
    f.Close()

}
4

3 回答 3

14

tar文件规范指出:

tar 存档由一系列 512 字节的记录组成。每个文件系统对象都需要一个头记录,该记录存储基本元数据(路径名、所有者、权限等)和零个或多个包含任何文件数据的记录。档案的结尾由两条完全由零字节组成的记录指示。

添加这两个零填充记录的 Go 实现发生在这里

要绕过tar文件格式预告片(基本上是 1024 字节),您可以替换以下行:

f, err = os.OpenFile("/home/jeff/Desktop/test.tar", os.O_APPEND|os.O_WRONLY, os.ModePerm)
if err != nil {
    log.Fatalln(err)
}
tw = tar.NewWriter(f)

和:

f, err = os.OpenFile("/home/jeff/Desktop/test.tar", os.O_RDWR, os.ModePerm)
if err != nil {
    log.Fatalln(err)
}
if _, err = f.Seek(-1024, os.SEEK_END); err != nil {
    log.Fatalln(err)
}
tw = tar.NewWriter(f)

它打开文件读/写(而不是追加/只写),然后在文件结束前寻找 1024 个字节并从那里写入。

它有效,但它一个可怕的黑客。

编辑:在tar更好地理解文件规范之后,我不再相信这是一个黑客。

完整代码:http ://play.golang.org/p/0zRScmY4AC

于 2013-08-20T08:56:44.213 回答
3

它只是一个编写器接口,因此在写入文件头后向其写入字节。

import (
  "archive/tar"
  "os"
)

f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModePerm)
if err != nil {
// handle error here
}

hdr := tar.Header{}
// populate your header
tw := tar.NewWriter(f)
// append a file
tw.WriteHeader(hdr)
tw.Write(content_of_file_as_bytes)

http://golang.org/pkg/archive/tar/#Writer告诉你所有你需要知道的。

编辑:事实证明,当 tar 文件关闭时,它会在末尾写入一个预告片。因此,即使您正在将新数据写入 tar 存档,也不会通过该预告片读取它。因此,看起来您必须先读取 tar 存档,然后将整个存档重写到磁盘,这是次优的。该软件包不支持附加到它们的必要内容,所以这是我现在可以推荐的最好的。

于 2013-08-19T22:41:49.100 回答
0

我发现@Intermernet 接受的解决方案基本上是正确的,除了存档末尾的填充通常是任意的(除非您也控制作者)。

据我所知,这对我有用:

var lastFileSize, lastStreamPos int64
tr := tar.NewReader(output)
for {
    hdr, err := tr.Next()
    if err == io.EOF {
        break
    }
    if err != nil {
        return err
    }
    lastStreamPos, err = output.Seek(0, io.SeekCurrent)
    if err != nil {
        return err
    }
    lastFileSize = hdr.Size
}

const blockSize = 512
newOffset := lastStreamPos + lastFileSize
newOffset += blockSize - (newOffset % blockSize) // shift to next-nearest block boundary
_, err := output.Seek(newOffset, io.SeekStart)
if err != nil {
    return err
}

tw := tar.NewWriter(output)
defer tw.Close()

// ... proceed to write to tw as usual ...

我不是 100% 确定为什么会这样。但基本思路是扫描存档,找到最后一个文件开头的偏移量,然后跳过它,然后对齐最近的块边界,然后从那里开始覆盖。

于 2021-12-31T07:19:54.767 回答