puedo - C#: ¿Cómo harías un nombre de archivo único al agregar un número?
cómo agregar un número internacional al whatsapp (16)
Me gustaría crear un método que tome un nombre de archivo como una string
o un FileInfo
y agrega un número incrementado al nombre del archivo si el archivo existe. Pero no puedo entender cómo hacerlo de una buena manera.
Por ejemplo, si tengo este FileInfo
var file = new FileInfo(@"C:/file.ext");
Me gustaría que el método me diera un nuevo FileInfo con C: / archivo 1.ext si C: / file.ext existiera, y C: / archivo 2.ext si C: / archivo 1.ext existiera y así sucesivamente. Algo como esto:
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.
}
Aquí hay uno que desacopla la pregunta de nomenclatura numerada del control del sistema de archivos:
/// <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));
}
Y así es como puedes llamarlo si estás usando el sistema de archivos
var safeName = NextUniqueFilename(filename, f => File.Exists(Path.Combine(folder, f)));
Eche un vistazo a los métodos en la clase Path , específicamente Path.GetFileNameWithoutExtension() , y Path.GetExtension() .
¡Incluso puede encontrar Path.GetRandomFileName() útil!
Editar:
En el pasado, he usado la técnica de intentar escribir el archivo (con mi nombre deseado) y luego uso las funciones anteriores para crear un nuevo nombre si se lanza una IOException
apropiada, repitiendo hasta IOException
tenga éxito.
En lugar de pinchar el disco varias veces para averiguar si tiene una variante particular del nombre de archivo deseado, puede solicitar la lista de archivos que ya existen y encontrar el primer espacio de acuerdo con su algoritmo.
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;
}
}
Espero que esta función iterativa pueda ayudar. Funciona bien para mí.
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;
}
Este método agregará un índice al archivo existente si es necesario:
Si el archivo existe, encuentre la posición del último guión bajo. Si el contenido después del subrayado es un número, aumente este número. de lo contrario, agregue el primer índice. repita hasta encontrar el nombre del archivo no utilizado.
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;
}
Esto es solo una operación de cuerda; encuentre la ubicación en la cadena de nombre de archivo donde desea insertar el número, y vuelva a construir una nueva cadena con el número insertado. Para que sea reutilizable, es posible que desee buscar un número en esa ubicación y analizarlo en un número entero para que pueda incrementarlo.
Tenga en cuenta que esto, en general, esta forma de generar un nombre de archivo único es inseguro; hay peligros obvios para la condición de carrera .
Puede haber soluciones ya preparadas para esto en la plataforma, no estoy al tanto de C #, así que no puedo ayudar.
Inserte un nuevo GUID en el nombre del archivo.
La idea es obtener una lista de los archivos existentes, analizar los números y luego hacer la siguiente más alta.
Nota: Esto es vulnerable a las condiciones de carrera, por lo que si tiene más de un hilo creando estos archivos, tenga cuidado .
Nota 2: Esto no ha sido probado.
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));
}
Lo hice así:
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
}
}
Creo que esta versión no es tan difícil como las demás, y es una respuesta directa para lo que el usuario quería.
No es lindo, pero he tenido esto por un tiempo:
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;
}
Suponiendo que los archivos ya existen:
- File.txt
- Archivo (1) .txt
- Archivo (2) .txt
la llamada getNextFileName ("File.txt") devolverá "File (3) .txt".
No es el más eficiente porque no utiliza la búsqueda binaria, pero debería estar bien para el conteo de archivos pequeños. Y no tiene en cuenta la condición de carrera ...
Si comprueba si el archivo existe es demasiado difícil, siempre puede agregar una fecha y hora al nombre del archivo para que sea único:
FileName.YYYYMMDD.HHMMSS
Tal vez incluso agregue milisegundos si es necesario.
Si el formato no te molesta, puedes llamar:
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
}
PD: - Lea más sobre esto en msdn antes de usar.
Un montón de buenos consejos aquí. Terminé usando un método escrito por Marc en una respuesta a una pregunta diferente . Reformateó un poco y agregó otro método para que sea un poco más fácil de usar "desde afuera". Aquí está el resultado:
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);
}
Solo lo he probado parcialmente hasta el momento, pero lo actualizaré si encuentro algún error con él. (¡El código de Marc funciona bien!) Si encuentras algún problema con él, por favor comenta o edita o algo :)
/// <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;
}
Como DateTime.Ticks tiene una resolución de 100 nanosegundos , las colisiones son extremadamente improbables. Sin embargo, un Thread.Sleep (1) se asegurará de eso, pero dudo que sea necesario
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));
}
Uso este código para crear un nombre de archivo _1, _2, _3, etc., consecutivo, cada vez que existe un archivo en el almacenamiento de blob.
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);
}
}
Obviamente, esto es vulnerable a las condiciones de carrera como se señala en otras respuestas.