2015-06-29 11 views
10

Im Programm, das ich es halten geschieht wie in:Wie programm der Anzahl der Dateien in einem Archiv zählt Python mit

# 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. Ist es wirklich der einzige Weg? Ich kann nicht scheinen any other command zu finden, aber es scheint ein bisschen seltsam, dass ich nicht einfach nach der Anzahl der Dateien fragen kann
  2. Was ist mit Fehlerprüfung? Wäre es genug, dies zu ändern:

    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) 
    

    oder sollte ich tatsächlich die Ausgabe von Popen analysieren?

EDIT: Interesse an 7z, rar, zip-Archive (die von 7z.exe unterstützt werden) - aber 7z und zip würden für den Anfang

+1

Welche Art des Archivs sollen Sie Unterstützung? –

+1

Für zip, Tar überprüfen https://docs.python.org/2/library/zipfile.html und https://docs.python.org/2/library/tarfile.html –

+0

@ LoïcFaure-Lacroix: Danke Ich habe definitiv 7z ... –

Antwort

7

genug sein, um die Anzahl der Archiv-Mitglieder in einer Zip zählen Archiv in 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) 

Es kann zlib, bz2, lzma Module, falls verfügbar, verwenden Sie das Archiv entpacken.


Um die Anzahl der regulären Dateien in einem tar-Archiv zählen:

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

Es gzip unterstützen kann, bz2 und lzma Kompression je nach Version von Python.

Sie könnten ein 3rd-Party-Modul finden, das eine ähnliche Funktionalität für 7z-Archive bieten würde.


Um die Anzahl der Dateien in einem Archiv mit 7z Dienstprogramm zu erhalten:

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)) 

Hier Version, die weniger Speicher verwenden kann, wenn viele Dateien im Archiv sind:

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)) 

Beispiel:

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) 

Um die Anzahl der Zeilen in der Ausgabe eines generischen subprocess zählen:

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) 

Es unbegrenzte Ausgabe unterstützt.


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

bufsize=-1 Mittel verwenden, um die Standard-I/O-Puffergröße statt bufsize=0 (ungepufferte) auf Python 2. auf Python eine Leistungssteigerung ist 2. Es standardmäßig auf den letzten Python 3 Versionen ist. Sie könnten einen kurzen Lesevorgang (Datenverlust) erhalten, wenn in früheren Python 3-Versionen nicht in bufsize=-1 geändert wurde.

Diese Antwort wird in Chunks eingelesen und daher ist der Stream für die Effizienz vollständig gepuffert. The solution you've linked ist linienorientiert. bufsize=1 bedeutet "Linie gepuffert". Ansonsten gibt es einen minimalen Unterschied zu bufsize=-1.

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

Es entspricht read_chunk = lambda: p.stdout.read(1<<15) aber mehr Selbstbeobachtung im Allgemeinen zur Verfügung stellt. Es ist an implement wc -l in Python efficiently gewohnt.

+0

Hey danke! Können Sie erklären, warum buffsize = -1 ist (im Gegensatz zu buffsize = 1 in Ihrer vorherigen Antwort: http://Stackoverflow.com/a/30984882/281545) - und auch, was der 'read_chunk = partially (p.stdout.read, 1 << 15) 'kauft uns? Wirklich diese "Buffsize" ist ein Mysterium für mich (und meine Google-Versuche). Inzwischen habe ich schon '7z.exe' gebündelt (und ich hätte gerne den genauen Fehler angezeigt) Ich denke ich werde mit meiner Antwort gehen (außer wenn ich irgendwas eklat dumm gemacht habe) –

+0

@Mr_and_Mrs_D: du solltest wohl nach dem fragen Fehlerbehandlung in '7z.exe' als separate Frage: schließen Sie folgendes mit ein:' 7z' liefert einen Reach-Satz von Exit-Codes, um verschiedene Fehler anzuzeigen, wie zB ['zip'-Dienstprogramm] (http: //linux.die .net/man/1/zip)? Sendet '7z' seine Fehlermeldungen an stderr oder mischt er sie mit der Archiv-Mitgliederliste im stdout? – jfs

+0

Wird tun, wenn ich etwas Zeit finde und sicher sein, Sie zu erwähnen - danke :) - die Exit-Codes: http://sevenzip.osdn.jp/chm/cmdline/exit_codes.htm –

1

Da ich bereits 7z.exe mit der App gebündelt und ich will sicher einen Dritten lib zu vermeiden, während ich denke ich rar und 7z-Archive zu analysieren brauchen werde ich gehen mit:

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 

Fehler wie in Python Popen - wait vs communicate vs CalledProcessError von @JFSebastien Überprüfung


Meine letzte (ish), basierend auf akzeptierte Antwort - Unicode nicht benötigt werden, hielten es für jetzt, wie ich sie überall benutzen. Auch gehalten regex (was ich erweitern kann, ich habe Dinge gesehen, wie re.compile(u'^(Error:.+|.+ Data Error?|Sub items Errors:.+)',re.U). Wird in check_output und CalledProcessError suchen.

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)) 

dies mit meinen Erkenntnissen bearbeiten Will.

+1

können Sie 'für Line-In:' hier oder besser 'für Zeile in io.TextIOWrapper (out, encoding = 'utf-8') verwenden:' (um Bytes in Unicode zu dekodieren und den universellen Zeilenumbruch-Modus zu aktivieren). Verwenden Sie nicht 'if len (container)', sondern 'if container' (leere Container sind in Python falsch). 'line.startswith ('Error:')' könnte anstelle der 'regErrMatch'-Regex verwendet werden. Sind Sie sicher, dass '7z' seine Fehler auf stdout druckt (es ist bedauerlich)? Bitte folgen Sie den Namenskonventionen von pep-8, es sei denn, Sie haben einen bestimmten Grund, dies nicht zu tun (https://www.python.org/dev/peps/pep-0008/#naming-conventions). – jfs

+0

Ja 7z druckt seine Ausgabe in stdout (...) - TextIOWrapper werde ich mir ansehen. regErrMatch: Ich muss vielleicht den regulären Ausdruck für die Fehler näher ausführen. PEP8 - es ist Legacy-Code, langsam PEP8 'inging es (siehe auch: https://www.youtube.com/watch?v=wf-BqAjZb8M - obwohl 79 Zeichen, ich bin voll und ganz einverstanden) –