【问题标题】:Overwriting previously extracted files instead of creating new ones覆盖以前提取的文件而不是创建新文件
【发布时间】:2015-06-20 07:49:12
【问题描述】:

有一些库用于通过 Python 提取存档文件,例如 gzip、zipfile 库、rarfile、tarfile、patool 等。我发现其中一个库 (patool) 由于其跨格式功能而特别有用从某种意义上说,它可以提取几乎任何类型的存档,包括最流行的存档,例如 ZIP、GZIP、TAR 和 RAR。

使用 patool 提取存档文件非常简单:

patoolib.extract_archive( "Archive.zip",outdir="Folder1")

其中"Archive.zip" 是存档文件的路径,"Folder1" 是存储解压文件的目录路径。

提取工作正常。问题是,如果我为完全相同的存档文件再次运行相同的代码,相同的提取文件将存储在同一个文件夹中,但名称略有不同(第一次运行时的文件名,第二次运行时的文件名 1,文件名 11 在第三个等等。

如果目录中已经存在同名文件,我需要代码覆盖提取的文件。

这个extract_archive 函数看起来非常小——它只有这两个参数,一个verbosity 参数和一个program 参数,用于指定您要用来提取档案的程序。

修改: Nizam Mohamed 的回答记录了 extract_archive 函数实际上正在覆盖输出。我发现这部分正确 - 该函数覆盖 ZIP 文件,但不是我所追求的 GZ 文件。对于 GZ 文件,该函数仍会生成新文件。

修改 Padraic Cunningham 的回答建议使用 master source 。因此,我下载了该代码并将旧的 patool 库脚本替换为链接中的脚本。结果如下:

os.listdir()
Out[11]: ['a.gz']

patoolib.extract_archive("a.gz",verbosity=1,outdir=".")
patool: Extracting a.gz ...
patool: ... a.gz extracted to `.'.
Out[12]: '.'

patoolib.extract_archive("a.gz",verbosity=1,outdir=".")
patool: Extracting a.gz ...
patool: ... a.gz extracted to `.'.
Out[13]: '.'

patoolib.extract_archive("a.gz",verbosity=1,outdir=".")
patool: Extracting a.gz ...
patool: ... a.gz extracted to `.'.
Out[14]: '.'

os.listdir()
Out[15]: ['a', 'a.gz', 'a1', 'a2']

因此,extract_archive 函数在每次执行时都会创建新文件。在a.gz 下存档的文件实际上与a 的名称不同。

【问题讨论】:

  • @nathanhayfield,不先解压怎么知道文件名?
  • 好点,我的错误
  • patool (1.7) 覆盖。
  • @NizamMohamed 你确定吗?我有 patoolib 1.7 版,它不会覆盖。

标签: python file extract overwrite ziparchive


【解决方案1】:

如果该功能不存在,您需要添加它。这方面的一个例子是用你自己的一个包装函数:

import os
from shutil import rmtree

def overwriting_extract_archive(zippath, outpath, **kwargs): 
    if os.path.exists(outpath):
        shutil.rmtree(outpath)
    patoolib.extract_archive(zippath, outdir=outpath, **kwargs)

如果您想逐个文件检查并将新输出与现有输出合并,这当然会成为一个更复杂的问题,但如果正如您所描述的那样(第二次运行),这应该可以工作。

【讨论】:

  • 我同意。代码位于github.com/wummel/patool/blob/…,我隐约猜测也许您可以为后端程序的命令行参数数据库添加猴子补丁,为您关心的每种格式添加一个“--overwrite”选项(它提供这个设施首先)。 OP 描述的行为似乎并不在 Python 代码中。
  • @tripleee 类似的东西可能会起作用,但猴子补丁被认为不如包装函数“Pythonic”。我也倾向于认为越简单越好,而且修改库可能会在以后给你带来麻烦。
  • @tripleee 我认为是这个函数引起的:github.com/wummel/patool/blob/…
【解决方案2】:

正如您所说,patoolib 旨在成为通用存档工具。

可以使用 patool 创建、提取、测试、列出、比较、搜索和重新打包各种存档类型。 patool 的优势在于它可以简单地处理存档文件,而无需记住无数的程序和选项。

通用提取行为与特定提取行为

这里的问题是extract_archive 没有公开修改归档工具的底层默认行为的能力。

对于 .zip 扩展名,patoolib 将使用解压缩。您可以通过将 -o 作为选项传递给命令行界面来获得提取存档的所需行为。即unzip -o ... 但是,这是解压缩的特定命令行选项,并且每个存档实用程序都会更改。

例如 tar 提供了一个覆盖选项,但没有与 zip 等效的缩短命令行。即tar --overwritetar -o 没有预期的效果。

要解决此问题,您可以向作者提出功能请求,或使用替代库。不幸的是,patoolib 的口头禅需要扩展所有提取实用程序函数,然后实现底层提取器自己的覆盖命令选项。

对 patoolib 的示例更改

patoolib.programs.unzip

def extract_zip (archive, compression, cmd, verbosity, outdir, overwrite=False):
    """Extract a ZIP archive."""
    cmdlist = [cmd]
    if verbosity > 1:
        cmdlist.append('-v')
    if overwrite:
        cmdlist.append('-o')
    cmdlist.extend(['--', archive, '-d', outdir])
    return cmdlist

patoolib.programs.tar

def extract_tar (archive, compression, cmd, verbosity, outdir, overwrite=False):
    """Extract a TAR archive."""
    cmdlist = [cmd, '--extract']
    if overwrite:
        cmdlist.append('--overwrite')
    add_tar_opts(cmdlist, compression, verbosity)
    cmdlist.extend(["--file", archive, '--directory', outdir])
    return cmdlist

每个程序更新都不是小事,每个程序都不一样!

猴子修补覆盖行为

所以您决定不改进 patoolib 源代码...我们可以覆盖 extract_archive 的行为以最初查找现有目录,将其删除,然后调用原始 extract_archive

你可以在你的模块中包含这个代码,如果很多模块需要它,也许坚持__init__.py

import os
import patoolib
from shutil import rmtree


def overwrite_then_extract_archive(archive, verbosity=0, outdir=None, program=None):
    if outdir:
        if os.path.exists(outdir):
            shutil.rmtree(outdir)
    patoolib.extract_archive(archive, verbosity, outdir, program)

patoolib.extract_archive = overwrite_then_extract_archive

现在当我们调用extract_archive() 时,我们拥有overwrite_then_extract_archive() 的功能。

【讨论】:

  • 我不熟悉猴子补丁或改进库源代码。你的意思是如果有人改进了源代码,我可以立即访问并下载更新后的库的改进版本?
  • 理想情况下,您应该自己为库做出必要的更改。 (如果您不想这样做,这完全可以理解!)如果您在使用 extract_archive 函数之前包含最后一个代码段,它将为您提供所需的覆盖行为。查看最后一行 patoolib.extract_archive = overwrite_then_extract_archive 它用覆盖行为修补了以前的行为。
  • 我想我只是添加一个关于猴子补丁性质的一般答案的参考:stackoverflow.com/questions/5626193/what-is-monkey-patch
  • 由于存在一些潜在的破坏性后果,我认为值得指出的是,在建议的猴子补丁实现中删除整个输出目录并不完全相同,因为它也会删除其中不属于存档的所有文件,这与仅覆盖其中的文件不太一样。
【解决方案3】:

如果提取失败,在提取存档时覆盖现有文件可能会使目标目录处于不一致状态。

如果提取失败,在提取之前删除目标目录可能会导致文件丢失。

我认为最好的方法是提取到临时目录并同步到目标目录。

对于此解决方案,模块 dirsync 是必需的。但dirsync snycs 仅当 mtimectime 默认较新,而不是文件大小。

import os
import sys
from shutil import rmtree
from patoolib import extract_archive
from dirsync import sync

archive = ''
dst_dir = ''

try:
    tmp_dir = extract_archive(archive)
except Exception as e:
    print('extract_archive error {}'.format(e))
    sys.exit(1)
else:
    try:
        sync(tmp_dir,dst_dir,'sync',options=['modtime'])
    except Exception as e:
        print('updating {} from {} failed, error {}'.format(dst_dir,tmp_dir,e))
        sys.exit(1)
    else:
        sys.exit(0)
finally:
   if os.path.exists(tmp_dir):
       rmtree(tmp_dir)

【讨论】:

  • 我明白了 - 因为您使用了 ZIP 文件,所以它正在与您合作。如果您传递一个 GZ 文件,该函数将不会覆盖。这是一个很好的发现,但问题仍然没有解决。
【解决方案4】:

如果您使用 outdir 传递目录,则使用 master source 它将覆盖 包括 .gz 文件:

from patoolib import extract_archive

extract_archive("foo.tar.gz",verbosity=1,outdir=".")

你会看到:

patool: ... /pathto/.foo.tar.gz extracted to `.'.

它不会覆盖的唯一方法是,如果您没有传递第二次提取的目录,您会得到如下内容:

 ...foo.tar.gz extracted to `foo-1.0.2.tar1' ...(local file exists).

从 bash 运行,7z 每次都要求确认覆盖:

In [9]: ls
foo.gz

In [10]: from patoolib import extract_archive

In [11]: extract_archive("foo.gz",verbosity=1,outdir=".")
patool: Extracting foo.gz ...
patool: running /usr/bin/7z e -o. -- foo.gz

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_IE.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)

Processing archive: foo.gz

Extracting  foo

Everything is Ok

Size:       12
Compressed: 36
patool: ... foo.gz extracted to `.'.
Out[11]: '.'

In [12]: extract_archive("foo.gz",verbosity=1,outdir=".")
patool: Extracting foo.gz ...
patool: running /usr/bin/7z e -o. -- foo.gz

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_IE.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)

Processing archive: foo.gz

file ./foo
already exists. Overwrite with 
foo?
(Y)es / (N)o / (A)lways / (S)kip all / A(u)to rename all / (Q)uit? y
Extracting  foo

Everything is Ok

Size:       12
Compressed: 36
patool: ... foo.gz extracted to `.'.
Out[12]: '.'

In [13]: extract_archive("foo.gz",verbosity=1,outdir=".")
patool: Extracting foo.gz ...
patool: running /usr/bin/7z e -o. -- foo.gz

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_IE.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)

Processing archive: foo.gz

file ./foo
already exists. Overwrite with 
foo?
(Y)es / (N)o / (A)lways / (S)kip all / A(u)to rename all / (Q)uit? y
Extracting  foo

Everything is Ok

Size:       12
Compressed: 36
patool: ... foo.gz extracted to `.'.
Out[13]: '.'

In [14]: ls
foo  foo.gz

解压 tar.gz 文件:

In [1]: from patoolib import extract_archive

In [2]: for x in range(4):
            extract_archive("/home/padraic/Downloads/pycrypto-2.0.1.tar.gz",verbosity=1,outdir=".")
   ...:     
patool: Extracting /home/padraic/Downloads/pycrypto-2.0.1.tar.gz ...
patool: running /bin/tar --extract -z --file /home/padraic/Downloads/pycrypto-2.0.1.tar.gz --directory .
patool: ... /home/padraic/Downloads/pycrypto-2.0.1.tar.gz extracted to `.'.
patool: Extracting /home/padraic/Downloads/pycrypto-2.0.1.tar.gz ...
patool: running /bin/tar --extract -z --file /home/padraic/Downloads/pycrypto-2.0.1.tar.gz --directory .
patool: ... /home/padraic/Downloads/pycrypto-2.0.1.tar.gz extracted to `.'.
patool: Extracting /home/padraic/Downloads/pycrypto-2.0.1.tar.gz ...
patool: running /bin/tar --extract -z --file /home/padraic/Downloads/pycrypto-2.0.1.tar.gz --directory .
patool: ... /home/padraic/Downloads/pycrypto-2.0.1.tar.gz extracted to `.'.
patool: Extracting /home/padraic/Downloads/pycrypto-2.0.1.tar.gz ...
patool: running /bin/tar --extract -z --file /home/padraic/Downloads/pycrypto-2.0.1.tar.gz --directory .
patool: ... /home/padraic/Downloads/pycrypto-2.0.1.tar.gz extracted to `.'.

In [3]: ls
pycrypto-2.0.1/

再次全部被覆盖,我能看到的唯一解释是,默认情况下调用任何应用程序来解压缩您的 .gz 文件不会覆盖或提示,而是每次稍微更改名称时都会创建新文件。

【讨论】:

  • 我下载了主源代码并用新文件替换了旧的 patool 库文件,但仍然没有覆盖。有关详细信息,请参阅我添加到问题中的最后一次编辑。
  • @ArditS.,您从什么操作系统以及从哪里运行它?
  • @ArditS。什么应用程序解压缩您的 .gz 文件?
  • 我使用的是 Windows。根据应用程序,我不确定如何获取该信息,但我可以看出我用于打开 GZ 文件的默认 Windows 应用程序是 WinRar。
【解决方案5】:

似乎我找到了解决每次执行patool 库的extract_archive 方法时创建新文件问题的解决方法。 需要强调的是,该方法能够覆盖/跳过先前为其他存档扩展提取的文件,但不能用于 Gun Zipped 文件。

我注意到,当提取任何 Gun Zipped 文件 (.gz) 时,提取的文件与存档具有相同的名称,但没有任何扩展名。为了更好地说明这一点,如果将名称从 X.gz 更改为 Y.gz,然后解压缩存档,解压缩的文件将具有名称“Y”。 因此,我能够实现一个简单的条件:

import os,patoolib
if "name" not in os.listdir():
    patoolib.extract_archive("name.gz",outdir="C:\")

这似乎解决了我的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多