Створення тимчасового каталогу в Windows?


126

Який найкращий спосіб отримати ім'я тимчасового каталогу в Windows? Я бачу, що я можу використовувати GetTempPathта GetTempFileNameстворювати тимчасовий файл, але чи є еквівалент функції Linux / BSD mkdtempдля створення тимчасового каталогу?


Це питання здається трохи важким для пошуку. Зокрема, це не відображається, якщо ви вводите такі речі, як "temp каталог .net" у вікні пошуку "Переповнення стека". Це здається прикрою, оскільки відповіді поки що всі .NET відповіді. Як ви думаєте, ви могли б додати тег ".net"? (А може бути тег "каталог" або "тимчасовий каталог"?) Чи, можливо, до заголовка додати слово ".NET"? Можливо, також в тілі запитання по черзі висловлюється "temp" з "тимчасовим" - так що якщо ви будете шукати більш коротку форму, ви все одно отримаєте хороший збіг пошуку тексту. Мені здається, у мене немає достатньої кількості представників, щоб робити ці речі сам. Дякую.
Кріс

Ну, я шукав відповідь, яка не є .NET, тому я вважаю за краще залишити це, але я вніс інші зміни, які ви запропонували. Дякую.
Джош Келлі

@Josh Kelley: Я лише двічі перевірив API Win32, і єдині варіанти є дотримуватися аналогічного підходу до отримання тимчасового шляху, генерування імені файлу ramdom, а потім створення каталогу.
Скотт Дорман

Відповіді:


230

Ні, немає еквівалента mkdtemp. Найкращий варіант - використовувати комбінацію GetTempPath і GetRandomFileName .

Вам знадобиться код, подібний до цього:

public string GetTemporaryDirectory()
{
   string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
   Directory.CreateDirectory(tempDirectory);
   return tempDirectory;
}

3
Це здається трохи небезпечним. Зокрема, є ймовірність (невелика, але не нульова, правда?), Що Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()) поверне ім'я каталогу, який вже існує. Оскільки Directory.CreateDirectory (tempDirectory) не викине виключення, якщо tempDirectory вже існує, цей випадок не буде виявлений вашою програмою. І тоді у вас можуть бути дві програми, що наступають на роботу один одного. Чи є безпечніша альтернатива в .NET?
Кріс

22
@Chris: Метод GetRandomFileName повертає криптографічно міцну випадкову рядок, який можна використовувати як ім'я папки, так і ім'я файлу. Я припускаю, що теоретично можливо, що отриманий шлях вже міг би існувати, але інших способів це не існує. Ви можете перевірити, чи існує шлях, і чи викликає він ще раз Path.GetRandomFileName (), і повторити.
Скотт Дорман

5
@Chris: Так, якщо ви турбуєтесь про безпеку до такої міри, є дуже невеликий шанс, що інший процес може створити каталог між викликами Path.Combine та Directory.CreateDirectory.
Скотт Дорман

8
GetRandomFileName генерує 11 випадкових малих літер і цифр, тобто розмір домену (26 + 10) ^ 11 = ~ 57 біт. Ви завжди можете здійснити два дзвінки на квадратний рівень
Марті Ніл,

27
Щойно перевіривши, що каталог не існує, Бог може призупинити всесвіт, прокрастись і створити той самий каталог із усіма тими ж файлами, які ви збираєтеся написати, внаслідок чого ваш додаток вибухне, просто зіпсувавши ваш день.
Трайнко

25

Я рубаю, Path.GetTempFileName()щоб дати мені дійсний, псевдовипадковий файловий шлях на диску, потім видалити файл і створити каталог з тим же шлях до файлу.

Це дозволяє уникнути необхідності перевіряти, чи доступний шлях до файлу через деякий час або цикл, за коментарем Кріса на відповідь Скотта Дормана.

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

  return tempFolder;
}

Якщо вам справді потрібне криптографічно захищене випадкове ім’я, можливо, ви захочете адаптувати відповідь Скотта до певного часу або робити цикл, щоб намагатися створити шлях на диску.


7

Мені подобається використовувати GetTempPath (), функцію створення GUID на зразок CoCreateGuid () та CreateDirectory ().

GUID розроблений так, щоб мати високу ймовірність унікальності, а також дуже малоймовірно, щоб хтось вручну створив каталог з тією ж формою, що і GUID (а якщо це буде, то CreateDirectory () не зможе вказати на його існування.)


5

@Chris. Я теж був одержимий віддаленим ризиком того, що тимчасовий каталог вже може існувати. Дискусії про випадкові та криптографічно сильні теж мене повністю не задовольняють.

Мій підхід базується на фундаментальному факті, що O / S не повинен дозволяти 2 дзвінки створити файл, щоб обоє були успішними. Мало дивно, що дизайнери .NET вирішили приховати функціональність API Win32 для каталогів, що робить це набагато простіше, оскільки він повертає помилку при спробі створення каталогу вдруге. Ось що я використовую:

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

Ви вирішуєте, чи варта "вартість / ризик" некерованого коду p / invoke. Більшість сказала б, що це не так, але принаймні у вас зараз є вибір.

CreateParentFolder () залишається студентом як вправа. Я використовую Directory.CreateDirectory (). Будьте уважні до отримання батьківського каталогу, оскільки він є нульовим, коли він знаходиться в корені.


5

Я зазвичай використовую це:

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

Якщо ви хочете бути абсолютно впевненим, що ім'я цього каталогу не існує в темп шляху, вам потрібно перевірити, чи існує це унікальне ім'я каталогу, і спробувати створити інше, якщо воно дійсно існує.

Але цього впровадження на основі GUID є достатнім. Я не маю досвіду жодної проблеми в цій справі. Деякі програми MS використовують і тимчасові каталоги на основі GUID.


1

GetTempPath - це правильний спосіб зробити це; Я не впевнений, в чому полягає ваша турбота щодо цього методу. Потім ви можете використовувати CreateDirectory, щоб зробити його.


Одна з проблем полягає в тому, що GetTempFileName створить файл з нульовим байтом. Вам потрібно використовувати GetTempPath, GetRandomFileName та CreateDirectory замість цього.
Скотт Дорман

Що добре, можливо і виконано. Я збирався надати код, але Дорман дістав його до мене, і він працює правильно.

1

Ось дещо більш жорстокий підхід до вирішення проблеми зіткнення тимчасових імен каталогів. Це не непогрішний підхід, але він значно зменшує шанси на зіткнення шляху папки.

Можна потенційно додати іншу інформацію, пов’язану з процесом або збіркою, до імені каталогу, щоб зробити зіткнення ще менш ймовірним, хоча зробити таку інформацію видимою у імені тимчасового каталогу може бути небажаним. Можна також змішати порядок, з яким поєднуються часові поля, щоб імена папок виглядали більш випадковими. Я особисто вважаю за краще залишити це таким чином просто тому, що мені простіше знайти їх під час налагодження.

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);

0

Як було сказано вище, Path.GetTempPath () - це один із способів зробити це. Ви також можете зателефонувати Environment.GetEnvironmentVariable ("TEMP"), якщо у користувача встановлена ​​змінна середовище TEMP.

Якщо ви плануєте використовувати каталог temp як засіб збереження даних у програмі, вам, мабуть, слід поглянути на використання IsolatedStorage як сховища для конфігурації / стану / тощо ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.