C#: How would you make a unique filename by adding a number?

前端 未结 18 676
没有蜡笔的小新
没有蜡笔的小新 2020-11-27 11:46

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

相关标签:
18条回答
  • 2020-11-27 12:05

    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;
        }
    }
    
    0 讨论(0)
  • 2020-11-27 12:07

    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; 
        }
    
    0 讨论(0)
  • 2020-11-27 12:09

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

    0 讨论(0)
  • 2020-11-27 12:10

    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)));
    
    0 讨论(0)
  • 2020-11-27 12:12
    /// <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

    0 讨论(0)
  • 2020-11-27 12:13
    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.

    0 讨论(0)
提交回复
热议问题