9个回答

91

我认为可以通过使用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
  • 引用生日悖论,我们预计当文件夹中的项数达到约360万个时会发生冲突
  • NTFS支持在文件夹中存储约2^32个项目,因此使用GetRandomFileName可能会导致冲突

另一方面,NewGuid可以有2^122种可能性,使得冲突几乎不可能发生。


2
我会把GetRandomFileName()New-Item放在一个循环中,以便在名称冲突的情况下自动重试。 - Ansgar Wiechers
@sodawillow 很好的问题。我错误地认为您需要-Force来创建不存在的父目录,但我错了,所以我更新了答案。 - Michael Kropat
引用MSDN的话:*-Force允许cmdlet创建一个覆盖现有只读项的项目。实现因提供程序而异。有关更多信息,请参见about_Providers。即使使用Force参数,cmdlet也无法覆盖安全限制。* - sodawillow
21
如果你的临时文件夹中有3.6亿个项目,我认为你面临的问题比一些因名称冲突而导致脚本失败要更大。 - jpmc26
1
可以通过以下方式实现稍短的目录名称:$name = (New-Guid).ToString('n'),这将创建一个类似于 450d881de6054d5894c7f7378bbb9f51 的字符串。 - zett42
显示剩余2条评论

37

我也喜欢一句话的表述方式,虽然我担心会被踩。我的请求只是希望你把我自己的模糊负面情绪用言语表达出来。

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

根据你是哪种纯粹主义者,你可以执行%{ mkdir $_-d },留下占位符以避免冲突。

同时也可以采用Join-Path $env:TEMP $(New-Guid) | %{ mkdir $_ } 的方式。


3
听起来对我来说是个不错的一句话。在有证据表明相反之前,我会认为出现竞态条件的可能性很小,但这对于99%的使用情况并不是真正的问题。 - Michael Kropat
4
请注意,我曾见过在反病毒软件运行缓慢的系统上,“删除和重建”策略失败的情况,正如雷蒙德·陈在“异步复制和删除案例”中所暗示的那样。该文章链接为:https://blogs.msdn.microsoft.com/oldnewthing/20120907-00/?p=6663。 - Nathan Schubkegel
2
这是“本地PowerShell”变体:New-TemporaryFile | % { Remove-Item $_; New-Item -ItemType Directory -Path $_ } - NReilingh
1
澄清一下,保留占位符是避免冲突的方法(直到重新实现命令)如果它们在实践中存在风险。任意确定性的 -d 是为了避免所谓的“碰撞”,导致文件和目录之间发生问题。 - nik.shornikov
支持别人的代码比创建自己的代码更困难。当然,你可以创建一个密集的一行代码。天哪,帮助那些试图理解你的意图以支持你的建议的可怜家伙。如果你的解决方案不明显,未来开发人员尝试支持你的代码时犯错误的可能性会大大增加。如果代码是明显和清晰的,犯错误的可能性就会减少。有人可能会争辩 - 这就是注释的作用,但其他人会反驳说,代码应该足够清晰,以消除对注释的需求。 - barrypicker
显示剩余3条评论

19

这是user4317867的答案的一个变体。我在用户的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

13

如果您想要一个循环解决方案,保证既不会出现竞争又不会发生碰撞,那么这里就是:

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
}

根据迈克尔·克罗帕特的答案中的分析,大多数情况下,它只会通过一次循环。很少会通过两次。几乎不可能通过三次。


1
如果发生碰撞,这样做是行不通的,因为$name没有在循环内更新,会导致无限循环。 - Per von Zweigbergk
好的,我的错误。我刚刚把名称创建移到了循环中。 - John Freeman
1
此外,如果任何原因导致失败,它还会抛出一个不友好的错误。此外,它返回两个对象(因为New-Item返回一个对象)。你的一般方法是正确的,但我已经提交了一份编辑来修复我发现的问题。据我所知,它似乎工作正常。 :-) - Per von Zweigbergk

4
这是我的尝试:
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
}

这段代码面临着while循环结束和New-Item命令之间的竞态条件。 - ComFreek
@ComFreek,你能解释一下吗?你是在说在多线程环境中快速调用此方法时会发生什么吗?我不清楚在单线程中如何实现,比如PowerShell脚本。 - HackSlash

3

.NET已经有了[System.IO.Path] :: GetTempFileName()很长一段时间了;您可以使用它来生成一个文件(并捕获名称),然后在删除文件后创建具有相同名称的文件夹。

$tempfile = [System.IO.Path]::GetTempFileName();
remove-item $tempfile;
new-item -type directory -path $tempfile;

$tempfilename 变量在哪里被使用了? - sodawillow
我的错误,我没有复制/粘贴我的最终版本。现在已经修复了。 - alroc
这段代码面临与此答案相同的竞态条件。 - ComFreek

3

如果可能的话,我喜欢使用一行代码。@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  

这是我的最爱。它很干净,并且可以在Server 2012 R2时代的机器上运行。我可能会直接使用$temp = New-Item -Type Directory -Path $env:TEMP -Name ([System.Guid]::NewGuid())来设置$temp。 - IsAGuest

0

如果你想的话,你可以非常高级地调用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://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppatha

1
这是一个与 Path.GetTempPath 内部执行完全相同的代码。 - Martin Prikryl
@martin-prikryl 好的跟进,你知道我怎么查看那个内部代码吗? - AK_
https://referencesource.microsoft.com/#mscorlib/system/io/path.cs,3a7a8c72321c6e1d,references。虽然它将正确使用API的Unicode版本,与您的传统Ansi版本相反。 - Martin Prikryl

-1

扩展自Michael Kropat的回答:https://dev59.com/tlsW5IYBdhLWcg3w0Z4j#34559554

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

这应该消除任何碰撞问题。


2
这段代码面临着竞态条件,即在while循环结束和New-Item命令之间存在竞争,就像上面的其他答案一样。 - ComFreek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接