Créer un répertoire temporaire sous Windows?

126

Quelle est la meilleure façon d'obtenir un nom de répertoire temporaire dans Windows? Je vois que je peux utiliser GetTempPathet GetTempFileNamecréer un fichier temporaire, mais existe-t-il un équivalent à la fonction Linux / BSD mkdtemppour créer un répertoire temporaire?

Josh Kelley
la source
Cette question semble un peu difficile à trouver. En particulier, il n'apparaît pas si vous saisissez des éléments comme "répertoire temporaire .net" dans la zone de recherche Stack Overflow. Cela semble malheureux, car les réponses sont pour l'instant toutes des réponses .NET. Pensez-vous que vous pourriez ajouter la balise ".net"? (Et peut-être la balise «répertoire» ou «répertoire temporaire»?) Ou peut-être ajouter le mot «.NET» au titre? Peut-être aussi dans le corps de la question alterner entre «temp» et «temporaire» - donc si vous recherchez la forme la plus courte, vous obtiendrez toujours une bonne correspondance de recherche de texte. Je ne semble pas avoir assez de représentants pour faire ces choses moi-même. Merci.
Chris
Eh bien, je cherchais une réponse non-.NET, donc je préférerais laisser cela de côté, mais j'ai fait les autres modifications que vous avez suggérées. Merci.
Josh Kelley
@Josh Kelley: Je viens de vérifier deux fois l'API Win32 et les seules options disponibles sont de suivre une approche similaire pour obtenir le chemin temporaire, générer un nom de fichier ramdom, puis créer un répertoire.
Scott Dorman

Réponses:

230

Non, il n'y a pas d'équivalent à mkdtemp. La meilleure option consiste à utiliser une combinaison de GetTempPath et GetRandomFileName .

Vous auriez besoin d'un code similaire à celui-ci:

public string GetTemporaryDirectory()
{
   string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
   Directory.CreateDirectory(tempDirectory);
   return tempDirectory;
}
Scott Dorman
la source
3
Cela semble un peu dangereux. En particulier, il y a une chance (petite, mais non nulle, non?) Que Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()) renvoie le nom d'un répertoire qui existe déjà. Comme Directory.CreateDirectory (tempDirectory) ne lèvera pas d'exception si tempDirectory existe déjà, ce cas ne sera pas détecté par votre application. Et puis vous pouvez avoir deux applications qui se marient mutuellement. Existe-t-il une alternative plus sûre dans .NET?
Chris
22
@Chris: La méthode GetRandomFileName renvoie une chaîne aléatoire forte d'un point de vue cryptographique qui peut être utilisée comme nom de dossier ou nom de fichier. Je suppose qu'il est théoriquement possible que le chemin résultant puisse déjà exister, mais il n'y a pas d'autres moyens de le faire. Vous pouvez vérifier si le chemin existe et s'il appelle à nouveau Path.GetRandomFileName () et recommencer.
Scott Dorman
5
@Chris: Oui, si vous êtes préoccupé par la sécurité dans cette mesure, il y a très peu de chances qu'un autre processus puisse créer le répertoire entre les appels Path.Combine et Directory.CreateDirectory.
Scott Dorman
8
GetRandomFileName génère 11 lettres et chiffres aléatoires minuscules, ce qui signifie que la taille du domaine est (26 + 10) ^ 11 = ~ 57 bits. Vous pouvez toujours faire deux appels pour faire la paix
Marty Neal
27
Juste après avoir vérifié que le répertoire n'existe pas, Dieu pourrait mettre l'univers en pause, se faufiler et créer le même répertoire avec tous les mêmes fichiers que vous êtes sur le point d'écrire, faisant exploser votre application, juste pour gâcher votre journée.
Triynko
25

Je pirate Path.GetTempFileName()pour me donner un chemin de fichier pseudo-aléatoire valide sur le disque, puis supprime le fichier et crée un répertoire avec le même chemin de fichier.

Cela évite d'avoir à vérifier si le chemin du fichier est disponible dans un moment ou une boucle, selon le commentaire de Chris sur la réponse de Scott Dorman.

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

  return tempFolder;
}

Si vous avez vraiment besoin d'un nom aléatoire sécurisé cryptographiquement, vous pouvez adapter la réponse de Scott pour utiliser un moment ou faire une boucle pour continuer à essayer de créer un chemin sur le disque.

Steve Jansen
la source
7

J'aime utiliser GetTempPath (), une fonction de création de GUID comme CoCreateGuid () et CreateDirectory ().

Un GUID est conçu pour avoir une forte probabilité d'unicité, et il est également hautement improbable que quelqu'un crée manuellement un répertoire avec la même forme qu'un GUID (et s'ils le font, CreateDirectory () échouera en indiquant son existence.)

Matthieu
la source
5

@Chris. J'étais aussi obsédé par le risque distant qu'un répertoire temporaire puisse déjà exister. Les discussions sur le hasard et la cryptographie forte ne me satisfont pas complètement non plus.

Mon approche repose sur le fait fondamental que l'O / S ne doit pas permettre à 2 appels de créer un fichier pour que les deux réussissent. Il est un peu surprenant que les concepteurs .NET aient choisi de masquer la fonctionnalité de l'API Win32 pour les répertoires, ce qui rend cela beaucoup plus facile, car cela renvoie une erreur lorsque vous essayez de créer un répertoire pour la deuxième fois. Voici ce que j'utilise:

    [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;
    }

Vous décidez si le «coût / risque» du code p / invoke non géré en vaut la peine. La plupart diraient que ce n'est pas le cas, mais au moins, vous avez maintenant le choix.

CreateParentFolder () est laissé comme exercice à l'étudiant. J'utilise Directory.CreateDirectory (). Faites attention à obtenir le parent d'un répertoire, car il est nul à la racine.

Andrew Dennison
la source
5

J'utilise habituellement ceci:

    /// <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 vous voulez être absolument sûr que ce nom de répertoire n'existe pas dans le chemin temporaire, vous devez vérifier si ce nom de répertoire unique existe et essayer d'en créer un autre s'il existe réellement.

Mais cette implémentation basée sur GUID est suffisante. Je n'ai aucune expérience avec aucun problème dans ce cas. Certaines applications MS utilisent également des répertoires temporaires basés sur GUID.

Jan Hlavsa
la source
1

GetTempPath est la bonne façon de le faire; Je ne sais pas quelle est votre préoccupation concernant cette méthode. Vous pouvez ensuite utiliser CreateDirectory pour le créer.


la source
Un problème est que GetTempFileName créera un fichier de zéro octet. Vous devez utiliser GetTempPath, GetRandomFileName et CreateDirectory à la place.
Scott Dorman
Ce qui est bien, possible et faisable. J'allais fournir du code mais Dorman l'a reçu avant moi, et cela fonctionne correctement.
1

Voici une approche un peu plus brutale pour résoudre le problème de collision des noms de répertoires temporaires. Ce n'est pas une approche infaillible, mais elle réduit considérablement les risques de collision de chemin de dossier.

On pourrait éventuellement ajouter d'autres informations relatives au processus ou à l'assemblage au nom du répertoire pour rendre la collision encore moins probable, bien que rendre ces informations visibles sur le nom du répertoire temporaire ne soit pas souhaitable. On pourrait également mélanger l'ordre dans lequel les champs liés au temps sont combinés pour rendre les noms de dossier plus aléatoires. Personnellement, je préfère le laisser ainsi simplement parce qu'il m'est plus facile de tous les trouver pendant le débogage.

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
la source
0

Comme mentionné ci-dessus, Path.GetTempPath () est une façon de le faire. Vous pouvez également appeler Environment.GetEnvironmentVariable ("TEMP") si l'utilisateur a configuré une variable d'environnement TEMP.

Si vous prévoyez d'utiliser le répertoire temporaire comme moyen de persister des données dans l'application, vous devriez probablement envisager d'utiliser IsolatedStorage comme référentiel pour configuration / state / etc ...

Chris Rauber
la source