¿Crear un directorio temporal en Windows?

126

¿Cuál es la mejor manera de obtener un nombre de directorio temporal en Windows? Veo que puedo usar GetTempPathy GetTempFileNamecrear un archivo temporal, pero ¿hay algún equivalente a la función Linux / BSD mkdtemppara crear un directorio temporal?

Josh Kelley
fuente
Esta pregunta parece un poco difícil de encontrar. En particular, no aparece si escribe cosas como "directorio temporal .net" en el cuadro de búsqueda Desbordamiento de pila. Eso parece lamentable, ya que las respuestas son hasta ahora todas las respuestas .NET. ¿Crees que podrías agregar la etiqueta ".net"? (¿Y quizás la etiqueta "directorio" o "directorio-temporal"?) ¿O tal vez agregue la palabra ".NET" al título? Tal vez también en el cuerpo de la pregunta alterne decir "temp" con "temporal", por lo que si busca el formulario más corto aún obtendrá una buena coincidencia de búsqueda de texto. Parece que no tengo suficiente representante para hacer estas cosas yo mismo. Gracias.
Chris
Bueno, estaba buscando una respuesta que no fuera de .NET, así que preferiría omitirla, pero hice las otras modificaciones que sugirió. Gracias.
Josh Kelley el
@Josh Kelley: acabo de verificar la API de Win32 y las únicas opciones son seguir un enfoque similar para obtener la ruta temporal, generar un nombre de archivo ramdom y luego crear un directorio.
Scott Dorman el

Respuestas:

230

No, no hay equivalente a mkdtemp. La mejor opción es usar una combinación de GetTempPath y GetRandomFileName .

Necesitarías un código similar a este:

public string GetTemporaryDirectory()
{
   string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
   Directory.CreateDirectory(tempDirectory);
   return tempDirectory;
}
Scott Dorman
fuente
3
Esto parece un poco peligroso. En particular, existe la posibilidad (pequeña, pero no cero, ¿verdad?) De que Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()) devuelva el nombre de un directorio que ya existe. Dado que Directory.CreateDirectory (tempDirectory) no arrojará una excepción si tempDirectory ya existe, su aplicación no detectará este caso. Y luego puede tener dos aplicaciones interviniendo en el trabajo del otro. ¿Hay alguna alternativa más segura en .NET?
Chris
22
@Chris: el método GetRandomFileName devuelve una cadena aleatoria criptográficamente fuerte que se puede usar como nombre de carpeta o nombre de archivo. Supongo que es teóricamente posible que la ruta resultante ya pueda existir, pero no hay otras formas de hacerlo. Puede verificar para ver si la ruta existe, y si llama a Path.GetRandomFileName () nuevamente, y repetir.
Scott Dorman el
55
@ Chris: Sí, si está preocupado por la seguridad en esa medida hay una muy pequeña posibilidad de que otro proceso podría crear el directorio entre el Path.Combine y las llamadas Directory.CreateDirectory.
Scott Dorman
8
GetRandomFileName genera 11 letras minúsculas y números aleatorios, lo que significa que el tamaño del dominio es (26 + 10) ^ 11 = ~ 57 bits. Siempre puedes hacer dos llamadas para corregirlo
Marty Neal
27
Justo después de verificar que el directorio no existe, Dios podría pausar el universo, colarse y crear el mismo directorio con todos los mismos archivos que está a punto de escribir, haciendo que su aplicación explote, solo para arruinar su día.
Triynko
25

Hackeo Path.GetTempFileName()para darme una ruta de archivo válida y seudoaleatoria en el disco, luego elimino el archivo y creo un directorio con la misma ruta de archivo.

Esto evita la necesidad de verificar si la ruta del archivo está disponible en un momento o ciclo, según el comentario de Chris sobre la respuesta de Scott Dorman.

public string GetTemporaryDirectory()
{
  string tempFolder = Path.GetTempFileName();
  File.Delete(tempFolder);
  Directory.CreateDirectory(tempFolder);

  return tempFolder;
}

Si realmente necesita un nombre aleatorio criptográficamente seguro, es posible que desee adaptar la respuesta de Scott para usar un tiempo o hacer un bucle para seguir intentando crear una ruta en el disco.

Steve Jansen
fuente
7

Me gusta usar GetTempPath (), una función de creación de GUID como CoCreateGuid () y CreateDirectory ().

Un GUID está diseñado para tener una alta probabilidad de unicidad, y también es muy improbable que alguien cree manualmente un directorio con la misma forma que un GUID (y si lo hacen, CreateDirectory () fallará indicando su existencia).

Mateo
fuente
5

@Chris. Yo también estaba obsesionado con el riesgo remoto de que un directorio temporal ya pudiera existir. Las discusiones sobre aleatorio y criptográficamente fuerte tampoco me satisfacen por completo.

Mi enfoque se basa en el hecho fundamental de que el O / S no debe permitir 2 llamadas para crear un archivo para que ambos tengan éxito. Es un poco sorprendente que los diseñadores de .NET opten por ocultar la funcionalidad de la API Win32 para los directorios, lo que lo hace mucho más fácil, ya que devuelve un error cuando intenta crear un directorio por segunda vez. Esto es lo que uso:

    [DllImport(@"kernel32.dll", EntryPoint = "CreateDirectory", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CreateDirectoryApi
        ([MarshalAs(UnmanagedType.LPTStr)] string lpPathName, IntPtr lpSecurityAttributes);

    /// <summary>
    /// Creates the directory if it does not exist.
    /// </summary>
    /// <param name="directoryPath">The directory path.</param>
    /// <returns>Returns false if directory already exists. Exceptions for any other errors</returns>
    /// <exception cref="System.ComponentModel.Win32Exception"></exception>
    internal static bool CreateDirectoryIfItDoesNotExist([NotNull] string directoryPath)
    {
        if (directoryPath == null) throw new ArgumentNullException("directoryPath");

        // First ensure parent exists, since the WIN Api does not
        CreateParentFolder(directoryPath);

        if (!CreateDirectoryApi(directoryPath, lpSecurityAttributes: IntPtr.Zero))
        {
            Win32Exception lastException = new Win32Exception();

            const int ERROR_ALREADY_EXISTS = 183;
            if (lastException.NativeErrorCode == ERROR_ALREADY_EXISTS) return false;

            throw new System.IO.IOException(
                "An exception occurred while creating directory'" + directoryPath + "'".NewLine() + lastException);
        }

        return true;
    }

Puede decidir si el "costo / riesgo" del código de p / invoke no administrado lo vale. La mayoría diría que no, pero al menos ahora tienes una opción.

CreateParentFolder () se deja como un ejercicio para el alumno. Yo uso Directory.CreateDirectory (). Tenga cuidado al obtener el padre de un directorio, ya que es nulo cuando está en la raíz.

Andrew Dennison
fuente
5

Usualmente uso esto:

    /// <summary>
    /// Creates the unique temporary directory.
    /// </summary>
    /// <returns>
    /// Directory path.
    /// </returns>
    public string CreateUniqueTempDirectory()
    {
        var uniqueTempDir = Path.GetFullPath(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
        Directory.CreateDirectory(uniqueTempDir);
        return uniqueTempDir;
    }

Si desea estar absolutamente seguro de que este nombre de directorio no existirá en la ruta temporal, debe verificar si este nombre de directorio único existe e intentar crear otro si realmente existe.

Pero esta implementación basada en GUID es suficiente. No tengo experiencia con ningún problema en este caso. Algunas aplicaciones de MS también usan directorios temporales basados ​​en GUID.

Jan Hlavsa
fuente
1

GetTempPath es la forma correcta de hacerlo; No estoy seguro de cuál es su preocupación sobre este método. Luego puede usar CreateDirectory para hacerlo.


fuente
Un problema es que GetTempFileName creará un archivo de cero bytes. En su lugar, debe usar GetTempPath, GetRandomFileName y CreateDirectory.
Scott Dorman
Lo cual está bien, es posible y factible. Iba a proporcionar código pero Dorman lo obtuvo antes que yo y funciona correctamente.
1

Aquí hay un enfoque algo más de fuerza bruta para resolver el problema de colisión de los nombres de directorio temporales. No es un enfoque infalible, pero reduce significativamente las posibilidades de una colisión de ruta de carpeta.

Potencialmente, se podría agregar otra información relacionada con el proceso o el ensamblaje al nombre del directorio para que la colisión sea aún menos probable, aunque hacer que dicha información sea visible en el nombre del directorio temporal podría no ser deseable. También se podría mezclar el orden con el que se combinan los campos relacionados con el tiempo para hacer que los nombres de las carpetas se vean más aleatorios. Personalmente prefiero dejarlo así simplemente porque me es más fácil encontrarlos todos durante la depuración.

string randomlyGeneratedFolderNamePart = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());

string timeRelatedFolderNamePart = DateTime.Now.Year.ToString()
                                 + DateTime.Now.Month.ToString()
                                 + DateTime.Now.Day.ToString()
                                 + DateTime.Now.Hour.ToString()
                                 + DateTime.Now.Minute.ToString()
                                 + DateTime.Now.Second.ToString()
                                 + DateTime.Now.Millisecond.ToString();

string processRelatedFolderNamePart = System.Diagnostics.Process.GetCurrentProcess().Id.ToString();

string temporaryDirectoryName = Path.Combine( Path.GetTempPath()
                                            , timeRelatedFolderNamePart 
                                            + processRelatedFolderNamePart 
                                            + randomlyGeneratedFolderNamePart);
Paulo de Barros
fuente
0

Como se mencionó anteriormente, Path.GetTempPath () es una forma de hacerlo. También puede llamar a Environment.GetEnvironmentVariable ("TEMP") si el usuario tiene configurada una variable de entorno TEMP.

Si está planeando usar el directorio temporal como un medio para conservar los datos en la aplicación, probablemente debería considerar usar IsolatedStorage como un repositorio para la configuración / estado / etc ...

Chris Rauber
fuente