2015-06-29 7 views
10

Nel programma io sostengo che è fatto come in:Come contare di codice il numero di file in un archivio utilizzando pitone

# 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. E 'davvero l'unico modo? Non riesco a trovare any other command ma sembra un po 'strano che non posso solo chiedere il numero di file
  2. E il controllo degli errori? Sarebbe sufficiente modificarlo in:

    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) 
    

    o devo effettivamente analizzare l'uscita di Popen?

EDIT: interessato a 7z, rar, zip archivi (che sono supportati da 7z.exe) - ma 7z e zip sarebbe abbastanza per cominciare

+1

Quale tipo di archivio si fa a supporto? –

+1

Per zip, tar controlla https://docs.python.org/2/library/zipfile.html e https://docs.python.org/2/library/tarfile.html –

+0

@ LoïcFaure-Lacroix: Grazie - modificato Ho sicuramente bisogno di 7z ... –

risposta

7

Per contare il numero di membri dell'archivio in una zip archivio 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) 

può utilizzare zlib, bz2, lzma moduli se disponibile, per decomprimere l'archivio.


per contare il numero di file normali in un archivio tar:

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

Può sostenere gzip, bz2 e lzma compressione a seconda della versione di Python.

È possibile trovare un modulo di terze parti che fornisce funzionalità simili per gli archivi 7z.


Per ottenere il numero di file in un archivio con 7z utility:

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

Ecco versione che possono utilizzare meno memoria se ci sono molti file in archivio:

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

Esempio:

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) 

per contare il numero di linee in uscita di un sottoprocesso generico:

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) 

Esso supporta l'uscita illimitata.


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

bufsize=-1 mezzi utilizzano la dimensione predefinita O I/tampone invece di bufsize=0 (unbuffered) su Python 2. Si tratta di un incremento delle prestazioni su Python 2. Si tratta di default sui recenti Python 3 versioni. È possibile ottenere una lettura breve (perdita di dati) se in alcune versioni precedenti di Python 3 in cui bufsize non viene modificato in bufsize=-1.

Questa risposta viene letta in blocchi e pertanto lo stream è completamente bufferizzato per l'efficienza. The solution you've linked è orientato alla linea. bufsize=1 significa "riga bufferizzata". C'è una differenza minima da bufsize=-1 in caso contrario.

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

È equivalente read_chunk = lambda: p.stdout.read(1<<15) ma fornisce più introspezione in generale. È usato per implement wc -l in Python efficiently.

+0

Ehi, grazie! Potresti spiegare perché buffsize = -1 (al contrario di buffsize = 1 nella tua risposta precedente: http://stackoverflow.com/a/30984882/281545) - e anche cosa 'read_chunk = partial (p.stdout.read, 1 << 15) 'ci compra? In realtà questo 'buffsize' è un mistero per me (e per i miei tentativi di google). Nel frattempo, poiché ho già '7z.exe' in bundle (e mi piacerebbe avere l'errore esatto visualizzato) penso che andrò con la mia risposta (tranne se ho fatto qualcosa di palesemente stupido) –

+0

@Mr_and_Mrs_D: dovresti probabilmente chiedere gestione degli errori in '7z.exe' come una domanda a parte: include:' '7z' fornisce un insieme di raggi di uscita per indicare vari errori come ad esempio, [utilità' zip'] (http: //linux.die .net/uomo/1/zip)? '7z' stampa i suoi messaggi di errore su stderr o li mescola con la lista dei membri di archivio nello stdout? – jfs

+0

Lo farò quando trovo un po 'di tempo e assicurati di menzionarti - grazie :) - i codici di uscita: http://sevenzip.osdn.jp/chm/cmdline/exit_codes.htm –

1

Poiché ho già 7z.exe in bundle con l'applicazione e io sicuramente vuole evitare una terza parte lib, mentre io ho bisogno di analizzare gli archivi RAR e 7Z penso io andrò con:

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 

controllo degli errori come in Python Popen - wait vs communicate vs CalledProcessError da @JFSebastien


mio finale (ish) in base a risposta accettata - unicode può non essere necessario, lo tenne per ora come lo uso in tutto il mondo. Anche tenuto regex (che io possa espandersi, ho visto le cose come re.compile(u'^(Error:.+|.+ Data Error?|Sub items Errors:.+)',re.U). Dovrà esaminare check_output e 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)) 

redigerà questo con i miei risultati.

+1

si potrebbe usare 'per line-out:' qui o meglio 'per la riga in io.TextIOWrapper (out, encoding = 'utf-8'):' (per decodificare i byte in Unicode e abilitare la modalità universale newlines). Non usare 'se len (container)', usare 'if container' (i contenitori vuoti sono False in Python). 'line.startswith ('Error:')' potrebbe essere usato al posto della regex 'regErrMatch'. Sei sicuro che '7z' stampa i suoi errori sullo stdout (è sfortunato)? Per favore, [segui le convenzioni di denominazione pep-8 a meno che tu non abbia una ragione specifica per non farlo] (https://www.python.org/dev/peps/pep-0008/#naming-conventions). – jfs

+0

Sì 7z stampa il suo output in stdout (...) - TextIOWrapper I avrà un aspetto. regErrMatch: potrebbe essere necessario elaborare l'espressione regolare per gli errori. PEP8 - è un codice legacy, lentamente PEP8 'it it (vedi anche: https://www.youtube.com/watch?v=wf-BqAjZb8M - anche se 79 caratteri, sono pienamente d'accordo) –

Problemi correlati