чтение содержимого tar-файла без его распаковки в скрипте python

У меня есть файл tar, в котором есть несколько файлов. Мне нужно написать скрипт Python, который будет читать содержимое файлов и подсчитывать общее количество символов, включая общее количество букв, пробелов, символов новой строки и т. д., без распаковки tar-файла.


person randeepsp    schedule 07.01.2010    source источник
comment
Как вы можете считать символы/буквы/пробелы/все, что угодно, не извлекая их куда-то еще?   -  person YOU    schedule 07.01.2010
comment
именно такой вопрос задан.   -  person Erik Kaplun    schedule 15.01.2013


Ответы (4)


вы можете использовать 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() и т. д.

person ghostdog74    schedule 07.01.2010
comment
для члена в tar.getmembers() можно изменить на для члена в tar, который является либо генератором, либо итератором (я не уверен, что именно). Но он получает член по одному. - person huggie; 28.12.2011
comment
У меня только что была похожая проблема, но модуль tarfile, кажется, съедает мой баран, хотя я использовал опцию 'r|'. - person devsnd; 21.05.2012
comment
Ах. Я решил это. Предполагая, что вы напишете код, как подсказывает Хагги, вам придется время от времени очищать список участников. Итак, учитывая приведенный выше пример кода, это будет tar.members = []. Дополнительная информация здесь: bit.ly/JKXrg6 - person devsnd; 21.05.2012
comment
будет ли tar.getmembers() вызываться несколько раз, если поместить его в цикл for member in tar.getmembers()? - person Haifeng Zhang; 04.03.2015
comment
что делать, если файлы вложены в подпапки? обычные операции os.path.isdir() не работают - person ; 14.01.2017
comment
После того, как вы сделаете f=tar.extractfile(member), вам нужно также закрыть f? - person bolei; 11.05.2017
comment
Это решение создает проблему безопасности. Проверьте эту ошибку для получения дополнительной информации. - person MeanEYE; 10.10.2018
comment
Поскольку extractfile не предоставляет атрибут encoding, если вам нужен текстовый поток, вы можете сделать f = codecs.getreader("utf-8")(f). - person Thomas Ahle; 18.07.2019
comment
это решение имеет плохую производительность, когда в tar-файле много файлов. Сначала необходимо разрешить все имена файлов. - person Jingpeng Wu; 01.11.2019

вам нужно использовать модуль 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()
person Stefano Borini    schedule 07.01.2010

Ранее в этом посте был показан пример dict(zip(()) объединения имен членов и списков членов вместе, это глупо и приводит к чрезмерному чтению архива, чтобы добиться того же, мы можем использовать понимание словаря:

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

Дополнительная информация о том, как использовать tarfile

Извлечь элемент tarfile

#!/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 -----'

tarfile с повторяющимися членами

в случае, если у нас есть tar, который был создан странно, в этом примере, добавляя много версий одного и того же файла в один и тот же tar-архив, мы можем работать с этим осторожно, я аннотировал, какие члены содержат какой текст, скажем, мы нужен четвертый (индекс 3) участник, захватите флаг\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')


    
person ThorSummoner    schedule 26.04.2014
comment
это дает мне поиск назад не допускается исключение - person KIC; 30.01.2021
comment
@KIC в моем примере выше мы должны прочитать файл дважды, один раз, чтобы проиндексировать его (перечислить все файлы, которые он содержит), и второй раз, чтобы извлечь файл, который мы хотим, по имени, это следствие / особенность того, как структурированы tar . Если вам нужно извлечь файл из tar, который вы можете прочитать только один раз (например, из потока), вы должны заранее знать имя файла. если вы знаете имя члена и хотите извлечь только один член, вы можете извлечь его при первом проходе, используя myArchive.extractfile('my/member/name.png') напрямую - person ThorSummoner; 31.01.2021

вы можете использовать tarfile.list() например:

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

после получения этих данных. вы можете манипулировать этим выводом или записывать его в файл и делать все, что вам нужно.

person ChandraShekhar Mahto    schedule 10.04.2020