¿Cómo puedo crear un archivo temporal con una extensión específica con .NET?

277

Necesito generar un archivo temporal único con una extensión .csv.

Lo que hago ahora es

string filename = System.IO.Path.GetTempFileName().Replace(".tmp", ".csv");

Sin embargo, esto no garantiza que mi archivo .csv sea único.

Sé que las posibilidades de que tenga una colisión son muy bajas (especialmente si consideras que no elimino los archivos .tmp), pero este código no me parece bien.

Por supuesto, podría generar manualmente nombres de archivos aleatorios hasta que finalmente encuentre uno único (que no debería ser un problema), pero tengo curiosidad por saber si otros han encontrado una buena manera de lidiar con este problema.

Brann
fuente
44
algunas advertencias sobre GetTempFileName El método GetTempFileName generará una IOException si se usa para crear más de 65535 archivos sin eliminar los archivos temporales anteriores. El método GetTempFileName generará una IOException si no hay un nombre de archivo temporal único disponible. Para resolver este error, elimine todos los archivos temporales innecesarios.
Max Hodges
2
Los archivos temporales se utilizan principalmente para un conjunto específico de condiciones. Si la extensión del archivo es importante, me pregunto si tal vez el uso de GetTempFileName no sea la solución de escritura. Sé que ha pasado mucho tiempo, pero si nos cuenta más sobre el contexto y la necesidad de estos archivos, podríamos sugerir un mejor enfoque por completo. más aquí: support.microsoft.com/kb/92635?wa=wsignin1.0
Max Hodges
1
Tenga en cuenta que GetTempFileName() crea un nuevo archivo cada vez que lo llama. - Si cambia inmediatamente la cadena a otra cosa, acaba de crear un nuevo archivo de cero bytes en su directorio temporal (y, como han señalado otros, esto eventualmente hará que falle cuando golpee 65535 archivos allí ...) - - Para evitar esto, asegúrese de eliminar los archivos que cree en esa carpeta (incluidos los que devuelve GetTempFileName(), idealmente en un bloque finalmente).
BrainSlugs83

Respuestas:

356

Garantizado para ser (estadísticamente) único:

string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv"; 

(Para citar del artículo wiki sobre la probabilidad de una colisión:

... se estima que el riesgo anual de ser golpeado por un meteorito es de una probabilidad en 17 mil millones [19], lo que significa que la probabilidad es de aproximadamente 0.00000000006 (6 × 10−11), equivalente a las probabilidades de crear unas pocas decenas de billones de UUID en un año y tener un duplicado. En otras palabras, solo después de generar mil millones de UUID por segundo durante los próximos 100 años, la probabilidad de crear un solo duplicado sería de aproximadamente el 50%. La probabilidad de un duplicado sería de aproximadamente el 50% si cada persona en la tierra posee 600 millones de UUID

EDITAR: Consulte también los comentarios de JaredPar.

Trigo Mitch
fuente
29
Pero no se garantiza que esté en un lugar de escritura
JaredPar
33
Y están no garantiza que sea único en todo, sólo estadísticamente improbable.
paxdiablo
30
@Pax: tienes más posibilidades de ganar la lotería 1000 veces seguidas que generar dos guías ideológicas. Eso es lo suficientemente único, supongo ...
Mitch Wheat
11
@Mitch la razón por la que no es única es porque es posible para mí simplemente crear un archivo con el mismo nombre en la misma ruta. GUID mientras se garantiza que sea único también son predecibles, que los medios se les da suficiente información que pude adivinar el siguiente conjunto de GUID generados por su caja
JaredPar
207
Dios mío, trate de mantener su cabeza fuera de las nubes. El enfoque es: generar un nombre de archivo aleatorio, luego crearlo si no existe. Así que solo ayúdelo a codificar eso muy bien. Toda esta charla sobre generadores pseudoaleatorios y números universalmente únicos es totalmente innecesaria.
Max Hodges
58

Prueba esta función ...

public static string GetTempFilePathWithExtension(string extension) {
  var path = Path.GetTempPath();
  var fileName = Guid.NewGuid().ToString() + extension;
  return Path.Combine(path, fileName);
}

Devolverá un camino completo con la extensión de su elección.

Tenga en cuenta que no se garantiza que produzca un nombre de archivo único, ya que técnicamente alguien más podría haber creado ese archivo. Sin embargo, las posibilidades de que alguien adivine el próximo guid producido por su aplicación y lo cree es muy, muy bajo. Es bastante seguro asumir que esto será único.

JaredPar
fuente
44
Quizás Path.ChangeExtension () sería más elegante que Guid.NewGuid (). ToString () + extension
Ohad Schneider
@ohadsc - de hecho, Guid.NewGuid().ToString() + extensionni siquiera es correcto, debería serloGuid.NewGuid().ToString() + "." + extension
Stephen Swensen
44
Supongo que eso depende del contrato del método (ya sea que lo espere .txto no txt), pero dado que ChangeExtensionmaneja ambos casos, no puede doler
Ohad Schneider
1
Llámalo Sufijo en lugar de Extensión y todos están felices, supongo
Chiel ten Brinke
46
public static string GetTempFileName(string extension)
{
  int attempt = 0;
  while (true)
  {
    string fileName = Path.GetRandomFileName();
    fileName = Path.ChangeExtension(fileName, extension);
    fileName = Path.Combine(Path.GetTempPath(), fileName);

    try
    {
      using (new FileStream(fileName, FileMode.CreateNew)) { }
      return fileName;
    }
    catch (IOException ex)
    {
      if (++attempt == 10)
        throw new IOException("No unique temporary file name is available.", ex);
    }
  }
}

Nota: esto funciona como Path.GetTempFileName. Se crea un archivo vacío para reservar el nombre del archivo. Realiza 10 intentos, en caso de colisiones generadas por Path.GetRandomFileName ();

Maxence
fuente
Tenga en cuenta que en Path.GetRandomFileName()realidad crea un archivo de cero bytes en el disco y devuelve la ruta completa de ese archivo. No está utilizando este archivo, solo su nombre para cambiar la extensión. Por lo tanto, si no se asegura de eliminar estos archivos temporales, después de 65535 llamadas a esta función, comenzará a fallar.
Vladimir Baranov
77
Has mezclado GetTempFileName()y GetRandomFileName(). GetTempFileName()crea un archivo de cero bytes como mi método, pero GetRandomFileName()no crea un archivo. Desde los documentos:> A diferencia de GetTempFileName, GetRandomFileName no crea un archivo. Su enlace apunta a la página incorrecta.
Maxence
19

También puede utilizar alternativamente System.CodeDom.Compiler.TempFileCollection .

string tempDirectory = @"c:\\temp";
TempFileCollection coll = new TempFileCollection(tempDirectory, true);
string filename = coll.AddExtension("txt", true);
File.WriteAllText(Path.Combine(tempDirectory,filename),"Hello World");

Aquí usé una extensión txt pero puedes especificar lo que quieras. También configuré el indicador keep en true para que el archivo temporal se mantenga después del uso. Desafortunadamente, TempFileCollection crea un archivo aleatorio por extensión. Si necesita más archivos temporales, puede crear varias instancias de TempFileCollection.

Mehmet Aras
fuente
10

¿Por qué no verificar si el archivo existe?

string fileName;
do
{
    fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv";
} while (System.IO.File.Exists(fileName));
Matías
fuente
9
File.Exists le brinda información sobre el pasado y, por lo tanto, no es confiable. Entre la devolución de File.Exist y la ejecución de su código, es posible que se cree el archivo.
JaredPar
44
... entonces usted está seguro con su propio programa tal vez, pero no con otro proceso de escribir un archivo en el mismo destino exacto ...
Koen
@JaredPar y ¿cuál es la posibilidad de que esto ocurra?
Migol
2
@Migol Es muy bajo y, por definición, una condición excepcional. Hmmm, exactamente para qué excepciones fueron diseñadas para ser utilizadas.
Cody Gray
3
La posibilidad de @CodyGray para el choque guid es 1/2 ^ 128. La posibilidad de que ocurra 2 veces es 1/2 ^ 256 etc. ¡No se moleste!
Migol
10

La documentación de MSDN para GetTempFileName de C ++ analiza su inquietud y la responde:

GetTempFileName no puede garantizar que el nombre del archivo sea único .

Solo se utilizan los 16 bits inferiores del parámetro único. Esto limita GetTempFileName a un máximo de 65.535 nombres de archivo únicos si los parámetros lpPathName y lpPrefixString siguen siendo los mismos.

Debido al algoritmo utilizado para generar nombres de archivo, GetTempFileName puede funcionar mal cuando se crea una gran cantidad de archivos con el mismo prefijo. En tales casos, se recomienda que construya nombres de archivo únicos basados ​​en GUID .

Coronel Panic
fuente
2
El método GetTempFileName generará una IOException si se usa para crear más de 65535 archivos sin eliminar los archivos temporales anteriores. El método GetTempFileName generará una IOException si no hay un nombre de archivo temporal único disponible. Para resolver este error, elimine todos los archivos temporales innecesarios.
Max Hodges
Esa es una cita incompleta. La cita relevante: " Si uUnique no es cero , debe crear el archivo usted mismo. Solo se crea un nombre de archivo, porque GetTempFileName no puede garantizar que el nombre del archivo sea único". Si lo llamas de la forma en que todos están discutiendo aquí, único será cero.
jnm2
6

Qué tal si:

Path.Combine(Path.GetTempPath(), DateTime.Now.Ticks.ToString() + "_" + Guid.NewGuid().ToString() + ".csv")

Es muy improbable que la computadora genere el mismo Guid en el mismo instante de tiempo. La única debilidad que veo aquí es el impacto en el rendimiento DateTime.Now.Ticks agregará.

IRBaboon
fuente
5

También puedes hacer lo siguiente

string filename = Path.ChangeExtension(Path.GetTempFileName(), ".csv");

y esto también funciona como se esperaba

string filename = Path.ChangeExtension(Path.GetTempPath() + Guid.NewGuid().ToString(), ".csv");
Michael Prewecki
fuente
2
esto fallará si hay un archivo temp.csv y crea temp.tmp y luego cambia la extensión a csv
David
No, no lo hará ... GetTempFileName () crea un nombre de archivo único ... hasta un límite de 32K, momento en el que debe eliminar algunos archivos, pero creo que mi solución es correcta. Está mal si tuviera que pasar una ruta de archivo a ChangeExtension que no se garantiza que sea única, pero eso no es lo que hace mi solución.
Michael Prewecki
11
GetTempFileName garantiza que la ruta que devuelve será única. No es que la ruta que devuelve + ".csv" sea única. Cambiar la extensión de esta manera podría fallar como dijo David.
Marcus Griep
55
GetTempFileName crea un archivo, por lo que su primer ejemplo es una pérdida de recursos.
Gary McGill
3

En mi opinión, la mayoría de las respuestas propuestas aquí son subóptimas. El que se acerca más es el original propuesto inicialmente por Brann.

Un nombre de archivo temporal debe ser

  • Único
  • Libre de conflictos (aún no existe)
  • Atomic (Creación de nombre y archivo en la misma operación)
  • Difícil de adivinar

Debido a estos requisitos, no es una buena idea programar una bestia por su cuenta. Las personas inteligentes que escriben bibliotecas IO se preocupan por cosas como el bloqueo (si es necesario), etc. Por lo tanto, no veo la necesidad de reescribir System.IO.Path.GetTempFileName ().

Esto, incluso si parece torpe, debería hacer el trabajo:

//Note that this already *creates* the file
string filename1 = System.IO.Path.GetTempFileName()
// Rename and move
filename = filename.Replace(".tmp", ".csv");
File.Move(filename1 , filename);
Daniel C. Oderbolz
fuente
2
Pero eso no es libre de conflictos, el 'csv' ya podría existir y sobrescribirse.
xvan
3
File.Movese eleva IOExceptionsi el archivo de destino ya existe. msdn.microsoft.com/en-us/library/…
joshuanapoli
2

Esto podría ser útil para ti ... Es crear una temperatura. carpeta y devolverlo como una cadena en VB.NET.

Fácilmente convertible a C #:

Public Function GetTempDirectory() As String
    Dim mpath As String
    Do
        mpath = System.IO.Path.Combine(System.IO.Path.GetTempPath, System.IO.Path.GetRandomFileName)
    Loop While System.IO.Directory.Exists(mpath) Or System.IO.File.Exists(mpath)
    System.IO.Directory.CreateDirectory(mpath)
    Return mpath
End Function
Simón
fuente
2

Esto parece funcionar bien para mí: comprueba la existencia del archivo y crea el archivo para asegurarse de que sea una ubicación de escritura. Debería funcionar bien, puede cambiarlo para que devuelva directamente FileStream (que normalmente es lo que necesita para un archivo temporal):

private string GetTempFile(string fileExtension)
{
  string temp = System.IO.Path.GetTempPath();
  string res = string.Empty;
  while (true) {
    res = string.Format("{0}.{1}", Guid.NewGuid().ToString(), fileExtension);
    res = System.IO.Path.Combine(temp, res);
    if (!System.IO.File.Exists(res)) {
      try {
        System.IO.FileStream s = System.IO.File.Create(res);
        s.Close();
        break;
      }
      catch (Exception) {

      }
    }
  }
  return res;
} // GetTempFile
Paolo Iommarini
fuente
2

Función fácil en C #:

public static string GetTempFileName(string extension = "csv")
{
    return Path.ChangeExtension(Path.GetTempFileName(), extension);
}
Rancho Camal
fuente
GetTempFileName () crea un archivo temporal en la carpeta temporal. Como resultado, tendrá dos archivos temporales creados, uno de los cuales se filtrará.
evgenybf
1

En base a las respuestas que encontré en Internet, llego a mi código de la siguiente manera:

public static string GetTemporaryFileName()
{       
    string tempFilePath = Path.Combine(Path.GetTempPath(), "SnapshotTemp");
    Directory.Delete(tempFilePath, true);
    Directory.CreateDirectory(tempFilePath);
    return Path.Combine(tempFilePath, DateTime.Now.ToString("MMddHHmm") + "-" + Guid.NewGuid().ToString() + ".png");
}

Y como C # Cookbook por Jay Hilyard, Stephen Teilhet señaló en Uso de un archivo temporal en su aplicación :

  • debe usar un archivo temporal siempre que necesite almacenar información temporalmente para su posterior recuperación.

  • Lo único que debe recordar es eliminar este archivo temporal antes de que finalice la aplicación que lo creó.

  • Si no se elimina, permanecerá en el directorio temporal del usuario hasta que el usuario lo elimine manualmente.

Speedoops
fuente
1

He mezclado @Maxence y @Mitch trigo respuestas teniendo en cuenta Quiero que la semántica del método GetTempFileName (el nombre del archivo es el nombre de un nuevo archivo creado) añadir la extensión preferido.

string GetNewTempFile(string extension)
{
    if (!extension.StartWith(".")) extension="." + extension;
    string fileName;
    bool bCollisions = false;
    do {
        fileName = Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString() + extension);
        try
        {
            using (new FileStream(fileName, FileMode.CreateNew)) { }
            bCollisions = false;
        }
        catch (IOException)
        {
            bCollisions = true;
        }
    }
    while (bCollisions);
    return fileName;
}
Fil
fuente
0

Esto es lo que estoy haciendo:

string tStamp = String.Format ("{0: aaaaMMdd.HHmmss}", DateTime.Now);
string ProcID = Process.GetCurrentProcess (). Id.ToString ();
string tmpFolder = System.IO.Path.GetTempPath ();
string outFile = tmpFolder + ProcID + "_" + tStamp + ".txt";
Michael Fitzpatrick
fuente
Bueno: incluye el identificador de proceso Malo: no incluye el identificador de subproceso (aunque podría ejecutar esto dentro de un bloqueo) Malo: la marca de tiempo tiene una resolución de solo 1 segundo. En muchas, muchas aplicaciones, es común producir muchos archivos por segundo.
andrewf
0

Esta es una forma simple pero efectiva de generar nombres de archivo incrementales. Buscará en el actual directamente (puede señalarlo fácilmente en otro lugar) y buscará archivos con la base YourApplicationName * .txt (de nuevo, puede cambiarlo fácilmente). Comenzará a las 0000 para que el primer nombre de archivo sea YourApplicationName0000.txt. si por alguna razón hay nombres de archivos con basura entre (partes que no son números) las partes izquierda y derecha, esos archivos serán ignorados en virtud de la llamada de prueba.

    public static string CreateNewOutPutFile()
    {
        const string RemoveLeft = "YourApplicationName";
        const string RemoveRight = ".txt";
        const string searchString = RemoveLeft + "*" + RemoveRight;
        const string numberSpecifier = "0000";

        int maxTempNdx = -1;

        string fileName;
        string [] Files = Directory.GetFiles(Directory.GetCurrentDirectory(), searchString);
        foreach( string file in Files)
        {
            fileName = Path.GetFileName(file);
            string stripped = fileName.Remove(fileName.Length - RemoveRight.Length, RemoveRight.Length).Remove(0, RemoveLeft.Length);
            if( int.TryParse(stripped,out int current) )
            {
                if (current > maxTempNdx)
                    maxTempNdx = current;
            }
        }
        maxTempNdx++;
        fileName = RemoveLeft + maxTempNdx.ToString(numberSpecifier) + RemoveRight;
        File.CreateText(fileName); // optional
        return fileName;
    }
Nombre para mostrar
fuente
-2

Creo que deberías probar esto:

string path = Path.GetRandomFileName();
path = Path.Combine(@"c:\temp", path);
path = Path.ChangeExtension(path, ".tmp");
File.Create(path);

Genera un nombre de archivo único y crea un archivo con ese nombre de archivo en una ubicación específica.

Smita
fuente
3
Esta solución tiene tantos problemas con ella. no puede combinar C: \ temp con una ruta absoluta, es posible que c: \ temp no se pueda escribir y no garantiza que la versión .tmp del archivo sea única.
Mark Lakata