这是获取Python的独特版本的文件名的最佳方法吗?

时间:2008-10-08 15:50:22

标签: python filenames

仍在'潜入'Python,并希望确保我不会忽略某些东西。我写了一个脚本,从几个zip文件中提取文件,并将提取的文件保存在一个目录中。为了防止重复的文件名被覆盖,我写了这个小函数 - 我只是想知道是否有更好的方法来做到这一点? 谢谢!

def unique_filename(file_name):
counter = 1
file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext')
while os.path.isfile(file_name): 
    file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1]
    counter += 1
return file_name

我真的要求文件在一个目录中,并且在我的情况下编号重复是绝对可以接受的,所以我不是在寻找一种更健壮的方法(因为我认为任何指针是受欢迎的),但只是为了确保实现的目标是以正确的方式完成。

6 个答案:

答案 0 :(得分:22)

一个问题是上面的代码中存在竞争条件,因为测试存在和创建文件之间存在差距。可能存在的安全隐患这个(想想有人恶意插入一个符号链接,他们将无法覆盖敏感文件,但你的程序具有更高的权限运行的可能),这样的攻击是为什么之类的东西os.tempnam( )已被弃用。

要解决它,最好的方法是实际尝试创建文件,以便在失败时获得异常,并在成功时返回实际打开的文件对象。这可以通过传递os.O_CREAT和os.O_EXCL标志来实现低级os.open函数。打开后,返回您创建的实际文件(以及可选的文件名)。例如,这里修改了你的代码以使用这种方法(返回(文件,文件名)元组):

def unique_file(file_name):
    counter = 1
    file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext')
    while 1:
        try:
            fd = os.open(file_name, os.O_CREAT | os.O_EXCL | os.O_RDRW)
            return os.fdopen(fd), file_name
        except OSError:
            pass
        file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1]
        counter += 1

[编辑] 实际上,更好的办法是为你处理上述问题,可能是使用tempfile模块,尽管你可能会失去对命名的控制权。以下是使用它的示例(保持类似的界面):

def unique_file(file_name):
    dirname, filename = os.path.split(file_name)
    prefix, suffix = os.path.splitext(filename)

    fd, filename = tempfile.mkstemp(suffix, prefix+"_", dirname)
    return os.fdopen(fd), filename

>>> f, filename=unique_file('/home/some_dir/foo.txt')
>>> print filename
/home/some_dir/foo_z8f_2Z.txt

这种方法唯一的缺点是,你总是会得到一个文件名,在它的一些随机字符,因为没有尝试创建一个未修改的文件(/home/some_dir/foo.txt)第一。 您可能还想查看tempfile.TemporaryFile和NamedTemporaryFile,它们将执行上述操作,并在关闭时自动从磁盘中删除。

答案 1 :(得分:6)

是的,对于可读但唯一的文件名,这是一个很好的策略。

一项重要更改:您应该将os.path.isfile替换为os.path.lexists!正如现在所写,如果有一个名为/foo/bar.baz的目录,你的程序将尝试使用新文件覆盖它(这将无效)...因为isfile只检查文件而不是目录。 lexists检查目录,符号链接等...基本上如果有任何原因无法创建文件名。

编辑:@Brian提供了一个更好的答案,在竞争条件方面更安全,更强大。

答案 2 :(得分:2)

两个小小的变化......

base_name, ext = os.path.splitext(file_name) 

你得到两个具有不同含义的结果,给它们不同的名称。

file_name = "%s_%d%s" % (base_name, str(counter), ext)

它不会更快或更短。但是,当您想要更改文件名模式时,模式位于一个位置,并且更容易使用。

答案 3 :(得分:1)

如果你想要可读的名​​字,这看起来是一个很好的解决方案 有例程可以返回唯一的文件名。临时文件,但它们产生长随机的名字。

答案 4 :(得分:1)

如果你不在乎可读性,uuid.uuid4()就是你的朋友。

import uuid

def unique_filename(prefix=None, suffix=None):
    fn = []
    if prefix: fn.extend([prefix, '-'])
    fn.append(str(uuid.uuid4()))
    if suffix: fn.extend(['.', suffix.lstrip('.')])
    return ''.join(fn)

答案 5 :(得分:0)

怎么样

def ensure_unique_filename(orig_file_path):    
    from time import time
    import os

    if os.path.lexists(orig_file_path):
        name, ext = os.path.splitext(orig_file_path)
        orig_file_path = name + str(time()).replace('.', '') + ext

    return orig_file_path

time()以毫秒为单位返回当前时间。结合原始文件名,即使在复杂的多线程情况下也是如此。