Create file but if name exists add number

Question:

Does Python have any built-in functionality to add a number to a filename if it already exists?

My idea is that it would work the way certain OS’s work – if a file is output to a directory where a file of that name already exists, it would append a number or increment it.

I.e: if “file.pdf” exists it will create “file2.pdf”, and next time “file3.pdf”.

Asked By: Parham

||

Answers:

In a way, Python has this functionality built into the tempfile module. Unfortunately, you have to tap into a private global variable, tempfile._name_sequence. This means that officially, tempfile makes no guarantee that in future versions _name_sequence even exists — it is an implementation detail.
But if you are okay with using it anyway, this shows how you can create uniquely named files of the form file#.pdf in a specified directory such as /tmp:

import tempfile
import itertools as IT
import os

def uniquify(path, sep = ''):
    def name_sequence():
        count = IT.count()
        yield ''
        while True:
            yield '{s}{n:d}'.format(s = sep, n = next(count))
    orig = tempfile._name_sequence 
    with tempfile._once_lock:
        tempfile._name_sequence = name_sequence()
        path = os.path.normpath(path)
        dirname, basename = os.path.split(path)
        filename, ext = os.path.splitext(basename)
        fd, filename = tempfile.mkstemp(dir = dirname, prefix = filename, suffix = ext)
        tempfile._name_sequence = orig
    return filename

print(uniquify('/tmp/file.pdf'))
Answered By: unutbu

I was trying to implement the same thing in my project but @unutbu’s answer seemed too ‘heavy’ for my needs so I came up with following code finally:

import os
index = ''
while True:
    try:
        os.makedirs('../hi'+index)
        break
    except WindowsError:
        if index:
            index = '('+str(int(index[1:-1])+1)+')' # Append 1 to number in brackets
        else:
            index = '(1)'
        pass # Go and try create file again

Just in case someone stumbled upon this and requires something simpler.

Answered By: arbulgazar

Since the tempfile hack A) is a hack and B) still requires a decent amount of code anyway, I went with a manual implementation. You basically need:

  1. A way to Safely create a file if and only if it does not exist (this is what the tempfile hack affords us).
  2. A generator for filenames.
  3. A wrapping function to hide the mess.

I defined a safe_open that can be used just like open:

def iter_incrementing_file_names(path):
    """
    Iterate incrementing file names. Start with path and add " (n)" before the
    extension, where n starts at 1 and increases.

    :param path: Some path
    :return: An iterator.
    """
    yield path
    prefix, ext = os.path.splitext(path)
    for i in itertools.count(start=1, step=1):
        yield prefix + ' ({0})'.format(i) + ext


def safe_open(path, mode):
    """
    Open path, but if it already exists, add " (n)" before the extension,
    where n is the first number found such that the file does not already
    exist.

    Returns an open file handle. Make sure to close!

    :param path: Some file name.

    :return: Open file handle... be sure to close!
    """
    flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY

    if 'b' in mode and platform.system() == 'Windows':
        flags |= os.O_BINARY

    for filename in iter_incrementing_file_names(path):
        try:
            file_handle = os.open(filename, flags)
        except OSError as e:
            if e.errno == errno.EEXIST:
                pass
            else:
                raise
        else:
            return os.fdopen(file_handle, mode)

# Example
with safe_open("some_file.txt", "w") as fh:
    print("Hello", file=fh)
Answered By: jtpereyda

I haven’t tested this yet but it should work, iterating over possible filenames until the file in question does not exist at which point it breaks.

def increment_filename(fn):
    fn, extension = os.path.splitext(path)

    n = 1
    yield fn + extension
    for n in itertools.count(start=1, step=1)
        yield '%s%d.%s' % (fn, n, extension)

for filename in increment_filename(original_filename):
    if not os.isfile(filename):
        break
Answered By: OnGle

recently I encountered the same thing and here is my approach:

import os

file_name = "file_name.txt"
if os.path.isfile(file_name):
    expand = 1
    while True:
        expand += 1
        new_file_name = file_name.split(".txt")[0] + str(expand) + ".txt"
        if os.path.isfile(new_file_name):
            continue
        else:
            file_name = new_file_name
            break
Answered By: lastro

This works for me.
The initial file name is 0.yml, if it exists, it will add one until meet the requirement

import os
import itertools

def increment_filename(file_name):
    fid, extension = os.path.splitext(file_name)

    yield fid + extension
    for n in itertools.count(start=1, step=1):
        new_id = int(fid) + n
        yield "%s%s" % (new_id, extension)


def get_file_path():
    target_file_path = None
    for file_name in increment_filename("0.yml"):
        file_path = os.path.join('/tmp', file_name)
        if not os.path.isfile(file_path):
            target_file_path = file_path
            break
    return target_file_path
Answered By: penny chan

A little bit later but there is still something like this should work properly, mb it will be useful for someone.

You can use built-in iterator to do this ( image downloader as example for you ):

def image_downloader():

        image_url = 'some_image_url'

        for count in range(10):
            image_data = requests.get(image_url).content

            with open(f'image_{count}.jpg', 'wb') as handler:
                handler.write(image_data)

Files will increment properly. Result is:

image.jpg
image_0.jpg
image_1.jpg
image_2.jpg
image_3.jpg
image_4.jpg
image_5.jpg
image_6.jpg
image_7.jpg
image_8.jpg
image_9.jpg
Answered By: Yevhen_Radchenko

If all files being numbered isn’t a problem, and you know beforehand the name of the file to be written, you could simply do:

import os

counter = 0
filename = "file{}.pdf"
while os.path.isfile(filename.format(counter)):
    counter += 1
filename = filename.format(counter)
Answered By: llk
import os

class Renamer():
    def __init__(self, name):
        self.extension = name.split('.')[-1]
        self.name = name[:-len(self.extension)-1]
        self.filename = self.name
    def rename(self):
        i = 1
        if os.path.exists(self.filename+'.'+self.extension):
            while os.path.exists(self.filename+'.'+self.extension):
                self.filename = '{} ({})'.format(self.name,i)
                i += 1
        return self.filename+'.'+self.extension
Answered By: Attaullah Khan

I’ve implemented a similar solution with pathlib:

Create file-names that match the pattern path/<file-name>-dd.ext. Perhaps this solution can help…

import pathlib
from toolz import itertoolz as itz

def file_exists_add_number(path_file_name, digits=2):

    pfn = pathlib.Path(path_file_name)
    parent = pfn.parent     # parent-dir of file
    stem = pfn.stem         # file-name w/o extension
    suffix = pfn.suffix     # NOTE: extension starts with '.' (dot)!

    try:
        # search for files ending with '-dd.ext'
        last_file = itz.last(parent.glob(f"{stem}-{digits * '?'}{suffix}"))
    except:
        curr_no = 1
    else:
        curr_no = int(last_file.stem[-digits:]) + 1

    # int to string and add leading zeros
    curr_no = str(last_no).zfill(digits)
    path_file_name = parent / f"{stem}-{curr_no}{suffix}"

    return str(path_file_name)

Pls note: That solution starts at 01 and will only find file-pattern containing -dd!

Answered By: Chris

I ended up writing my own simple function for this. Primitive, but gets the job done:

def uniquify(path):
    filename, extension = os.path.splitext(path)
    counter = 1

    while os.path.exists(path):
        path = filename + " (" + str(counter) + ")" + extension
        counter += 1

    return path
Answered By: S. Tarık Çetin

I found that the os.path.exists() conditional function did what I needed. I’m using a dictionary-to-csv saving as an example, but the same logic could work for any file type:

import os 

def smart_save(filename, dict):
    od = filename + '_' # added underscore before number for clarity

    for i in np.arange(0,500,1): # I set an arbitrary upper limit of 500
        d = od + str(i)

        if os.path.exists(d + '.csv'):
            pass

        else:
            with open(d + '.csv', 'w') as f: #or any saving operation you need
                for key in dict.keys():
                    f.write("%s,%sn"%(key, dictionary[key]))
            break

Note: this appends a number (starting at 0) to the file name by default, but it’s easy to shift that around.

Answered By: naspitha

Let’s say you already have those files:

enter image description here

This function generates the next available non-already-existing filename, by adding a _1, _2, _3, … suffix before the extension if necessary:

import os

def nextnonexistent(f):
    fnew = f
    root, ext = os.path.splitext(f)
    i = 0
    while os.path.exists(fnew):
        i += 1
        fnew = '%s_%i%s' % (root, i, ext)
    return fnew

print(nextnonexistent('foo.txt'))  # foo_3.txt
print(nextnonexistent('bar.txt'))  # bar_1.txt
print(nextnonexistent('baz.txt'))  # baz.txt
Answered By: Basj

Easy way for create new file if this name in your folder

if 'sample.xlsx' in os.listdir('testdir/'):

    i = 2
    
    while os.path.exists(f'testdir/sample ({i}).xlsx'):
        i += 1
    
    wb.save(filename=f"testdir/sample ({i}).xlsx")
else:
    wb.save(filename=f"testdir/sample.xlsx")

Answered By: Achak

This function validates if the file name exists using regex expresion and recursion

def validate_outfile_name(input_path):
    filename, extension = os.path.splitext(input_path)
    if os.path.exists(input_path):
        output_path = ""
        pattern = '([0-9])'
        match = re.search(pattern, filename)
        if match:
            version = filename[match.start() + 1]
            try: new_version = int(version) + 1 
            except: new_version = 1
            output_path = f"{filename[:match.start()]}({new_version}){extension}"
            output_path = validate_outfile_name(output_path)
        else: 
            version = 1
            output_path = f"{filename}({version}){extension}"
    
        return output_path
    else:
        return input_path
Answered By: JAVIER ESCUDERO
def create_file():
        counter = 0
        filename = "file"
        while os.path.isfile(f"dir/{filename}{counter}.txt"):
            counter += 1
        print(f"{filename}{counter}.txt")
Answered By: M1nybe
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.