16

まだPythonに「飛び込んで」いて、何かを見落としていないことを確認したい. 複数の zip ファイルからファイルを抽出し、抽出したファイルを 1 つのディレクトリにまとめて保存するスクリプトを作成しました。重複したファイル名が上書きされるのを防ぐために、私はこの小さな関数を書きました。ありがとう!

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

ファイルが単一のディレクトリにあることが本当に必要であり、私の場合は重複に番号を付けることは間違いなく受け入れられるので、より堅牢な方法を探しているわけではありません(ポインターは歓迎されると思います)が、念のためこれが達成することは、正しい方法で行われることです。

4

6 に答える 6

23

1 つの問題は、存在のテストとファイルの作成の間にギャップがあるため、上記のコードに競合状態があることです。これにはセキュリティ上の影響があるかもしれません (誰かが悪意を持って重要なファイルにシンボリックリンクを挿入して上書きすることはできませんが、より高い特権で実行されているプログラムは可能です) これらのような攻撃が 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 も参照してください。これらは上記を実行し、閉じたときにディスクからも自動的に削除します。

于 2008-10-08T16:13:02.637 に答える
6

はい、これは読み取り可能であるが一意のファイル名に適した戦略です。

重要な変更の1つ: !に置き換える必要がos.path.isfileあります。os.path.lexists現在書かれているように、/ foo / bar.bazという名前のディレクトリがある場合、プログラムはそれを新しいファイルで上書きしようとします(これは機能しません)...isfileディレクトリではなくファイルのみをチェックするためです。 lexistsディレクトリ、シンボリックリンクなどをチェックします...基本的にファイル名を作成できなかった理由がある場合。

編集:@Brianはより良い答えを出しました。これは、競合状態に関してより安全で堅牢です。

于 2008-10-08T16:02:08.773 に答える
2

2つの小さな変更...

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

異なる意味を持つ2つの結果が得られ、それらに異なる名前を付けます。

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

それは速くも大幅に短くもありません。ただし、ファイル名のパターンを変更する場合は、パターンが1か所にあるため、操作が少し簡単になります。

于 2008-10-08T16:00:51.973 に答える
1

読みやすい名前が必要な場合、これは良い解決策のようです。
たとえば、一意のファイル名を返すルーチンがあります。一時ファイルですが、長いランダムに見える名前が生成されます。

于 2008-10-08T15:52:38.090 に答える
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)
于 2008-10-09T01:07:49.497 に答える
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() は現在の時間をミリ秒単位で返します。元のファイル名と組み合わせると、複雑なマルチスレッドの場合でもかなりユニークです。

于 2009-03-27T18:47:16.763 に答える