2008-10-08 32 views
14

Vẫn đang 'lặn' với Python và muốn đảm bảo rằng tôi không nhìn thấy gì đó. Tôi đã viết một tập lệnh trích xuất các tệp từ một số tệp zip và lưu các tệp được trích xuất cùng nhau trong một thư mục. Để ngăn chặn các tên tập tin trùng lặp bị ghi đè, tôi đã viết hàm nhỏ này - và tôi chỉ tự hỏi liệu có cách nào tốt hơn để làm điều này không? Cảm ơn!Đây có phải là cách tốt nhất để lấy phiên bản duy nhất của tên tệp w/Python không?

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 

Tôi thực sự làm yêu cầu các tập tin được trong một thư mục duy nhất, và đánh số các bản sao chắc chắn là chấp nhận được trong trường hợp của tôi, vì vậy tôi không tìm kiếm một phương pháp mạnh mẽ hơn (tho' Tôi cho rằng bất kỳ con trỏ được chào đón), nhưng chỉ để đảm bảo rằng những gì thành công này được thực hiện đúng cách.

Trả lời

22

Một vấn đề là có một điều kiện chủng tộc trong mã trên của bạn, vì có khoảng cách giữa kiểm tra sự tồn tại và tạo tệp. Có thể có những tác động bảo mật cho điều này (nghĩ về ai đó chèn một liên kết mềm vào một tệp nhạy cảm mà họ không thể ghi đè, nhưng chương trình của bạn chạy với đặc quyền cao hơn có thể) Các cuộc tấn công như thế này là tại sao những thứ như os.tempnam () không được chấp nhận.

Để giải quyết vấn đề này, cách tốt nhất là thực sự thử tạo tệp theo cách bạn sẽ nhận được ngoại lệ nếu không thành công và thành công, trả về đối tượng tệp thực sự mở. Điều này có thể được thực hiện với các hàm os.open mức thấp hơn, bằng cách chuyển cả hai cờ os.O_CREAT và os.O_EXCL. Khi đã mở, hãy trả lại tệp thực tế (và tên tệp tùy chọn) mà bạn tạo. Ví dụ, đây là mã của bạn sửa đổi để sử dụng phương pháp này (trả lại một (tập tin, tên tập tin) tuple):

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 

[Chỉnh sửa] Trên thực tế, một cách tốt hơn, mà sẽ xử lý các vấn đề trên cho bạn, có lẽ để sử dụng mô-đun tempfile, mặc dù bạn có thể mất quyền kiểm soát việc đặt tên.Dưới đây là một ví dụ của việc sử dụng nó (giữ một giao diện tương tự):

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 

Nhược điểm duy nhất của phương pháp này là bạn sẽ luôn có được một tên tập tin với một số nhân vật ngẫu nhiên trong nó, như không có nỗ lực để tạo ra một tập tin chưa sửa đổi (/home/some_dir/foo.txt) trước tiên. Bạn cũng có thể xem tempfile.TemporaryFile và NamedTemporaryFile, sẽ thực hiện thao tác trên và cũng tự động xóa khỏi đĩa khi đóng.

+0

Có, đây là The_Right_Way để làm điều đó. Tôi ước tôi có thể tự sửa mình và đưa câu trả lời của bạn lên hàng đầu! –

+1

Lỗi đánh máy nhỏ: phải là 'os.O_RDWR' thay vì' os.O_RDRW' – tremby

1

Nếu bạn muốn tên có thể đọc, điều này trông giống như một giải pháp tốt.
Có các thủ tục để trả về các tên tệp duy nhất cho ví dụ. các tệp tạm thời nhưng chúng tạo ra các tên tìm kiếm ngẫu nhiên dài.

2

Hai thay đổi nhỏ ...

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

Bạn nhận được hai kết quả với ý nghĩa khác nhau, cung cấp cho chúng những cái tên khác nhau.

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

Nó không nhanh hơn hoặc ngắn hơn đáng kể. Tuy nhiên, khi bạn muốn thay đổi mẫu tên tệp của mình, mẫu sẽ ở một nơi và dễ dàng hơn để làm việc.

6

Vâng, đây là chiến lược tốt cho tên tệp có thể đọc nhưng duy nhất.

Một thay đổi quan trọng: Bạn nên thay thế os.path.isfile bằng os.path.lexists! Vì nó được viết ngay bây giờ, nếu có một thư mục có tên /foo/bar.baz, chương trình của bạn sẽ cố gắng ghi đè lên bằng tệp mới (sẽ không hoạt động) ... vì isfile chỉ kiểm tra các tệp chứ không phải thư mục . lexists kiểm tra các thư mục, liên kết tượng trưng, ​​v.v ... về cơ bản nếu có bất kỳ lý do gì mà tên tệp không thể tạo được.

CHỈNH SỬA: @Brian đưa ra câu trả lời hay hơn, an toàn hơn và mạnh mẽ hơn về điều kiện chủng tộc.

1

nếu bạn không quan tâm đến khả năng đọc, uuid.uuid4() là bạn của bạn.

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

Làm thế nào về

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 

thời gian() trả về thời gian hiện tại trong mili giây. kết hợp với tên tệp gốc, nó khá độc đáo ngay cả trong các trường hợp đa luồng phức tạp.

Các vấn đề liên quan