Создание временного каталога в Windows?

126

Как лучше всего получить имя временного каталога в Windows? Я вижу, что могу использовать GetTempPathи GetTempFileNameдля создания временного файла, но есть ли какой-либо эквивалент функции Linux / BSD mkdtempдля создания временного каталога?

Джош Келли
источник
Этот вопрос кажется трудновыполнимым. В частности, он не отображается, если вы вводите такие вещи, как «временный каталог .net» в поле поиска переполнения стека. Это кажется досадным, поскольку пока все ответы .NET. Как вы думаете, можно ли добавить тег ".net"? (А может быть, тег «каталог» или «временный каталог»?) Или, может быть, добавить слово «.NET» в заголовок? Возможно, также в теле вопроса вместо слов «temp» указано «временное» - поэтому, если вы будете искать более короткую форму, вы все равно получите хорошее текстовое соответствие. Похоже, у меня недостаточно репутации, чтобы самому заниматься этим. Спасибо.
Крис
Что ж, я искал ответ, не относящийся к .NET, поэтому я бы предпочел опустить его, но я внес другие изменения, которые вы предложили. Спасибо.
Джош Келли,
@Josh Kelley: Я только что дважды проверил Win32 API, и единственные варианты, которые существуют, - это использовать аналогичный подход: получение временного пути, создание имени файла 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;
}

Если вам действительно нужно криптографически безопасное случайное имя, вы можете адаптировать ответ Скотта, чтобы использовать цикл while или do, чтобы продолжать попытки создать путь на диске.

Стив Янсен
источник
7

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

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

Мэтью
источник
5

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

Мой подход основан на фундаментальном факте, что операционная система не должна позволять двум вызовам создавать файл, чтобы оба были успешными. Немного удивительно, что разработчики .NET решили скрыть функциональность Win32 API для каталогов, что значительно упрощает эту задачу, поскольку при второй попытке создания каталога возвращается ошибка. Вот что я использую:

    [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.

Если вы планируете использовать временный каталог в качестве средства сохранения данных в приложении, вам, вероятно, следует рассмотреть возможность использования IsolatedStorage в качестве репозитория для конфигурации / состояния / и т. Д.

Крис Раубер
источник