Tworzysz katalog tymczasowy w systemie Windows?

126

Jaki jest najlepszy sposób na uzyskanie nazwy katalogu tymczasowego w systemie Windows? Widzę, że mogę użyć GetTempPathi GetTempFileNameutworzyć plik tymczasowy, ale czy istnieje odpowiednik funkcji Linux / BSD mkdtempdo tworzenia katalogu tymczasowego?

Josh Kelley
źródło
To pytanie wydaje się trochę trudne do znalezienia. W szczególności nie pojawia się, jeśli wpiszesz „tymczasowy katalog .net” w polu wyszukiwania Stack Overflow. Wydaje się to niefortunne, ponieważ odpowiedzi dotyczą jak dotąd wszystkich odpowiedzi .NET. Czy myślisz, że mógłbyś dodać tag „.net”? (A może znacznik „katalog” lub „katalog tymczasowy”?) A może dodać słowo „.NET” do tytułu? Może również w treści pytania naprzemiennie pojawia się słowo „tymczasowe” i „tymczasowe” - więc jeśli szukasz krótszej formy, nadal uzyskasz dobre dopasowanie wyszukiwania tekstowego. Wydaje się, że nie mam wystarczającej liczby powtórzeń, aby samemu robić te rzeczy. Dzięki.
Chris
Cóż, szukałem odpowiedzi innej niż .NET, więc wolałbym ją pominąć, ale wprowadziłem inne sugerowane zmiany. Dzięki.
Josh Kelley
@Josh Kelley: Właśnie dwukrotnie sprawdziłem API Win32 i jedynymi dostępnymi opcjami są podobne podejście polegające na uzyskaniu ścieżki tymczasowej, wygenerowaniu nazwy pliku ramdom, a następnie utworzeniu katalogu.
Scott Dorman

Odpowiedzi:

230

Nie, nie ma odpowiednika mkdtemp. Najlepszą opcją jest użycie kombinacji GetTempPath i GetRandomFileName .

Potrzebowałbyś kodu podobnego do tego:

public string GetTemporaryDirectory()
{
   string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
   Directory.CreateDirectory(tempDirectory);
   return tempDirectory;
}
Scott Dorman
źródło
3
Wydaje się to trochę niebezpieczne. W szczególności istnieje szansa (mała, ale niezerowa, prawda?), Że Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()) zwróci nazwę katalogu, który już istnieje. Ponieważ Directory.CreateDirectory (tempDirectory) nie zgłosi wyjątku, jeśli tempDirectory już istnieje, ten przypadek nie zostanie wykryty przez aplikację. A potem możesz mieć dwie aplikacje, które będą wzajemnie oddziaływać na swoją pracę. Czy jest bezpieczniejsza alternatywa w .NET?
Chris
22
@Chris: Metoda GetRandomFileName zwraca silny kryptograficznie, losowy ciąg, którego można użyć jako nazwy folderu lub nazwy pliku. Przypuszczam, że teoretycznie jest możliwe, że wynikowa ścieżka mogła już istnieć, ale nie ma innych sposobów, aby to zrobić. Możesz sprawdzić, czy ścieżka istnieje i czy wywołuje ponownie Path.GetRandomFileName () i powtórz.
Scott Dorman
5
@Chris: Tak, jeśli obawiasz się o bezpieczeństwo w tym zakresie, istnieje bardzo niewielka szansa, że ​​inny proces może utworzyć katalog między wywołaniami Path.Combine i Directory.CreateDirectory.
Scott Dorman
8
GetRandomFileName generuje 11 losowych małych liter i cyfr, co oznacza, że ​​rozmiar domeny to (26 + 10) ^ 11 = ~ 57 bitów. Zawsze możesz wykonać dwa połączenia, aby to
wyrównać
27
Zaraz po sprawdzeniu, że katalog nie istnieje, Bóg może wstrzymać wszechświat, wkraść się i utworzyć ten sam katalog z tymi samymi plikami, które masz zamiar napisać, powodując wysadzenie aplikacji, po prostu zrujnowanie twojego dnia.
Triynko,
25

Siekać Path.GetTempFileName() aby podać mi prawidłową, pseudolosową ścieżkę do pliku na dysku, a następnie usuwam plik i tworzę katalog z tą samą ścieżką do pliku.

Dzięki temu nie trzeba sprawdzać, czy ścieżka pliku jest dostępna przez chwilę lub w pętli, zgodnie z komentarzem Chrisa na temat odpowiedzi Scotta Dormana.

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

  return tempFolder;
}

Jeśli naprawdę potrzebujesz bezpiecznej kryptograficznie losowej nazwy, możesz dostosować odpowiedź Scotta, aby wykorzystywała chwilę lub pętlę do kontynuowania próby utworzenia ścieżki na dysku.

Steve Jansen
źródło
7

Lubię używać GetTempPath (), funkcji tworzenia GUID, takiej jak CoCreateGuid () i CreateDirectory ().

Identyfikator GUID został zaprojektowany tak, aby mieć duże prawdopodobieństwo unikalności, a także jest wysoce nieprawdopodobne, aby ktoś ręcznie utworzył katalog o takiej samej formie jak identyfikator GUID (a jeśli tak, metoda CreateDirectory () nie wskaże jego istnienia).

Mateusz
źródło
5

@Chris. Ja również miałem obsesję na punkcie zdalnego ryzyka, że ​​katalog tymczasowy może już istnieć. Dyskusje o losowości i silnych kryptograficznie również mnie nie satysfakcjonują.

Moje podejście opiera się na podstawowym fakcie, że system operacyjny nie może pozwolić 2 wywołaniom na utworzenie pliku, aby oba zakończyły się pomyślnie. Trochę zaskakujące jest to, że projektanci .NET zdecydowali się ukryć funkcjonalność Win32 API dla katalogów, co znacznie to ułatwia, ponieważ zwraca błąd, gdy próbujesz utworzyć katalog po raz drugi. Oto czego używam:

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

To Ty decydujesz, czy „koszt / ryzyko” niezarządzanego kodu p / invoke jest tego warte. Większość powie, że tak nie jest, ale przynajmniej masz teraz wybór.

CreateParentFolder () pozostaje jako ćwiczenie dla ucznia. Używam Directory.CreateDirectory (). Uważaj, aby uzyskać katalog nadrzędny, ponieważ w katalogu głównym jest on pusty.

Andrew Dennison
źródło
5

Zwykle używam tego:

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

Jeśli chcesz mieć absolutną pewność, że ta nazwa katalogu nie będzie istniała w ścieżce tymczasowej, musisz sprawdzić, czy ta unikalna nazwa katalogu istnieje i spróbować utworzyć inny, jeśli naprawdę istnieje.

Ale ta implementacja oparta na identyfikatorze GUID jest wystarczająca. Nie mam doświadczenia z żadnym problemem w tym przypadku. Niektóre aplikacje MS używają również katalogów tymczasowych opartych na GUID.

Jan Hlavsa
źródło
1

GetTempPath to właściwy sposób na zrobienie tego; Nie jestem pewien, jakie masz obawy dotyczące tej metody. Następnie możesz użyć CreateDirectory, aby to zrobić.


źródło
Jednym z problemów jest to, że GetTempFileName utworzy plik o rozmiarze zerowym. Zamiast tego musisz użyć GetTempPath, GetRandomFileName i CreateDirectory.
Scott Dorman
Co jest w porządku, możliwe i wykonalne. Miałem dostarczyć kod, ale Dorman dostał go wcześniej i działa poprawnie.
1

Oto nieco bardziej brutalne podejście do rozwiązania problemu kolizji nazw katalogów tymczasowych. Nie jest to niezawodne podejście, ale znacznie zmniejsza ryzyko kolizji ścieżki folderu.

Można by potencjalnie dodać inne informacje związane z procesem lub zespołem do nazwy katalogu, aby kolizja była jeszcze mniej prawdopodobna, chociaż wyświetlanie takich informacji w nazwie katalogu tymczasowego może nie być pożądane. Można również mieszać kolejność łączenia pól związanych z czasem, aby nazwy folderów wyglądały bardziej losowo. Osobiście wolę zostawić to w ten sposób po prostu dlatego, że łatwiej mi je wszystkie znaleźć podczas debugowania.

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
źródło
0

Jak wspomniano powyżej, Path.GetTempPath () jest jednym ze sposobów na zrobienie tego. Możesz również wywołać zmienną Environment.GetEnvironmentVariable („TEMP”) jeśli użytkownik ma skonfigurowaną zmienną środowiskową TEMP.

Jeśli planujesz używać katalogu tymczasowego jako środka do utrwalania danych w aplikacji, prawdopodobnie powinieneś przyjrzeć się użyciu IsolatedStorage jako repozytorium konfiguracji / stanu / itp ...

Chris Rauber
źródło