Python 直接将字符串写入 tar 文件

42
7个回答

39
我认为这是可能的,通过使用TarInfo和TarFile.addfile来处理StringIO作为文件对象。非常粗糙,但可以运行。
import tarfile
import StringIO

tar = tarfile.TarFile("test.tar","w")

string = StringIO.StringIO()
string.write("hello")
string.seek(0)
info = tarfile.TarInfo(name="foo")
info.size=len(string.buf)
tar.addfile(tarinfo=info, fileobj=string)

tar.close()

2
你可以直接使用StringIO.StringIO("hello")来替换写入和寻找。 - mpenkov
这个过程与Python3和bytesIO对象类似吗? - proteneer
2
@proteneer:我相信在Python 3中,seek方法会给你一个二进制长度,同时它内部使用字符串len()函数,所以tarfile.copyfileobj函数将会失败并抛出OSError("end of file reached")异常。 - luckydonald

16

正如Stefano所指出的那样,你可以使用TarFile.addfileStringIO

import tarfile, StringIO

data = 'hello, world!'

tarinfo = tarfile.TarInfo('test.txt')
tarinfo.size = len(data)

tar = tarfile.open('test.tar', 'a')
tar.addfile(tarinfo, StringIO.StringIO(data))
tar.close()

你很可能还需要填充tarinfo的其他字段(例如mtimeuname等)。


"正如Stefano所指出的那样"是编辑吗?否则,我不明白你做了什么不同的事情。感谢您的回复。 - gatoatigrado
我认为在我回复时,Stefano还没有发布任何代码,他只是指出可以使用TarFile.addfile和StringIO。尽管我的记忆有点模糊。 - avakar
顺便说一句,是的,在你写这篇文章之后,@Stefano提供的详细信息已经在编辑中添加了。另一个给出相同答案的回答也几乎同时出现。 - mattdm

11

我在查找如何在Django中提供刚创建的内存中的.tgz归档文件时发现了这个,也许其他人会发现我的代码有用:

import tarfile
from io import BytesIO


def serve_file(request):
    out = BytesIO()
    tar = tarfile.open(mode = "w:gz", fileobj = out)
    data = 'lala'.encode('utf-8')
    file = BytesIO(data)
    info = tarfile.TarInfo(name="1.txt")
    info.size = len(data)
    tar.addfile(tarinfo=info, fileobj=file)
    tar.close()

    response = HttpResponse(out.getvalue(), content_type='application/tgz')
    response['Content-Disposition'] = 'attachment; filename=myfile.tgz'
    return response

6
Python 3的解决方案使用io.BytesIO。请确保将TarInfo.size设置为字节的长度,而不是字符串的长度。
如果给定单个字符串,则最简单的解决方案是调用.encode()来获取字节。在当今时代,您可能需要UTF-8,但如果收件人希望使用特定的编码方式,例如ASCII(即无多字节字符),请改用该编码方式。
import io
import tarfile

data = 'hello\n'.encode('utf8')
info = tarfile.TarInfo(name='foo.txt')
info.size = len(data)

with tarfile.TarFile('test.tar', 'w') as tar:
    tar.addfile(info, io.BytesIO(data))

如果您真的需要一个可写的字符串缓冲区,类似于@Stefano Borini在Python 2中接受的答案,那么解决方案是使用io.TextIOWrapper覆盖底层的io.BytesIO缓冲区。
import io
import tarfile

textIO = io.TextIOWrapper(io.BytesIO(), encoding='utf8')
textIO.write('hello\n')
bytesIO = textIO.detach()
info = tarfile.TarInfo(name='foo.txt')
info.size = bytesIO.tell()

with tarfile.TarFile('test.tar', 'w') as tar:
    bytesIO.seek(0)
    tar.addfile(info, bytesIO)

你可以不指定 utf8 进行编码,因为它是默认值: data = 'hello\n'.encode() - gerardw

4

仅作记录:
StringIO对象有一个.len属性。
不需要执行seek(0),也不需要对foo.buf执行len()。
无需将整个字符串保留下来以进行len()操作,更不用说自己进行计算了。

(也许在OP编写时没有这样的属性。)


StringIO 对象没有 len 属性。在 Python 3.8 中,代码 StringIO('foo').len 会引发异常 AttributeError: '_io.StringIO' object has no attribute 'len'。(也许在回答写作时还没有这个属性。) - Jeyekomon
显然,它在2.7的StringIO中没有记录,但存在(但不在cStringIO中)。https://dev59.com/W2445IYBdhLWcg3w1tgS - Alias_Knagg

3
在我的情况下,我想要从一个已存在的 tar 文件中读取数据,添加一些内容到其中,并将其写入一个新文件。就像这样:
for ti in tar_in:
    buf_in = tar.extractfile(ti)
    buf_out = io.BytesIO()
    size = buf_out.write(buf_in.read())
    size += buf_out.write(other data)
    buf_out.seek(0)
    ti.size = size
    tar_out.addfile(ti, fileobj=buf_out)

处理目录和链接需要额外的代码。


2
你需要使用TarInfo对象和addfile方法,而不是通常的add方法:
from StringIO import StringIO
from tarfile import open, TarInfo

s = "Hello World!"
ti = TarInfo("test.txt")
ti.size = len(s)

tf = open("testtar.tar", "w")
tf.addfile(ti, StringIO(s))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接