57

I would like to create a method which takes either a filename as a string or a FileInfo and adds an incremented number to the filename if the file exists. But can't quite wrap my head around how to do this in a good way.

For example, if I have this FileInfo

var file = new FileInfo(@"C:\file.ext");

I would like the method to give me a new FileInfo with C:\file 1.ext if C:\file.ext existed, and C:\file 2.ext if C:\file 1.ext existed and so on. Something like this:

public FileInfo MakeUnique(FileInfo fileInfo)
{
    if(fileInfo == null)
        throw new ArgumentNullException("fileInfo");
    if(!fileInfo.Exists)
        return fileInfo;

    // Somehow construct new filename from the one we have, test it, 
    // then do it again if necessary.
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
Svish
  • 152,914
  • 173
  • 462
  • 620

22 Answers22

50
public FileInfo MakeUnique(string path)
{            
    string dir = Path.GetDirectoryName(path);
    string fileName = Path.GetFileNameWithoutExtension(path);
    string fileExt = Path.GetExtension(path);

    for (int i = 1; ;++i) {
        if (!File.Exists(path))
            return new FileInfo(path);

        path = Path.Combine(dir, fileName + " " + i + fileExt);
    }
}

Obviously, this is vulnerable to race conditions as noted in other answers.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • 1
    This is really inefficient for large numbers of files, a binary search approach is better – Sam Saffron Jul 03 '09 at 06:42
  • One way to (help) eliminate the race condition is to instead return an open stream to the file instead of just the info about the file. – Matthew Scharley Jul 03 '09 at 06:43
  • 14
    @Sam: Go write up yours. I'm gonna sleep ;) – Mehrdad Afshari Jul 03 '09 at 06:43
  • @Matthew: How would you do that? – Svish Jul 03 '09 at 06:49
  • no need, Marc already did :p http://stackoverflow.com/questions/909521/how-to-solve-this-problem-storing-values-persistenly-of-files-in-a-directory/909545#909545 – Sam Saffron Jul 03 '09 at 06:53
  • The race condition is that someone else might open the file before you do... opening as part of what the function does eliminates that (if it fails, you keep looking for a new file) – Matthew Scharley Jul 03 '09 at 07:38
  • @Matthew How would you write a method which did this in a way that it prevented the race conditions? – Svish Jul 03 '09 at 08:08
  • 19
    -1 - Dunno why this answer has so many ups, it will not do what the question asked for. Instead of incrementing and replacing the number in the file name it will just append it (the code does nothing to remove the original number and replace it with one that is incremented). – bytedev Jan 29 '13 at 17:06
  • 1
    Agree with @nashwan, this produces undesirable results. – Jeb Sep 30 '14 at 18:45
  • The placement of the opening brace on the if statement really messed me up. Took me a good 20 seconds to notice it, haha. – Owen James Jul 04 '16 at 19:56
45

Lots of good advice here. I ended up using a method written by Marc in an answer to a different question. Reformatted it a tiny bit and added another method to make it a bit easier to use "from the outside". Here is the result:

private static string numberPattern = " ({0})";

public static string NextAvailableFilename(string path)
{
    // Short-cut if already available
    if (!File.Exists(path))
        return path;

    // If path has extension then insert the number pattern just before the extension and return next filename
    if (Path.HasExtension(path))
        return GetNextFilename(path.Insert(path.LastIndexOf(Path.GetExtension(path)), numberPattern));

    // Otherwise just append the pattern to the path and return next filename
    return GetNextFilename(path + numberPattern);
}

private static string GetNextFilename(string pattern)
{
    string tmp = string.Format(pattern, 1);
    if (tmp == pattern)
        throw new ArgumentException("The pattern must include an index place-holder", "pattern");

    if (!File.Exists(tmp))
        return tmp; // short-circuit if no matches

    int min = 1, max = 2; // min is inclusive, max is exclusive/untested

    while (File.Exists(string.Format(pattern, max)))
    {
        min = max;
        max *= 2;
    }

    while (max != min + 1)
    {
        int pivot = (max + min) / 2;
        if (File.Exists(string.Format(pattern, pivot)))
            min = pivot;
        else
            max = pivot;
    }

    return string.Format(pattern, max);
}

Only partially tested it so far, but will update if I find any bugs with it. (Marcs code works nicely!) If you find any problems with it, please comment or edit or something :)

Derrick Moeller
  • 4,808
  • 2
  • 22
  • 48
Svish
  • 152,914
  • 173
  • 462
  • 620
  • note, from my benchmarking it appears that as long as you have more than 170 files in your dir this is faster than just getting all the files in the dir and doing all the work in memory – Sam Saffron Jul 03 '09 at 11:25
  • @Sam: Thanks. Def nice to know. My folders will probably not even be close to that, but it is nice to know that it will be efficient if it should happen. A nice utility method to have around kind of :) – Svish Jul 03 '09 at 13:19
  • What if NextAvailableFilename("MyFile (1).txt")? Is the result "MyFile (1) (1).txt"? Shouldn't it be "MyFile (2).txt"? – J Pollack Apr 11 '13 at 12:21
  • 1
    No it shouldn't, because that function expects the path to be how you want it to be. Not how a file happens to be. Of course you can adjust the function if you want it to behave differently, but you'd have to do that yourself ;) – Svish Apr 11 '13 at 21:09
  • I'm sorry to bring up an old question, but can someone explain me how this approach works? I don't get how and why GetNextFilename(string pattern) uses pivot,max and min. – misleadingTitle Sep 24 '13 at 16:24
  • @misleadingTitle Been a while, but I think it's doing sort of a binary search for available filenames. This is to avoid checking 1000 files for existence if the next available is 1001 for example. How exactly the pivot,max,min thing works I don't remember, but if you're curious just insert a breakpoint and step through it in your debugger. Should be pretty easy to follow :) – Svish Sep 25 '13 at 08:03
  • This actually just appends ({0}) to an existing filename, so if the filename is /temp/myFilename (1).txt you will get /temp/myFilename (1)({0}).txt as the pattern passed into GetNextFilename() - which will produce the following path: /temp/myFilename (1)(1).txt - not really what is desired. – mdebeus Jul 27 '18 at 20:07
  • 2
    @mdebeus Read [my previous comment](https://stackoverflow.com/questions/1078003/c-how-would-you-make-a-unique-filename-by-adding-a-number?noredirect=1#comment22744335_1078898). If you're passing in `/tmp/myFile (1).txt`, then yes that's what you'll get. You should be passing in `/tmp/myFile.txt`. You pass in your wanted name, not an existing name. – Svish Jul 27 '18 at 20:51
  • 2
    This is great. It gave me valuable insight to resolve a persistent design flaw that I have been tackling. Very useful. – tobbyioa Jun 26 '20 at 19:11
35

Not pretty, but I've had this for a while :

private string getNextFileName(string fileName)
{
    string extension = Path.GetExtension(fileName);

    int i = 0;
    while (File.Exists(fileName))
    {
        if (i == 0)
            fileName = fileName.Replace(extension, "(" + ++i + ")" + extension);
        else
            fileName = fileName.Replace("(" + i + ")" + extension, "(" + ++i + ")" + extension);
    }

    return fileName;
}

Assuming the files already exist:

  • File.txt
  • File(1).txt
  • File(2).txt

the call getNextFileName("File.txt") will return "File(3).txt".

Not the most efficient because it doesn't use binary search, but should be ok for small file count. And it doesn't take race condition into account...

Pierre-Olivier Goulet
  • 968
  • 2
  • 11
  • 18
12

If checking if the file exists is too hard you can always just add a date and time to the file name to make it unique:

FileName.YYYYMMDD.HHMMSS

Maybe even add milliseconds if necessary.

Matthew Lock
  • 13,144
  • 12
  • 92
  • 130
mga911
  • 1,536
  • 1
  • 16
  • 33
  • 3
    I have used this technique successfully. If you are creating files too fast, you run the risk of name collision, but if you know that you are not creating multiple files within milliseconds of each other it works great. – Jay Jul 03 '09 at 06:47
  • If the odering is not importatnt, you can also use random number generation with a counter and the pid. – HeretoLearn Jul 04 '09 at 17:47
  • 2
    You can just change it to YYYYMMDD.HHMMSS in order to solve the ordering issue... – Romias Jun 28 '16 at 18:14
  • 1
    And the C# code to create the suffix is as follows: `DateTime.UtcNow.ToString("yyyyMMddHHmmss")` (case sensitive). – Borislav Ivanov May 22 '17 at 05:27
5

If the format doesn't bother you then you can call:

try{
    string tempFile=System.IO.Path.GetTempFileName();
    string file=System.IO.Path.GetFileName(tempFile);
    //use file
    System.IO.File.Delete(tempFile);
}catch(IOException ioe){
  //handle 
}catch(FileIOPermission fp){
  //handle
}

PS:- Please read more about this at msdn before using.

TheVillageIdiot
  • 40,053
  • 20
  • 133
  • 188
  • Thing is I already have the filename to use. If I could just make one up it wouldn't be a problem in the first place ;) – Svish Jul 03 '09 at 08:09
  • @Svish I've already said if "...format doesn't bother you..". Yes this not very nice. You can append it to your filename TextFile + "_" + tempFile. Definitely this is no prone to any race conditions. – TheVillageIdiot Jul 03 '09 at 09:24
4
/// <summary>
/// Create a unique filename for the given filename
/// </summary>
/// <param name="filename">A full filename, e.g., C:\temp\myfile.tmp</param>
/// <returns>A filename like C:\temp\myfile633822247336197902.tmp</returns>
public string GetUniqueFilename(string filename)
{
    string basename = Path.Combine(Path.GetDirectoryName(filename),
                                   Path.GetFileNameWithoutExtension(filename));
    string uniquefilename = string.Format("{0}{1}{2}",
                                            basename,
                                            DateTime.Now.Ticks,
                                            Path.GetExtension(filename));
    // Thread.Sleep(1); // To really prevent collisions, but usually not needed
    return uniquefilename;
}

As DateTime.Ticks has a resolution of 100 nanoseconds, collisions are extremely unlikely. However, a Thread.Sleep(1) will ensure that, but I doubt that it's needed

Marcus Mangelsdorf
  • 2,852
  • 1
  • 30
  • 40
Michael Stum
  • 177,530
  • 117
  • 400
  • 535
2

Insert a new GUID into the file name.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
  • I'm amazed no one else had said this, anything else would be a waste of effort (unless you really want your temporary filenames to look pretty... but why?!) – Daniel Earwicker Jul 03 '09 at 08:15
  • 2
    Because the files are not temporary? I already have the name that is wanted. I just need to add a number to it if a file with that name already exists so that I don't overwrite the one already there. – Svish Jul 03 '09 at 08:17
  • 6
    A good example of where this is necessary, is when Visual Studio creates a new class. If Class1.cs already exists, it creates Class2.cs, etc. It would be ridiculous for VS to create a new class with a GUID as the name. – Xcalibur Mar 09 '11 at 13:50
2

I must throw my 2-cents in. This is how I did it and it works for my use.

    private static string IterateFileName(string fileName)
    {
        if (!File.Exists(fileName)) return fileName;

        FileInfo fi = new FileInfo(fileName);
        string ext = fi.Extension;
        string name = fi.FullName.Substring(0, fi.FullName.Length - ext.Length);

        int i = 2;
        while (File.Exists($"{name}_{i}{ext}"))
        {
            i++;
        }


        return $"{name}_{i}{ext}";
    }
tolsen64
  • 881
  • 1
  • 9
  • 22
  • I would use System.IO.Path to get the fileName and extension instead of FileInfo. FileInfo may throw an exception. – CBFT Jan 27 '23 at 05:59
1

The idea is to get a list of the existing files, parse out the numbers, then make the next highest one.

Note: This is vulnerable to race conditions, so if you have more than one thread creating these files, be careful.

Note 2: This is untested.

public static FileInfo GetNextUniqueFile(string path)
{
    //if the given file doesn't exist, we're done
    if(!File.Exists(path))
        return new FileInfo(path);

    //split the path into parts
    string dirName = Path.GetDirectoryName(path);
    string fileName = Path.GetFileNameWithoutExtension(path);
    string fileExt = Path.GetExtension(path);

    //get the directory
    DirectoryInfo dir = new DirectoryInfo(dir);

    //get the list of existing files for this name and extension
    var existingFiles = dir.GetFiles(Path.ChangeExtension(fileName + " *", fileExt);

    //get the number strings from the existing files
    var NumberStrings = from file in existingFiles
                        select Path.GetFileNameWithoutExtension(file.Name)
                            .Remove(0, fileName.Length /*we remove the space too*/);

    //find the highest existing number
    int highestNumber = 0;

    foreach(var numberString in NumberStrings)
    {
        int tempNum;
        if(Int32.TryParse(numberString, out tempnum) && tempNum > highestNumber)
            highestNumber = tempNum;
    }

    //make the new FileInfo object
    string newFileName = fileName + " " + (highestNumber + 1).ToString();
    newFileName = Path.ChangeExtension(fileName, fileExt);

    return new FileInfo(Path.Combine(dirName, newFileName));
}
lc.
  • 113,939
  • 20
  • 158
  • 187
  • tried to make the question a bit clearer. it was the next file that doesn't exist that I wanted, but wrote it a bit crooked :p – Svish Jul 03 '09 at 07:11
  • That's what I figured and my answer does that. :) – lc. Jul 03 '09 at 07:12
1

Instead of poking the disk a number of times to find out if it has a particular variant of the desired file name, you could ask for the list of files that already exist and find the first gap according to your algorithm.

public static class FileInfoExtensions
{
    public static FileInfo MakeUnique(this FileInfo fileInfo)
    {
        if (fileInfo == null)
        {
            throw new ArgumentNullException("fileInfo");
        }

        string newfileName = new FileUtilities().GetNextFileName(fileInfo.FullName);
        return new FileInfo(newfileName);
    }
}

public class FileUtilities
{
    public string GetNextFileName(string fullFileName)
    {
        if (fullFileName == null)
        {
            throw new ArgumentNullException("fullFileName");
        }

        if (!File.Exists(fullFileName))
        {
            return fullFileName;
        }
        string baseFileName = Path.GetFileNameWithoutExtension(fullFileName);
        string ext = Path.GetExtension(fullFileName);

        string filePath = Path.GetDirectoryName(fullFileName);
        var numbersUsed = Directory.GetFiles(filePath, baseFileName + "*" + ext)
            .Select(x => Path.GetFileNameWithoutExtension(x).Substring(baseFileName.Length))
            .Select(x =>
                    {
                        int result;
                        return Int32.TryParse(x, out result) ? result : 0;
                    })
            .Distinct()
            .OrderBy(x => x)
            .ToList();

        var firstGap = numbersUsed
            .Select((x, i) => new { Index = i, Item = x })
            .FirstOrDefault(x => x.Index != x.Item);
        int numberToUse = firstGap != null ? firstGap.Item : numbersUsed.Count;
        return Path.Combine(filePath, baseFileName) + numberToUse + ext;
    }
}    
Handcraftsman
  • 6,863
  • 2
  • 40
  • 33
1
    private async Task<CloudBlockBlob> CreateBlockBlob(CloudBlobContainer container,  string blobNameToCreate)
    {
        var blockBlob = container.GetBlockBlobReference(blobNameToCreate);

        var i = 1;
        while (await blockBlob.ExistsAsync())
        {
            var newBlobNameToCreate = CreateRandomFileName(blobNameToCreate,i.ToString());
            blockBlob = container.GetBlockBlobReference(newBlobNameToCreate);
            i++;
        }

        return blockBlob;
    }



    private string CreateRandomFileName(string fileNameWithExtension, string prefix=null)
    {

        int fileExtPos = fileNameWithExtension.LastIndexOf(".", StringComparison.Ordinal);

        if (fileExtPos >= 0)
        {
            var ext = fileNameWithExtension.Substring(fileExtPos, fileNameWithExtension.Length - fileExtPos);
            var fileName = fileNameWithExtension.Substring(0, fileExtPos);

            return String.Format("{0}_{1}{2}", fileName, String.IsNullOrWhiteSpace(prefix) ? new Random().Next(int.MinValue, int.MaxValue).ToString():prefix,ext);
        }

        //This means there is no Extension for the file and its fine attaching random number at the end.
        return String.Format("{0}_{1}", fileNameWithExtension, new Random().Next(int.MinValue, int.MaxValue));
    }

I use this code to create a consecutive _1,_2,_3 etc.. file name everytime a file exists in the blob storage.

Avinash Gadiraju
  • 723
  • 1
  • 7
  • 16
1

Hope this self iterating function may help. It works fine for me.

public string getUniqueFileName(int i, string filepath, string filename)
    {
        string path = Path.Combine(filepath, filename);
        if (System.IO.File.Exists(path))
        {
            string name = Path.GetFileNameWithoutExtension(filename);
            string ext = Path.GetExtension(filename);
            i++;
            filename = getUniqueFileName(i, filepath, name + "_" + i + ext);
        }
        return filename; 
    }
Bikuz
  • 36
  • 3
1

This is an answer to question in this Link, but they marked it as a duplicate, so I post my answer here.

I created this proof of concept class (may contain bugs). More explanation in code comments.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace ConsoleApp
{
    class Program
    {
        static void Main( string[] args )
        {
            var testFilePaths = new List<string>
            {
                @"c:\test\file.txt",
                @"c:\test\file(1).txt",
                @"c:\test\file(2).txt",
                @"c:\TEST2\file(3).txt",
                @"c:\test\file(5).txt",
                @"c:\test\file(5)abc.txt",
                @"c:\test\file(5).avi"
            };

            // inspect in debbuger for correct values
            var withSuffix      = new DecomposedFilePath( "c:\\files\\file(13).txt");
            var withoutSuffix   = new DecomposedFilePath( "c:\\files\\file(abc).txt");
            var withExtraNumber = new DecomposedFilePath( "c:\\files\\file(34)xyz(35).txt"); // "(34)" in the middle should be ignored

            DecomposedFilePath changedSuffix = withExtraNumber.ReplaceSuffix( 1999 ); // "file(34)xyz(35).txt" -> "file(34)xyz(1999).txt"
            DecomposedFilePath removedSuffix = changedSuffix.ReplaceSuffix( null ); // "file(34)xyz(1999).txt" -> "file(34)xyz.txt"

            var testPath = new DecomposedFilePath( "c:\\test\\file.txt");
            DecomposedFilePath nextPath1 = testPath.GetFirstFreeFilePath( testFilePaths );

            // update our list
            testFilePaths.Add( nextPath1.FullFilePath );
            DecomposedFilePath nextPath2 = testPath.GetFirstFreeFilePath( testFilePaths );
        
            testFilePaths.Add( nextPath2.FullFilePath );
            DecomposedFilePath nextPath3 = testPath.GetFirstFreeFilePath( testFilePaths );
        }
    }

    public sealed class DecomposedFilePath
    {
        public DecomposedFilePath( string filePath )
        {
            FullFilePath = Path.GetFullPath( filePath );
        }

        // "c:\myfiles\file(4).txt"
        public string FullFilePath { get; }

        // "file" or "file(1)"
        public string FileNameWithoutExt => Path.GetFileNameWithoutExtension( FullFilePath );

        // "file(13)" -> "file"
        public string FileNameWithoutExtAndSuffix => FileNameWithoutExt.Substring( 0, FileNameWithoutExt.Length - Suffix.Length ); // removes suffix

        // ".txt"
        public string Extenstion => Path.GetExtension( FullFilePath );

        // "c:\myfiles"
        public string DirectoryPath => Path.GetDirectoryName( FullFilePath );

        // "file(23)" -> "23", file -> stirng.Empty
        public string Suffix
        {
            get
            {
                // we want to extract suffix from file name, e.g. "(34)" from "file(34)"
                // I am not good at regex, but I hope it will work correctly

                var regex = new Regex( @"\([0-9]+\)$" );
                Match match = regex.Match( FileNameWithoutExt );

                if (!match.Success) return string.Empty; // suffix not found

                return match.Value; // return "(number)"
            }
        }

        // tranlates suffix "(33)" to 33. If suffix is does not exist (string.empty), returns null (int?)
        public int? SuffixAsInt
        {
            get
            {
                if (Suffix == string.Empty) return null;

                string numberOnly = Suffix.Substring( 1, Suffix.Length - 2 ); // remove '(' from beginning and ')' from end

                return int.Parse( numberOnly );
            }
        }

        // e.g. input is suffix: 56 then it changes file name from "file(34)" to "file(56)"
        public DecomposedFilePath ReplaceSuffix( int? suffix ) // null - removes suffix
        {
            string strSuffix = suffix is null ? string.Empty : $"({suffix})"; // add ( and )

            string path = Path.Combine( DirectoryPath, FileNameWithoutExtAndSuffix + strSuffix + Extenstion ); // build full path

            return new DecomposedFilePath( path );
        }

        public DecomposedFilePath GetFirstFreeFilePath( IEnumerable<string> filesInDir )
        {
            var decomposed = filesInDir
                // convert all paths to our class
                .Select( x => new DecomposedFilePath( x ) )
                // pick files only with the same extensionm as our base file, ignore case
                .Where( x => string.Equals( Extenstion, x.Extenstion, StringComparison.OrdinalIgnoreCase) )
                // pick files only with the same name (ignoring suffix)
                .Where( x => string.Equals( FileNameWithoutExtAndSuffix, x.FileNameWithoutExtAndSuffix, StringComparison.OrdinalIgnoreCase) )
                // with the same directory
                .Where( x => string.Equals( DirectoryPath, x.DirectoryPath, StringComparison.OrdinalIgnoreCase) )
                .ToList(); // create copy for easier debugging

            if (decomposed.Count == 0) return this; // no name collision

            int? firstFreeSuffix = Enumerable.Range( 1, int.MaxValue) // start numbering duplicates from 1
                                  .Select( x => (int?) x) // change to int? because SuffixAsInt is of that type
                                  .Except( decomposed.Select( x => x.SuffixAsInt) ) // remove existing suffixes
                                  .First(); // get first free suffix

            return ReplaceSuffix( firstFreeSuffix );
        }

        public override string ToString() => FullFilePath;
    }
}
apocalypse
  • 5,764
  • 9
  • 47
  • 95
1
public static string MakeUniqueFilePath(string filePath)
{
    if (!File.Exists(filePath)) return filePath;

    var directory = Path.GetDirectoryName(filePath);
    var fileName = Path.GetFileNameWithoutExtension(filePath);
    var fileExt = Path.GetExtension(filePath);

    var i = 1;
    do
    {
        filePath = Path.Combine(directory, fileName + "(" + i + ")" + fileExt);
        i++;
    } while (File.Exists(filePath));

    return filePath;
}

Returns files like so:

test.txt

test(1).txt

test(2).txt etc.

Notes:

  • Can handle filenames without extensions
  • Can Handle directories included in the file path.
  • Does not handle file creation race conditions when saving.
1

Here's one that decouples the numbered naming question from the check of the filesystem:

/// <summary>
/// Finds the next unused unique (numbered) filename.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <param name="inUse">Function that will determine if the name is already in use</param>
/// <returns>The original filename if it wasn't already used, or the filename with " (n)"
/// added to the name if the original filename is already in use.</returns>
private static string NextUniqueFilename(string fileName, Func<string, bool> inUse)
{
    if (!inUse(fileName))
    {
        // this filename has not been seen before, return it unmodified
        return fileName;
    }
    // this filename is already in use, add " (n)" to the end
    var name = Path.GetFileNameWithoutExtension(fileName);
    var extension = Path.GetExtension(fileName);
    if (name == null)
    {
        throw new Exception("File name without extension returned null.");
    }
    const int max = 9999;
    for (var i = 1; i < max; i++)
    {
        var nextUniqueFilename = string.Format("{0} ({1}){2}", name, i, extension);
        if (!inUse(nextUniqueFilename))
        {
            return nextUniqueFilename;
        }
    }
    throw new Exception(string.Format("Too many files by this name. Limit: {0}", max));
}

And here's how you might call it if you are using the filesystem

var safeName = NextUniqueFilename(filename, f => File.Exists(Path.Combine(folder, f)));
Tim Abell
  • 11,186
  • 8
  • 79
  • 110
  • Is it really a point with the limit there? Does the file system have a limit like that? – Svish Mar 21 '12 at 15:15
  • The limit is up to you, personally I didn't want something getting into an infinite loop, and for me 10000 files is unlikely to ever happen ;-) Oh and by the way the above doesn't deal with the performance problems others have identified, perhaps someone could do a combined version. – Tim Abell Mar 21 '12 at 15:20
  • Don't see how this would get into an infinite loop, unless you had an infinite number of files :p – Svish Mar 21 '12 at 15:27
  • It could happen through a coding error, (eg passing in `f => true` or something more convoluted as the second parameter). Hitting max will be easier to troubleshoot than watching your cpu get pinned to 100% and waiting for an integer overflow. – Tim Abell Mar 21 '12 at 15:46
  • Sure, but I prefer not to put in arbitrary limits like that :) (If I were to throw an exception I would use a more specific one than `Exception` though... ;) – Svish Mar 21 '12 at 16:14
  • These are certainly reasonable points. With introducing max, I'm balancing easy of troubleshooting an out of control process vs arbitrary limits. More elegant approaches welcome ;-). Wrt exceptions: I suppose I could create a custom exception class, but it seems a bit like overkill unless I have any intention of catching it (which personally I don't this time). I couldn't think of a standard exception class that fitted the bill. Suggestions appreciated. – Tim Abell Mar 21 '12 at 17:06
0

This is just a string operation; find the location in the filename string where you want to insert the number, and re-construct a new string with the number inserted. To make it re-usable, you might want to look for a number in that location, and parse it out into an integer, so you can increment it.

Please note that this in general this way of generating a unique filename is insecure; there are obvious race condition hazards.

There might be ready-made solutions for this in the platform, I'm not up to speed with C# so I can't help there.

unwind
  • 391,730
  • 64
  • 469
  • 606
0

Take a look at the methods in the Path class, specifically Path.GetFileNameWithoutExtension(), and Path.GetExtension().

You may even find Path.GetRandomFileName() useful!

Edit:

In the past, I've used the technique of attempting to write the file (with my desired name), and then using the above functions to create a new name if an appropriate IOException is thrown, repeating until successful.

Steve Guidi
  • 19,700
  • 9
  • 74
  • 90
0

This method will add a index to existing file if needed:

If the file exist, find the position of the last underscore. If the content after the underscore is a number, increase this number. otherwise add first index. repeat until unused file name found.

static public string AddIndexToFileNameIfNeeded(string sFileNameWithPath)
{
    string sFileNameWithIndex = sFileNameWithPath;

    while (File.Exists(sFileNameWithIndex)) // run in while scoop so if after adding an index the the file name the new file name exist, run again until find a unused file name
    { // File exist, need to add index

        string sFilePath = Path.GetDirectoryName(sFileNameWithIndex);
        string sFileName = Path.GetFileNameWithoutExtension(sFileNameWithIndex);
        string sFileExtension = Path.GetExtension(sFileNameWithIndex);

        if (sFileName.Contains('_'))
        { // Need to increase the existing index by one or add first index

            int iIndexOfUnderscore = sFileName.LastIndexOf('_');
            string sContentAfterUnderscore = sFileName.Substring(iIndexOfUnderscore + 1);

            // check if content after last underscore is a number, if so increase index by one, if not add the number _01
            int iCurrentIndex;
            bool bIsContentAfterLastUnderscoreIsNumber = int.TryParse(sContentAfterUnderscore, out iCurrentIndex);
            if (bIsContentAfterLastUnderscoreIsNumber)
            {
                iCurrentIndex++;
                string sContentBeforUnderscore = sFileName.Substring(0, iIndexOfUnderscore);

                sFileName = sContentBeforUnderscore + "_" + iCurrentIndex.ToString("000");
                sFileNameWithIndex = sFilePath + "\\" + sFileName + sFileExtension;
            }
            else
            {
                sFileNameWithIndex = sFilePath + "\\" + sFileName + "_001" + sFileExtension;
            }
        }
        else
        { // No underscore in file name. Simple add first index
            sFileNameWithIndex = sFilePath + "\\" + sFileName + "_001" + sFileExtension;
        }
    }

    return sFileNameWithIndex;
}
Gil Epshtain
  • 8,670
  • 7
  • 63
  • 89
0

I did it like this:

for (int i = 0; i <= 500; i++) //I suppose the number of files will not pass 500
        {       //Checks if C:\log\log+TheNumberOfTheFile+.txt exists...
            if (System.IO.File.Exists(@"C:\log\log"+conta_logs+".txt"))
            {
                conta_logs++;//If exists, then increment the counter
            }
            else
            {              //If not, then the file is created
                var file = System.IO.File.Create(@"C:\log\log" + conta_logs + ".txt");
                break; //When the file is created we LEAVE the *for* loop
            }
        }

I think this version is not so hard like the others, and It's a straightforward answer for what the user wanted.

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
0

If you need just a unique file name, so, how about this?

Path.GetRandomFileName()
Oleg
  • 1,467
  • 4
  • 26
  • 39
0

I have written a method that returns "next" file name with number. Supports numbering from 1 to 99.

Examples:

  • C:\Recovery.txt → C:\Recovery1.txt
  • C:\Recovery1.txt → C:\Recovery2.txt

How to call:

    while (File.Exists( path ))
                path = NextFileNum( path );


    internal static string NextFileNum( string path )
    {
        string filename = Path.GetFileNameWithoutExtension( path );
        string ext = Path.GetExtension( path );
        string dir = Path.GetDirectoryName( path );
        for (int i = 99; i > 0; i--)
        {
            if (filename.EndsWith( i.ToString() ))
            {
                string suffix = ( i + 1 ).ToString();
                filename = filename.Substring( 0, filename.Length - suffix.Length ) + suffix;
                return Path.Combine( dir, filename + ext );
            }
        }
        filename = filename + "1";
        return Path.Combine( dir, filename + ext );
    }
Sergey
  • 835
  • 7
  • 6
0

If you are working on Windows you may want to use the native methods PathMakeUniqueName or PathYetAnotherMakeUniqueName from shell32.dll. Both will append a number in parentheses to the file name, or fill an empty pair of parentheses with a number, to produce a unique unused file name. The most notable difference between them is that PathMakeUniqueName starts from 1 and PathYetAnotherMakeUniqueName from 2.

using System.Text;
using System.IO;
[DllImport("shell32.dll", EntryPoint="PathMakeUniqueName", CharSet=CharSet.Unicode)]
internal static extern bool PathMakeUniqueName (
  StringBuilder pszUniqueName,
  uint nBufferSize,
  string pszTemplate,
  string pszLong,
  string pszDir);
[DllImport("shell32.dll", EntryPoint="PathYetAnotherMakeUniqueName", CharSet=CharSet.Unicode)]
internal static extern bool PathYetAnotherMakeUniqueName (
  StringBuilder pszUniqueName,
  string pszPath,
  string pszShort,
  string pszFileSpec);

const int MAX_PATH = 260;
var buffer = new StringBuilder(MAX_PATH);
var directory = Path.GetDirectoryName(filePath);
var filespec = Path.GetFileName(filePath);
bool ok = PathMakeUniqueName(buffer, MAX_PATH, null, filespec, directory);
//bool ok = PathYetAnotherMakeUniqueName(buffer, directory, null, filespec);
// true: success, false: all numbers (999) used up
var uniquePath = buffer.ToString();

E.g. if you pass in foo.txt and the file already exists, PathYetAnotherMakeUniqueName returns foo (2).txt or any higher number should that also exist.

Note the methods will not catch race conditions.

yacc
  • 2,915
  • 4
  • 19
  • 33