Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A way to create files and directories without overwriting

You know how when you download something and the downloads folder contains a file with the same name, instead of overwriting it or throwing an error, the file ends up with a number appended to the end? For example, if I want to download my_file.txt, but it already exists in the target folder, the new file will be named my_file(2).txt. And if I try again, it will be my_file(3).txt.

I was wondering if there is a way in Python 3.x to check that and get a unique name (not necessarily create the file or directory). I'm currently implementing it doing this:

import os
def new_name(name, newseparator='_')
    #name can be either a file or directory name

    base, extension = os.path.splitext(name)
    i = 2
    while os.path.exists(name):
        name = base + newseparator + str(i) + extension
        i += 1

    return name

In the example above, running new_file('my_file.txt') would return my_file_2.txt if my_file.txt already exists in the cwd. name can also contain the full or relative path, it will work as well.

like image 878
Puff Avatar asked Oct 17 '22 10:10

Puff


2 Answers

I would use PathLib and do something along these lines:

from pathlib import Path 

def new_fn(fn, sep='_'):
    p=Path(fn)
    if p.exists():
        if not p.is_file(): 
            raise TypeError
        np=p.resolve(strict=True)
        parent=str(np.parent)
        extens=''.join(np.suffixes)  # handle multiple ext such as .tar.gz
        base=str(np.name).replace(extens,'')
        i=2
        nf=parent+base+sep+str(i)+extens    
        while Path(nf).exists():
            i+=1
            nf=parent+base+sep+str(i)+extens    
        return nf   
    else:       
        return p.parent.resolve(strict=True) / p 

This only handles files as written but the same approach would work with directories (which you added later.) I will leave that as a project for the reader.

like image 165
dawg Avatar answered Oct 20 '22 22:10

dawg


Another way of getting a new name would be using the built-in tempfile module:

from pathlib import Path
from tempfile import NamedTemporaryFile

def new_path(path: Path, new_separator='_'):
    prefix = str(path.stem) + new_separator
    dir = path.parent
    suffix = ''.join(path.suffixes)

    with NamedTemporaryFile(prefix=prefix, suffix=suffix, delete=False, dir=dir) as f:
        return f.name

If you execute this function from within Downloads directory, you will get something like:

>>> new_path(Path('my_file.txt'))
'/home/krassowski/Downloads/my_file_90_lv301.txt'

where the 90_lv301 part was generated internally by the Python's tempfile module.

Note: with the delete=False argument, the function will create (and leave undeleted) an empty file with the new name. If you do not want to have an empty file created that way, just remove the delete=False, however keeping it will prevent anyone else from creating a new file with such name before your next operation (though they could still overwrite it).

Simply put, having delete=False prevents concurrency issues if you (or the end-user) were to run your program twice at the same time.

like image 37
krassowski Avatar answered Oct 21 '22 00:10

krassowski