Создать временный каталог в PowerShell?

PowerShell 5 представляет New-TemporaryFile командлет, что очень удобно. Как я могу сделать то же самое, но вместо файла создать каталог? Есть ли командлет New-TemporaryDirectory?


person Michael Kropat    schedule 01.01.2016    source источник


Ответы (9)


Я думаю, что это можно сделать без зацикливания, используя GUID для имени каталога:

function New-TemporaryDirectory {
    $parent = [System.IO.Path]::GetTempPath()
    [string] $name = [System.Guid]::NewGuid()
    New-Item -ItemType Directory -Path (Join-Path $parent $name)
}

Исходная попытка GetRandomFileName

Вот мой порт этого решения C #:

function New-TemporaryDirectory {
    $parent = [System.IO.Path]::GetTempPath()
    $name = [System.IO.Path]::GetRandomFileName()
    New-Item -ItemType Directory -Path (Join-Path $parent $name)
}

Анализ возможности столкновения

Насколько вероятно, что GetRandomFileName вернет имя, которое уже существует во временной папке?

  • Имена файлов возвращаются в форме XXXXXXXX.XXX, где X может быть строчной буквой или цифрой.
  • Это дает нам 36 ^ 11 комбинаций, что в битах составляет около 2 ^ 56.
  • Вызывая парадокс дня рождения, мы ожидаем столкновения, как только мы дойдем до 2 ^ 28 элементов в папка, а это около 360 миллионов
  • NTFS поддерживает около 2 ^ 32 элементов в папке, поэтому можно получить коллизию, используя GetRandomFileName

NewGuid с другой стороны, может быть одной из 2 ^ 122 возможностей, что делает столкновения почти невозможны.

person Michael Kropat    schedule 01.01.2016
comment
Я бы, вероятно, поместил GetRandomFileName() и New-Item в цикл, чтобы автоматически повторить попытку в случае конфликта имен. - person Ansgar Wiechers; 02.01.2016
comment
@sodawillow Отличный вопрос. Я ошибочно подумал, что вам нужно -Force для создания несуществующих родительских каталогов, но я ошибался и обновил ответ. - person Michael Kropat; 02.01.2016
comment
Процитируем MSDN: -Force позволяет командлету создавать элемент, который записывает поверх существующего элемента, доступного только для чтения. Реализация варьируется от поставщика к поставщику. Для получения дополнительной информации см. About_Providers. Даже используя параметр Force, командлет не может переопределить ограничения безопасности. - person sodawillow; 02.01.2016
comment
Если у вас есть 360 миллионов элементов в вашей временной папке, я думаю, у вас есть более серьезные проблемы, чем сбой какого-либо сценария PS из-за конфликта имен. - person jpmc26; 22.03.2017

Мне также нравятся однострочные сообщения, и я прошу проголосовать против. Все, о чем я прошу, это выразить словами мои смутные негативные чувства по этому поводу.

New-TemporaryFile | %{ rm $_; mkdir $_ }

В зависимости от того, какой вы пурист, вы можете сделать %{ mkdir $_-d }, оставив заполнитель, чтобы избежать коллизий.

И на Join-Path $env:TEMP $(New-Guid) | %{ mkdir $_ } тоже стоит стоять.

person nik.shornikov    schedule 16.01.2017
comment
Мне кажется, это хорошая фраза. Пока не будет доказано обратное, я предполагаю, что существует небольшая вероятность состояния гонки, но это не является серьезной проблемой для 99% случаев использования. - person Michael Kropat; 16.01.2017
comment
Остерегайтесь, я видел, как стратегии удаления и воссоздания терпят неудачу в системах с медленным антивирусным ПО, как намекал в случае асинхронного копирования и удаления Рэймонд Чен на blogs.msdn.microsoft.com/oldnewthing/20120907-00/?p=6663. - person Nathan Schubkegel; 09.01.2019
comment
Вот вариант собственной оболочки PowerShell: New-TemporaryFile | % { Remove-Item $_; New-Item -ItemType Directory -Path $_ } - person NReilingh; 02.03.2019
comment
Добавление -d не позволяет избежать коллизий. (Что, если вы используете один и тот же подход в двух разных сценариях одновременно?) Но тогда вы не пытались избежать столкновений, не так ли? ;-) - person jpaugh; 02.10.2019
comment
Чтобы уточнить, оставление заполнителя - это то, что позволило бы избежать столкновений (до тех пор, пока командлет не будет повторно реализован), если бы они были риском на практике. Произвольный детерминированный -d предназначен для предотвращения так называемого «столкновения» между результирующим файлом и каталогом. - person nik.shornikov; 04.10.2019
comment
Поддерживать чужой код труднее, чем создавать свой собственный. Конечно, вы можете создать плотный однострочник. Боже, помоги бедняге, пытающемуся понять твое намерение поддержать твою рекомендацию. Если ваше решение неочевидно, вероятность того, что будущий разработчик попытается поддержать ваш код и сделает ошибку, значительно возрастет. Если код очевиден и понятен, вероятность ошибки уменьшается. Некоторые могут возразить - это то, для чего нужны комментарии, но другие возразят, что код должен быть достаточно ясным, чтобы исключить необходимость в комментариях. - person barrypicker; 02.01.2020
comment
Указанные вами команды создают каталог, но не изменяют cwd в каталог и не сохраняют имя нового каталога в переменной для будущего использования. Не могли бы вы обновить ответ, чтобы каталог был создан и также сохранен в переменной? - person Michael Altfield; 14.09.2020
comment
@NathanSchubkegel Это хороший момент. К имени создаваемого временного каталога может быть добавлен символ, чтобы избежать конфликта имен, если файл не был удален до создания каталога. New-TemporaryFile | %{ rm $_; mkdir "${_}d" }. - person Dave F; 08.03.2021

Вот вариант ответа пользователя4317867. Я создаю новый каталог в пользовательской папке Windows Temp и делаю путь к временной папке доступным как переменная ($tempFolderPath):

$tempFolderPath = Join-Path $Env:Temp $(New-Guid)
New-Item -Type Directory -Path $tempFolderPath | Out-Null

Вот тот же сценарий, доступный как однострочный:

$tempFolderPath = Join-Path $Env:Temp $(New-Guid); New-Item -Type Directory -Path $tempFolderPath | Out-Null

А вот как выглядит полный путь к временной папке ($tempFolderPath):

C:\Users\MassDotNet\AppData\Local\Temp\2ae2dbc4-c709-475b-b762-72108b8ecb9f
person Mass Dot Net    schedule 10.12.2019

Если вам нужно решение с циклами, которое гарантированно не будет содержать гонок и конфликтов, то вот оно:

function New-TemporaryDirectory {
  $parent = [System.IO.Path]::GetTempPath()
  do {
    $name = [System.IO.Path]::GetRandomFileName()
    $item = New-Item -Path $parent -Name $name -ItemType "directory" -ErrorAction SilentlyContinue
  } while (-not $item)
  return $Item.FullName
}

Согласно анализу, приведенному в ответе Майкла Кропата, в подавляющем большинстве случаев это будет проходить через цикл только один раз. Редко он пройдет дважды. Практически никогда не пройдет трижды.

person John Freeman    schedule 28.02.2019
comment
Это не сработает, он просто застрянет в бесконечном цикле, если когда-либо произойдет столкновение ... потому что $ name не обновляется внутри цикла. - person Per von Zweigbergk; 02.04.2019
comment
Верно, моя ошибка. Я просто переместил создание имени в цикл. - person John Freeman; 02.04.2019
comment
Также он выдаст некрасивую ошибку, если по какой-либо причине выйдет из строя. Также он возвращает два объекта (потому что New-Item возвращает объект). Ваш общий подход хорош, но я отредактировал ваше сообщение, чтобы исправить обнаруженные мной проблемы. Насколько я могу судить, вроде работает. :-) - person Per von Zweigbergk; 02.04.2019

Вот моя попытка:

function New-TemporaryDirectory {
    $path = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())

    #if/while path already exists, generate a new path
    while(Test-Path $path)) {
        $path = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
    }

    #create directory with generated path
    New-Item -ItemType Directory -Path $path
}
person sodawillow    schedule 02.01.2016
comment
Этот код сталкивается с условиями гонки между концом while и New-Item команды. - person ComFreek; 04.04.2018

У .NET уже довольно давно есть [System.IO.Path]::GetTempFileName(); вы можете использовать это для создания файла (и сохранения имени), а затем создать папку с тем же именем после удаления файла.

$tempfile = [System.IO.Path]::GetTempFileName();
remove-item $tempfile;
new-item -type directory -path $tempfile;
person alroc    schedule 01.01.2016
comment
Где $tempfilename используется? - person sodawillow; 02.01.2016
comment
Ошибка с моей стороны, я не скопировал / вставил свою окончательную версию. Исправлено сейчас. - person alroc; 02.01.2016
comment
Этот код сталкивается с тем же условием гонки , что и этот ответ. - person ComFreek; 04.04.2018

Я люблю одинарные вкладыши, если это возможно. @alroc .NET также имеет [System.Guid]::NewGuid()

$temp = [System.Guid]::NewGuid();new-item -type directory -Path d:\$temp

Directory: D:\


Mode                LastWriteTime     Length Name                                                                                                                        
----                -------------     ------ ----                                                                                                                        
d----          1/2/2016  11:47 AM            9f4ef43a-a72a-4d54-9ba4-87a926906948  
person user4317867    schedule 02.01.2016

если хотите, можете проявить крайность и вызвать функцию Windows API GetTempPathA() следующим образом:

# DWORD GetTempPathA(
#   DWORD nBufferLength,
#   LPSTR lpBuffer
# );

$getTempPath = @"
using System;
using System.Runtime.InteropServices;
using System.Text;

public class getTempPath {
    [DllImport("KERNEL32.DLL", EntryPoint = "GetTempPathA")]
    public static extern uint GetTempPath(uint nBufferLength, [Out] StringBuilder lpBuffer);
}
"@

Add-Type $getTempPath

$str = [System.Text.StringBuilder]::new()
$MAX_PATH = 260
$catch_res = [getTempPath]::GetTempPath($MAX_PATH, $str)
Write-Host $str.ToString() #echos temp path to STDOUT
# ... continue your code here and create sub folders as you wish ...
  1. https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppatha
person AK_    schedule 25.03.2021
comment
Это точный код, который Path.GetTempPath делает для вас внутри компании. - person Martin Prikryl; 26.03.2021
comment
@ martin-prikryl хорошее продолжение, вы знаете, как я могу просмотреть этот внутренний код? - person AK_; 26.03.2021
comment
referenceource.microsoft.com/#mscorlib/system/io/ + Хотя он будет правильно использовать Unicode-версию API, в отличие от вашей устаревшей версии Ansi. - person Martin Prikryl; 26.03.2021

Расширение из ответа Майкла Кропата: https://stackoverflow.com/a/34559554/8083582

Function New-TemporaryDirectory {
  $tempDirectoryBase = [System.IO.Path]::GetTempPath();
  $newTempDirPath = [String]::Empty;
  Do {
    [string] $name = [System.Guid]::NewGuid();
    $newTempDirPath = (Join-Path $tempDirectoryBase $name);
  } While (Test-Path $newTempDirPath);

  New-Item -ItemType Directory -Path $newTempDirPath;
  Return $newTempDirPath;
}

Это должно устранить любые проблемы с коллизиями.

person Mitchell R    schedule 27.07.2017
comment
Этот код сталкивается с условиями гонки между концом while и New-Item команды, точно так же, как другой ответ выше. - person ComFreek; 04.04.2018