在Python脚本中读取tar文件内容而不解压缩它。

106

我有一个tar文件,其中包含许多文件。我需要编写一个Python脚本,读取文件的内容并给出总字符数,包括字母、空格、换行符等所有内容,而无需解压tar文件。


你如何在不将它们提取到其他地方的情况下计算字符/字母/空格/所有内容? - YOU
23
这正是所问的问题。 - Erik Kaplun
5个回答

147

你可以使用 getmembers()

>>> import  tarfile
>>> tar = tarfile.open("test.tar")
>>> tar.getmembers()

在那之后,你可以使用extractfile()函数将成员提取为文件对象。这只是一个例子。

import tarfile,os
import sys
os.chdir("/tmp/foo")
tar = tarfile.open("test.tar")
for member in tar.getmembers():
    f=tar.extractfile(member)
    content=f.read()
    print "%s has %d newlines" %(member, content.count("\n"))
    print "%s has %d spaces" % (member,content.count(" "))
    print "%s has %d characters" % (member, len(content))
    sys.exit()
tar.close()
在上面的例子中,使用文件对象 f ,您可以使用 read()readlines() 等函数。

28
"for member in tar.getmembers()" 可以改为 "for member in tar",它是一个生成器或迭代器(我不确定哪个)。但它会一次获取一个成员。 - huggie
2
我之前遇到过类似的问题,但是tarfile模块似乎会占用我的内存,即使我使用了“'r|'”选项。 - devsnd
2
啊,我解决了。假设你按照huggie的提示编写代码,你必须定期“清理”成员列表。因此,给定上面的代码示例,这将是tar.members = []。更多信息请参见:http://bit.ly/JKXrg6 - devsnd
当将tar.getmembers()放入for member in tar.getmembers()循环中时,tar.getmembers()会被调用多次吗? - Haifeng Zhang
1
在执行“f=tar.extractfile(member)”之后,您是否需要关闭f? - bolei
显示剩余4条评论

14

您需要使用tarfile模块。具体地,您需要使用TarFile类的实例来访问文件,然后使用TarFile.getnames()访问名称。

 |  getnames(self)
 |      Return the members of the archive as a list of their names. It has
 |      the same order as the list returned by getmembers().

如果你想要阅读内容,那么可以使用这个方法。

 |  extractfile(self, member)
 |      Extract a member from the archive as a file object. `member' may be
 |      a filename or a TarInfo object. If `member' is a regular file, a
 |      file-like object is returned. If `member' is a link, a file-like
 |      object is constructed from the link's target. If `member' is none of
 |      the above, None is returned.
 |      The file-like object is read-only and provides the following
 |      methods: read(), readline(), readlines(), seek() and tell()

9

之前,这篇文章展示了一个例子,即将成员名称和成员列表一起“dict(zip(()”'ing”,这很愚蠢并且会导致对存档的过度读取。为了实现相同的功能,我们可以使用字典推导式:

index = {i.name: i for i in my_tarfile.getmembers()}

关于如何使用tarfile的更多信息

提取tar文件成员

#!/usr/bin/env python3
import tarfile

my_tarfile = tarfile.open('/path/to/mytarfile.tar')

print(my_tarfile.extractfile('./path/to/file.png').read())

索引tar文件

#!/usr/bin/env python3
import tarfile
import pprint

my_tarfile = tarfile.open('/path/to/mytarfile.tar')

index = my_tarfile.getnames()  # a list of strings, each members name
# or
# index = {i.name: i for i in my_tarfile.getmembers()}

pprint.pprint(index)

索引、读取和动态添加tar文件

#!/usr/bin/env python3

import tarfile
import base64
import textwrap
import random

# note, indexing a tar file requires reading it completely once
# if we want to do anything after indexing it, it must be a file
# that can be seeked (not a stream), so here we open a file we
# can seek
my_tarfile = tarfile.open('/path/to/mytar.tar')


# tarfile.getmembers is similar to os.stat kind of, it will
# give you the member names (i.name) as well as TarInfo attributes:
#
# chksum,devmajor,devminor,gid,gname,linkname,linkpath,
# mode,mtime,name,offset,offset_data,path,pax_headers,
# size,sparse,tarfile,type,uid,uname
#
# here we use a dictionary comprehension to index all TarInfo
# members by the member name
index = {i.name: i for i in my_tarfile.getmembers()}

print(index.keys())

# pick your member
# note: if you can pick your member before indexing the tar file,
# you don't need to index it to read that file, you can directly
# my_tarfile.extractfile(name)
# or my_tarfile.getmember(name)

# pick your filename from the index dynamically
my_file_name = random.choice(index.keys())

my_file_tarinfo = index[my_file_name]
my_file_size = my_file_tarinfo.size
my_file_buf = my_tarfile.extractfile( 
    my_file_name
    # or my_file_tarinfo
)

print('file_name: {}'.format(my_file_name))
print('file_size: {}'.format(my_file_size))
print('----- BEGIN FILE BASE64 -----'
print(
    textwrap.fill(
        base64.b64encode(
            my_file_buf.read()
        ).decode(),
        72
    )
)
print('----- END FILE BASE64 -----'

具有重复成员的tar文件

在某些情况下,我们可能会遇到一个以奇怪方式创建的tar文件,例如将许多版本的同一文件附加到同一个tar存档中,我们需要仔细处理这种情况。我已经注明了哪些成员包含哪些文本,假设我们想要第四个(索引为3)成员,即“capturetheflag\n”。

tar -tf mybadtar.tar 
mymember.txt  # "version 1\n"
mymember.txt  # "version 1\n"
mymember.txt  # "version 2\n"
mymember.txt  # "capturetheflag\n"
mymember.txt  # "version 3\n"

#!/usr/bin/env python3

import tarfile
my_tarfile = tarfile.open('mybadtar.tar')

# >>> my_tarfile.getnames()
# ['mymember.txt', 'mymember.txt', 'mymember.txt', 'mymember.txt', 'mymember.txt']

# if we use extracfile on a name, we get the last entry, I'm not sure how python is smart enough to do this, it must read the entire tar file and buffer every valid member and return the last one

# >>> my_tarfile.extractfile('mymember.txt').read()
# b'version 3\n'

# >>> my_tarfile.extractfile(my_tarfile.getmembers()[3]).read()
# b'capturetheflag\n'

或者我们可以遍历tar文件 #!/usr/bin/env python3

import tarfile
my_tarfile = tarfile.open('mybadtar.tar')
# note, if we do anything to the tarfile object that will 
# cause a full read, the tarfile.next() method will return none,
# so call next in a loop as the first thing you do if you want to
# iterate

while True:
    my_member = my_tarfile.next()
    if not my_member:
        break
    print((my_member.offset, mytarfile.extractfile(my_member).read,))

# (0, b'version 1\n')
# (1024, b'version 1\n')
# (2048, b'version 2\n')
# (3072, b'capturetheflag\n')
# (4096, b'version 3\n')


    

这给了我“不允许向后查找”的异常。 - KIC
在我上面的示例中,我们必须读取文件两次,一次用于索引它(列出它包含的所有文件),第二次通过名称提取我们想要的文件,这是tar结构的结果/特征。如果您需要从tar中提取文件,并且只能读取一次(例如从流中),则必须事先知道文件名。如果您知道成员名称,并且只想提取该成员,则可以通过直接使用myArchive.extractfile('my/member/name.png') 在第一遍通过时提取它。 - ThorSummoner

0

你可以使用tarfile.list() 例如:

filename = "abc.tar.bz2"
with open( filename , mode='r:bz2') as f1:
    print(f1.list())

在获取这些数据之后,您可以对其进行操作或将此输出写入文件并根据您的要求进行任何操作。


0
 import tarfile

 targzfile = "path to the file"

 tar = tarfile.open(targzfile)

 for item in tar.getnames():

     if "README.txt" in item:

       file_content = tar.extractfile(item).read()

       fileout = open("output file path", 'wb')

       fileout.write(file_content)

       fileout.close()

       break

感谢您为Stack Overflow社区做出的贡献。这可能是一个正确的答案,但如果您能提供代码的额外解释,让开发人员能够理解您的推理过程,那将非常有用。对于不太熟悉语法或难以理解概念的新开发人员来说,这尤其有帮助。您是否可以编辑您的答案,包含更多细节,以造福整个社区? - Jeremy Caney
感谢您为Stack Overflow社区做出的贡献。这可能是一个正确的答案,但如果您能提供代码的额外解释,让开发人员能够理解您的推理过程,那将非常有用。对于那些对语法不太熟悉或者难以理解概念的新开发人员来说,这尤其有帮助。您是否可以编辑您的答案,以包含额外的细节,以造福社区? - undefined

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