【问题标题】:Create a temporary directory in PowerShell?在 PowerShell 中创建一个临时目录?
【发布时间】:2016-01-01 19:59:35
【问题描述】:

PowerShell 5 introduces the New-TemporaryFile cmdlet,很方便。我怎样才能做同样的事情,而不是一个文件创建一个目录?有New-TemporaryDirectory cmdlet 吗?

【问题讨论】:

    标签: powershell temporary-directory


    【解决方案1】:

    我认为可以通过对目录名称使用 GUID 来完成,而无需循环:

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

    使用 GetRandomFileName 的原始尝试

    这是我this C# solution的端口:

    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
    • 调用birthday paradox,我们预计一旦文件夹中有大约 2^28 个项目,即大约 3.6 亿项,就会发生冲突
    • NTFS supports about 2^32 items in a folder,因此使用GetRandomFileName 可能会发生碰撞

    NewGuidcan be one of 2^122 possibilities,几乎不可能发生碰撞。

    【讨论】:

    • 我可能会将GetRandomFileName()New-Item 放在一个循环中,以便在名称冲突的情况下自动重试。
    • @sodawillow 好问题。我误以为你需要-Force 来创建不存在的父目录,但我错了,所以我更新了答案。
    • 引用 MSDN :-Force 允许 cmdlet 创建一个覆盖现有只读项的项。实施因供应商而异。有关详细信息,请参阅 about_Providers。即使使用 Force 参数,cmdlet 也无法覆盖安全限制。
    • 如果您的临时文件夹中有 3.6 亿个项目,我认为您的问题比某些 PS 脚本因名称冲突而失败更大。
    【解决方案2】:

    我也喜欢单线,我请求在这里投反对票。我所要求的只是你把我自己对此模糊的负面感受用语言表达出来。

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

    根据您的纯粹主义者类型,您可以使用%{ mkdir $_-d },留下占位符以避免冲突。

    而且支持Join-Path $env:TEMP $(New-Guid) | %{ mkdir $_ } 也是合理的。

    【讨论】:

    • 对我来说似乎是一个很好的单线。除非另有证明,否则我会假设出现竞争条件的可能性很小,但这对于 99% 的用例来说并不是真正的问题。
    • 当心,我已经看到“删除和重新创建”策略在防病毒软件速度较慢的系统上失败,正如 Raymond Chen 在blogs.msdn.microsoft.com/oldnewthing/20120907-00/?p=6663 的“异步复制和删除案例”中所暗示的那样。
    • 这里是“本机 PowerShell”变体:New-TemporaryFile | % { Remove-Item $_; New-Item -ItemType Directory -Path $_ }
    • 添加 -d 并不能避免冲突。 (如果您同时在两个不同的脚本中使用相同的方法怎么办?)但是,您并没有试图避免冲突,是吗? ;-)
    • 澄清一下,如果在实践中存在风险,那么保留占位符可以避免冲突(直到重新实现命令行开关)。任意确定性-d 是为了避免生成的文件和目录之间的所谓“冲突”。
    【解决方案3】:

    这是 user4317867 的answer 的变体。我在用户的 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
    

    【讨论】:

      【解决方案4】:

      如果您希望循环解决方案能够保证无竞争和无碰撞,那么就是:

      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
      }
      

      根据Michael Kropat's answer 中的分析,绝大多数情况下,这只会通过循环一次。很少会通过两次。几乎永远不会超过三遍。

      【讨论】:

      • 这行不通,如果发生碰撞,它只会陷入无限循环......因为循环内没有更新$name。
      • 对,我的错。我只是将名称创建移到循环中。
      • 如果由于任何原因失败,它也会抛出一个难看的错误。它还返回两个对象(因为 New-Item 返回一个对象)。您的一般方法是合理的,但我对您的帖子进行了编辑以解决我发现的问题。据我所知,它似乎有效。 :-)
      【解决方案5】:

      这是我的尝试:

      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 命令之间的竞争条件。
      【解决方案6】:

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

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

      【讨论】:

      • $tempfilename 在哪里使用?
      • 我的错误,我没有复制/粘贴我的最终版本。现已修复。
      • 这段代码面临相同的竞争条件as this answer
      【解决方案7】:

      如果可能的话,我喜欢一个班轮。 @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  
      

      【讨论】:

        【解决方案8】:

        如果你愿意,你可以非常花哨,像这样调用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

        【讨论】:

        • 这是Path.GetTempPath 在内部为您执行的准确代码。
        • @martin-prikryl 很好的跟进,你知道我如何查看该内部代码吗?
        • referencesource.microsoft.com/#mscorlib/system/io/… + 虽然它会正确使用 API 的 Unicode 版本,但与您的旧 Ansi 版本相反。
        【解决方案9】:

        从 Michael Kropat 的回答扩展: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;
        }
        

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

        【讨论】:

        • 此代码在whileNew-Item 命令的结尾之间面临竞争条件,与上面的other answer 完全相同。
        猜你喜欢
        • 1970-01-01
        • 2013-09-18
        • 2017-03-06
        • 2010-09-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-01
        相关资源
        最近更新 更多