如何使用 python 以编程方式计算存档中的文件数

Posted

技术标签:

【中文标题】如何使用 python 以编程方式计算存档中的文件数【英文标题】:How to programmatically count the number of files in an archive using python 【发布时间】:2015-09-16 10:43:45 【问题描述】:

在我维护的程序中,它是这样完成的:

# 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()
    真的是唯一的方法吗?我似乎找不到any other command,但我不能只询问文件的数量似乎有点奇怪

    错误检查呢?将其修改为:

    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 的输出?

编辑:对 7z、rar、zip 档案(由 7z.exe 支持)感兴趣 - 但 7z 和 zip 对于初学者来说已经足够了

【问题讨论】:

您应该支持哪种类型的存档? 对于 zip,tar 检查 docs.python.org/2/library/zipfile.html 和 docs.python.org/2/library/tarfile.html @LoïcFaure-Lacroix:谢谢 - 已编辑。我绝对需要 7z... 也许看看这个? github.com/fancycode/pylzma/blob/master/py7zlib.py py7zlib 应该能够读取存档。之后,您可以使用类似于 zipfile 或 tarfile 的东西来提取其中的名称 (py7zlib.Archive7z.getnames)。 【参考方案1】:

在 Python 中计算 zip 存档中存档成员的数量:

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

它可以使用zlibbz2lzma 模块(如果可用)来解压缩存档。


要计算 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)

它可能支持gzipbz2lzma压缩,具体取决于Python版本。

您可以找到一个为 7z 存档提供类似功能的第 3 方模块。


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

支持无限输出。


您能否解释一下为什么 buffsize=-1(而不是您之前的回答中的 buffsize=1:***.com/a/30984882/281545)

bufsize=-1 表示在 Python 2 上使用默认 I/O 缓冲区大小而不是 bufsize=0(无缓冲)。这是对 Python 2 的性能提升。在最近的 Python 3 版本中是默认设置。如果在 bufsize 未更改为 bufsize=-1 的某些早期 Python 3 版本上,您可能会得到简短的读取(丢失数据)。

此答案以块的形式读取,因此流被完全缓冲以提高效率。 The solution you've linked 是面向行的。 bufsize=1 表示“行缓冲”。否则与bufsize=-1 的区别很小。

还有 read_chunk = partial(p.stdout.read, 1

它等同于read_chunk = lambda: p.stdout.read(1&lt;&lt;15),但通常提供更多的内省。用于implement wc -l in Python efficiently。

【讨论】:

嘿,谢谢!您能否解释一下为什么 buffsize=-1(而不是您之前的回答中的 buffsize=1:***.com/a/30984882/281545)-以及read_chunk = partial(p.stdout.read, 1 &lt;&lt; 15) 给我们带来了什么?真的,这个buffsize 对我来说是一个谜(以及我的谷歌尝试)。同时,由于我已经捆绑了7z.exe(并且我希望显示确切的错误),我想我会接受我的答案(除非我做了任何公然愚蠢的事情) @Mr_and_Mrs_D:您可能应该将7z.exe 中的错误处理作为一个单独的问题询问:包括以下内容:7z 是否提供了一组退出代码来指示各种错误,例如, zip utility does? 7z 会将其错误消息打印到 stderr 还是将它们与 stdout 中的存档成员列表混合在一起? 我会找时间做,一定要提到你 - 谢谢 :) - 退出代码:sevenzip.osdn.jp/chm/cmdline/exit_codes.htm @Mr_and_Mrs_D:所有代码都应该按原样工作,即不需要-scsUTF-8 -sccUTF-8。注意:基于check_output() 的版本可能比count_files_7z()Popen() 使用更多的内存,但错误处理是相同的——您可以使用count_files_7z() 实现运行示例——尽管第二个变体不存储输出直到遇到错误(这就是它使用较少内存的原因)。 @Mr_and_Mrs_D:否则您可能会收到另一种语言的消息(取决于您的语言环境),并且使用英文单词“files”、“folders”的正则表达式可能会失败。【参考方案2】:

由于我已经将 7z.exe 与应用程序捆绑在一起,我当然希望避免使用第三方库,而我确实需要解析 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://***.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

@JFSebastien 在Python Popen - wait vs communicate vs CalledProcessError 中的错误检查


我的最终(ish)基于接受的答案 - 可能不需要 unicode,现在保留它,因为我在任何地方都使用它。还保留了正则表达式(我可能会扩展,我见过像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))

将根据我的发现对其进行编辑。

【讨论】:

您可以在这里使用for line in out: 或更好的for line in io.TextIOWrapper(out, encoding='utf-8'):(将字节解码为Unicode 并启用通用换行模式)。不要使用if len(container),而是使用if container(空容器在 Python 中为 False)。 line.startswith('Error:') 可以用来代替 regErrMatch 正则表达式。您确定7z 将其错误打印到标准输出(很不幸)?请follow pep-8 naming conventions unless you have a specific reason not to。 是的 7z 在 stdout (...) 中打印其输出 - TextIOWrapper 我会看看。 regErrMatch:我可能需要详细说明错误的正则表达式。 PEP8 - 它是遗留代码,慢慢 PEP8 'ing 它(另见:youtube.com/watch?v=wf-BqAjZb8M - 虽然 79 个字符,我完全同意)

以上是关于如何使用 python 以编程方式计算存档中的文件数的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式搜索 CNN 头条新闻的存档?

如何以编程方式创建 RAM 磁盘?

如何以编程方式阻止应用程序读取计算机文件?

以编程方式打开 zip 文件菜单 C#

如何在 C/C++ 中以编程方式查找“保存的游戏”文件夹?

如何以编程方式从 Python 中的融合模式注册表中获取模式