2015-06-29 4 views
10

В программе я утверждаю, это делается как в:Как программно подсчитать количество файлов в архиве, используя питон

# count the files in the archive 
length = 0 
command = ur'"%s" l -slt "%s"' % (u'path/to/7z.exe', srcFile) 
ins, err = Popen(command, stdout=PIPE, stdin=PIPE, 
       startupinfo=startupinfo).communicate() 
ins = StringIO.StringIO(ins) 
for line in ins: length += 1 
ins.close() 
  1. Является ли это действительно единственный способ? Кажется, я не могу найти any other command, но кажется немного странным, что я не могу просто запросить количество файлов
  2. Как насчет проверки ошибок? Достаточно ли это изменить:

    proc = Popen(command, stdout=PIPE, stdin=PIPE, 
          startupinfo=startupinfo) 
    out = proc.stdout 
    # ... count 
    returncode = proc.wait() 
    if returncode: 
        raise Exception(u'Failed reading number of files from ' + srcFile) 
    

    или должен ли я фактически анализировать выходные данные Popen?

EDIT: заинтересован в 7z, RAR, ZIP архивы (которые поддерживаются 7z.exe) - но 7z и застежка-молния будет достаточно для начала

+1

Какой тип архива вы предполагаете поддержка? –

+1

Для zip, tar проверьте https://docs.python.org/2/library/zipfile.html и https://docs.python.org/2/library/tarfile.html –

+0

@ LoïcFaure-Lacroix: Спасибо - отредактирован. Мне определенно нужно 7z ... –

ответ

7

Чтобы подсчитать количество членов архива в почтовый индекс архив в Python:

#!/usr/bin/env python 
import sys 
from contextlib import closing 
from zipfile import ZipFile 

with closing(ZipFile(sys.argv[1])) as archive: 
    count = len(archive.infolist()) 
print(count) 

Он может использовать zlib, bz2, lzma модули, если имеется, распаковать архив.


Для подсчета количества обычных файлов в архиве дегтя:

#!/usr/bin/env python 
import sys 
import tarfile 

with tarfile.open(sys.argv[1]) as archive: 
    count = sum(1 for member in archive if member.isreg()) 
print(count) 

Он может поддерживать gzip, bz2 и lzma сжатие в зависимости от версии Python.

Вы можете найти сторонний модуль, который обеспечит аналогичную функциональность для архивов 7z.


Чтобы получить количество файлов в архиве, используя 7z утилиты:

import os 
import subprocess 

def count_files_7z(archive): 
    s = subprocess.check_output(["7z", "l", archive], env=dict(os.environ, LC_ALL="C")) 
    return int(re.search(br'(\d+)\s+files,\s+\d+\s+folders$', s).group(1)) 

Вот версия, которая может использовать меньше памяти, если имеется много файлов в архиве:

import os 
import re 
from subprocess import Popen, PIPE, CalledProcessError 

def count_files_7z(archive): 
    command = ["7z", "l", archive] 
    p = Popen(command, stdout=PIPE, bufsize=1, env=dict(os.environ, LC_ALL="C")) 
    with p.stdout: 
     for line in p.stdout: 
      if line.startswith(b'Error:'): # found error 
       error = line + b"".join(p.stdout) 
       raise CalledProcessError(p.wait(), command, error) 
    returncode = p.wait() 
    assert returncode == 0 
    return int(re.search(br'(\d+)\s+files,\s+\d+\s+folders', line).group(1)) 

Пример:

import sys 

try: 
    print(count_files_7z(sys.argv[1])) 
except CalledProcessError as e: 
    getattr(sys.stderr, 'buffer', sys.stderr).write(e.output) 
    sys.exit(e.returncode) 

Для подсчета количества строк в выводе общего подпроцесса:

from functools import partial 
from subprocess import Popen, PIPE, CalledProcessError 

p = Popen(command, stdout=PIPE, bufsize=-1) 
with p.stdout: 
    read_chunk = partial(p.stdout.read, 1 << 15) 
    count = sum(chunk.count(b'\n') for chunk in iter(read_chunk, b'')) 
if p.wait() != 0: 
    raise CalledProcessError(p.returncode, command) 
print(count) 

Он поддерживает неограниченный выход.


Could you explain why buffsize=-1 (as opposed to buffsize=1 in your previous answer: stackoverflow.com/a/30984882/281545)

bufsize=-1 означает использование по умолчанию I/O размер буфера вместо bufsize=0 (небуферизован) на Python 2. Это является повышение производительности на Python 2. Это по умолчанию в последних версиях Python 3. Вы можете получить короткое чтение (потерять данные), если на некоторых более ранних версиях Python 3, где bufsize не изменен на bufsize=-1.

Этот ответ читается в кусках, и поэтому поток полностью забуферирован для эффективности. The solution you've linked - ориентирован на линию. bufsize=1 означает «строка буферизирована». В противном случае существует минимальная разница с bufsize=-1.

and also what the read_chunk = partial(p.stdout.read, 1 << 15) buys us ?

Это эквивалентно read_chunk = lambda: p.stdout.read(1<<15), но обеспечивает более интроспекцию в целом. Используется для implement wc -l in Python efficiently.

+0

Привет, спасибо! Не могли бы вы объяснить, почему buffsize = -1 (в отличие от buffsize = 1 в вашем предыдущем ответе: http://stackoverflow.com/a/30984882/281545), а также то, что 'read_chunk = partial (p.stdout.read, 1 << 15) 'покупает нас? На самом деле этот «баффизм» является для меня загадкой (и мои попытки Google). Между тем, поскольку у меня уже есть пакет «7z.exe» (и я хотел бы, чтобы отображалась точная ошибка), я думаю, что поеду с моим ответом (за исключением случаев, когда я сделал что-то откровенно глупое) –

+0

@Mr_and_Mrs_D: вы должны, вероятно, спросить о обработка ошибок в '7z.exe' в качестве отдельного вопроса: включить следующее: делает ли' 7z' набор достижений кодов выхода для обозначения различных ошибок, например, ['zip' utility does] (http: //linux.die .net/человек/1/застежка-молния)? Выводит ли '7z 'свои сообщения об ошибках в stderr или смешивает их с списком членов архива в stdout? – jfs

+0

Сделаю, когда найду какое-то время и обязательно скажу тебе - спасибо :) - коды выхода: http://sevenzip.osdn.jp/chm/cmdline/exit_codes.htm –

1

Поскольку я уже 7z.exe в комплекте с приложением, и я, конечно, хочу, чтобы избежать LIB третьей стороны, в то время как мне нужно разобрать RAR и 7z архивы, я думаю, что я пойду с:

regErrMatch = re.compile(u'Error:', re.U).match # needs more testing 
r"""7z list command output is of the form: 
    Date  Time Attr   Size Compressed Name 
------------------- ----- ------------ ------------ ------------------------ 
2015-06-29 21:14:04 ....A  <size>    <filename> 
where ....A is the attribute value for normal files, ....D for directories 
""" 
reFileMatch = re.compile(ur'(\d|:|-|\s)*\.\.\.\.A', re.U).match 

def countFilesInArchive(srcArch, listFilePath=None): 
    """Count all regular files in srcArch (or only the subset in 
    listFilePath).""" 
    # https://stackoverflow.com/q/31124670/281545 
    command = ur'"%s" l -scsUTF-8 -sccUTF-8 "%s"' % ('compiled/7z.exe', srcArch) 
    if listFilePath: command += u' @"%s"' % listFilePath 
    proc = Popen(command, stdout=PIPE, startupinfo=startupinfo, bufsize=-1) 
    length, errorLine = 0, [] 
    with proc.stdout as out: 
     for line in iter(out.readline, b''): 
      line = unicode(line, 'utf8') 
      if errorLine or regErrMatch(line): 
       errorLine.append(line) 
      elif reFileMatch(line): 
       length += 1 
    returncode = proc.wait() 
    if returncode or errorLine: raise StateError(u'%s: Listing failed\n' + 
     srcArch + u'7z.exe return value: ' + str(returncode) + 
     u'\n' + u'\n'.join([x.strip() for x in errorLine if x.strip()])) 
    return length 

Ошибка проверки, как в Python Popen - wait vs communicate vs CalledProcessError по @JFSebastien


Мой последний (МОГ) на основании принятого ответа - юникода может быть не нужен, держали его сейчас, как я использую его везде. Также хранится регулярное выражение (которое я могу расширить, я видел такие вещи, как re.compile(u'^(Error:.+|.+ Data Error?|Sub items Errors:.+)',re.U). Придется смотреть в check_output и CalledProcessError.

def countFilesInArchive(srcArch, listFilePath=None): 
    """Count all regular files in srcArch (or only the subset in 
    listFilePath).""" 
    command = [exe7z, u'l', u'-scsUTF-8', u'-sccUTF-8', srcArch] 
    if listFilePath: command += [u'@%s' % listFilePath] 
    proc = Popen(command, stdout=PIPE, stdin=PIPE, # stdin needed if listFilePath 
       startupinfo=startupinfo, bufsize=1) 
    errorLine = line = u'' 
    with proc.stdout as out: 
     for line in iter(out.readline, b''): # consider io.TextIOWrapper 
      line = unicode(line, 'utf8') 
      if regErrMatch(line): 
       errorLine = line + u''.join(out) 
       break 
    returncode = proc.wait() 
    msg = u'%s: Listing failed\n' % srcArch.s 
    if returncode or errorLine: 
     msg += u'7z.exe return value: ' + str(returncode) + u'\n' + errorLine 
    elif not line: # should not happen 
     msg += u'Empty output' 
    else: msg = u'' 
    if msg: raise StateError(msg) # consider using CalledProcessError 
    # number of files is reported in the last line - example: 
    #        3534900  325332 75 files, 29 folders 
    return int(re.search(ur'(\d+)\s+files,\s+\d+\s+folders', line).group(1)) 

будет редактировать это с моими выводами.

+1

вы можете использовать 'for line in out:' здесь или лучше' для строки в io.TextIOWrapper (out, encoding = 'utf-8'): '(для декодирования байтов в Unicode и для включения универсального режима новых строк). Не используйте 'if len (container)', вместо этого используйте 'if container' (пустые контейнеры False в Python). 'line.startswith ('Error:')' может использоваться вместо регулярного выражения regErrMatch'. Вы уверены, что '7z' печатает свои ошибки в stdout (это прискорбно)? Пожалуйста, [следуйте соглашениям об именах pep-8, если у вас нет конкретной причины] (https://www.python.org/dev/peps/pep-0008/#naming-conventions). – jfs

+0

Да 7z выводит свой вывод в stdout (...) - TextIOWrapper. Я посмотрю. regErrMatch: Мне может потребоваться уточнить регулярное выражение для ошибок. PEP8 - это устаревший код, медленно PEP8 его (см. Также: https://www.youtube.com/watch?v=wf-BqAjZb8M - хотя 79 символов, я полностью согласен) –

 Смежные вопросы

  • Нет связанных вопросов^_^