Criando um diretório temporário no Windows?

126

Qual é a melhor maneira de obter um nome de diretório temporário no Windows? Vejo que posso usar GetTempPathe GetTempFileNamecriar um arquivo temporário, mas há algum equivalente à função Linux / BSD mkdtemppara criar um diretório temporário?

Josh Kelley
fonte
Esta pergunta parece um pouco difícil de encontrar. Em particular, ele não aparece se você digitar coisas como "temp directory .net" na caixa de pesquisa Estouro de pilha. Isso parece lamentável, já que as respostas são até agora todas as respostas do .NET. Você acha que poderia adicionar a tag ".net"? (E talvez a tag "diretório" ou "diretório temporário"?) Ou talvez adicione a palavra ".NET" ao título? Talvez também no corpo da pergunta se alterne entre "temp" e "temporário" - por isso, se você procurar pelo formulário mais curto, ainda conseguirá uma boa correspondência de pesquisa de texto. Parece que não tenho representantes suficientes para fazer essas coisas sozinho. Obrigado.
Chris
Bem, eu estava procurando uma resposta que não fosse o .NET, então prefiro deixar isso de fora, mas fiz as outras edições que você sugeriu. Obrigado.
21139 Josh Kelley
Josh Kelley: Acabei de verificar duas vezes a API do Win32 e as únicas opções para seguir uma abordagem semelhante de obter o caminho temporário, gerar um nome de arquivo ramdom e criar um diretório.
Scott Dorman

Respostas:

230

Não, não há equivalente ao mkdtemp. A melhor opção é usar uma combinação de GetTempPath e GetRandomFileName .

Você precisaria de código semelhante a este:

public string GetTemporaryDirectory()
{
   string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
   Directory.CreateDirectory(tempDirectory);
   return tempDirectory;
}
Scott Dorman
fonte
3
Isso parece um pouco perigoso. Em particular, há uma chance (pequena, mas diferente de zero, certo?) De que Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()) retorne o nome de um diretório que já existe. Como o Directory.CreateDirectory (tempDirectory) não lançará uma exceção se o tempDirectory já existir, esse caso não será detectado pelo seu aplicativo. E então você pode ter dois aplicativos que pisam no trabalho um do outro. Existe alguma alternativa mais segura no .NET?
Chris
22
@ Chris: O método GetRandomFileName retorna uma sequência aleatória criptograficamente forte que pode ser usada como um nome de pasta ou um nome de arquivo. Suponho que seja teoricamente possível que o caminho resultante já exista, mas não há outras maneiras de fazer isso. Você pode verificar se o caminho existe e chamar Path.GetRandomFileName () novamente e repetir.
21730 Scott Dorman
5
@ Chris: Sim, se você estiver preocupado com a segurança nessa medida há uma muito pequena chance de que outro processo poderia criar o diretório entre o Path.Combine e as chamadas Directory.CreateDirectory.
22430 Scott Dorman
8
GetRandomFileName gera 11 letras minúsculas e números aleatórios, significando que o tamanho do domínio é (26 + 10) ^ 11 = ~ 57 bits. Você sempre pode fazer duas chamadas para enquadrar-lo
Marty Neal
27
Logo depois de verificar se o diretório não existe, Deus pode pausar o universo, entrar furtivamente e criar o mesmo diretório com os mesmos arquivos que você está prestes a escrever, causando a explosão do aplicativo, apenas para arruinar o seu dia.
Triynko
25

Eu cortei Path.GetTempFileName() para me fornecer um caminho de arquivo pseudo-aleatório válido no disco, excluo o arquivo e crio um diretório com o mesmo caminho de arquivo.

Isso evita a necessidade de verificar se o caminho do arquivo está disponível em um tempo ou loop, de acordo com o comentário de Chris sobre a resposta de Scott Dorman.

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

  return tempFolder;
}

Se você realmente precisa de um nome aleatório criptograficamente seguro, convém adaptar a resposta de Scott para usar um while ou loop para continuar tentando criar um caminho no disco.

Steve Jansen
fonte
7

Eu gosto de usar GetTempPath (), uma função de criação de GUID como CoCreateGuid () e CreateDirectory ().

Um GUID é projetado para ter uma alta probabilidade de exclusividade, e também é altamente improvável que alguém crie manualmente um diretório com o mesmo formato de um GUID (e, se o fizer, o CreateDirectory () falhará indicando sua existência.)

Mateus
fonte
5

@Chris. Eu também estava obcecado pelo risco remoto de um diretório temporário já existir. As discussões sobre aleatória e criptograficamente forte também não me satisfazem completamente.

Minha abordagem baseia-se no fato fundamental de que o sistema operacional não deve permitir 2 chamadas para criar um arquivo para que ambos tenham êxito. É um pouco surpreendente que os designers do .NET optem por ocultar a funcionalidade da API do Win32 para diretórios, o que facilita muito isso, pois retorna um erro quando você tenta criar um diretório pela segunda vez. Aqui está o que eu 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;
    }

Você decide se o "custo / risco" do código p / invocação não gerenciado vale a pena. A maioria diria que não, mas pelo menos agora você tem uma escolha.

CreateParentFolder () é deixado como um exercício para o aluno. Eu uso Directory.CreateDirectory (). Cuidado ao obter o pai de um diretório, pois ele é nulo na raiz.

Andrew Dennison
fonte
5

Eu costumo usar isso:

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

Se você deseja ter certeza absoluta de que esse nome de diretório não existe no caminho temporário, verifique se esse nome de diretório exclusivo existe e tente criar outro, se ele realmente existir.

Mas essa implementação baseada em GUID é suficiente. Não tenho experiência com nenhum problema neste caso. Alguns aplicativos MS também usam diretórios temporários baseados em GUID.

Jan Hlavsa
fonte
1

GetTempPath é a maneira correta de fazer isso; Não sei ao certo qual é a sua preocupação com esse método. Você pode usar o CreateDirectory para fazer isso.


fonte
Um problema é que GetTempFileName criará um arquivo de zero byte. Você precisa usar GetTempPath, GetRandomFileName e CreateDirectory.
10118 Scott Dorman
O que é bom, possível e factível. Eu iria fornecer o código, mas Dorman o obteve antes de mim e funciona corretamente.
1

Aqui está uma abordagem de força bruta para resolver o problema de colisão para nomes de diretório temporários. Não é uma abordagem infalível, mas reduz significativamente as chances de uma colisão no caminho da pasta.

É possível adicionar outras informações relacionadas ao processo ou à montagem ao nome do diretório para tornar a colisão ainda menos provável, embora tornar essas informações visíveis no nome do diretório temporário possa não ser desejável. Pode-se também misturar a ordem com a qual os campos relacionados ao tempo são combinados para tornar os nomes das pastas mais aleatórios. Pessoalmente, prefiro deixar dessa maneira simplesmente porque é mais fácil encontrá-las durante a depuração.

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
fonte
0

Como mencionado acima, Path.GetTempPath () é uma maneira de fazer isso. Você também pode chamar Environment.GetEnvironmentVariable ("TEMP") se o usuário tiver uma variável de ambiente TEMP configurada.

Se você planeja usar o diretório temp como um meio de persistência de dados no aplicativo, provavelmente deve usar o IsolatedStorage como um repositório para configuration / state / etc ...

Chris Rauber
fonte