Powershell:"remove-item $object" 与“$object | remove-item”

Powershell: "remove-item $object" vs "$object | remove-item"

PS C:\Users\robert.lee> new-item C:\temp\junk\delete-me
PS C:\Users\robert.lee> $d=get-childitem C:\temp\junk -file
PS C:\Users\robert.lee> remove-item $d

/* 这不会删除 C:\temp\junk\delete-me,但会删除 C:\Users\robert.lee\delete-me。 */

PS C:\Users\robert.lee> $d | remove-item

/* 这会删除 C:\temp\junk\delete-me。 */

为什么 remove-item $d 不知道在哪里可以找到文件 (C:\temp\junk) 而 $d | remove-item 知道?在这两种情况下,对象 $d 都有完整路径。

PS:remove-item $d 确实删除了另一个位置的文件,如在 macOS Monterey 上的 PS 版本 7.2.1 上测试的那样:

PS /Users/robert.lee/m/Windows> $d=get-childitem /tmp/Junk2 -file  
PS /Users/robert.lee/m/Windows> ls /tmp/Junk2 
delete-me
PS /Users/robert.lee/m/Windows> remove-item $d
PS /Users/robert.lee/m/Windows> ls /tmp/Junk2 
PS /Users/robert.lee/m/Windows> 

PowerShell 只能为您做这么多,它不能做的一件事就是一直猜测您的目标。您遇到的问题来自幕后进行的参数绑定。因此,与 Remove-Item $g 的绑定只发生在传递给它的 [FileInfo] 对象的 Name 属性 上,这会将对象强制转换为字符串,然后将其绑定到当前工作目录将其绑定到 Path.

$g | Remove-Item,来自 管道 的绑定使用不同的过程进行绑定,并采用 PSPath 实例成员并将其绑定到 LiteralPath,即 FullName 属性 在您的 [FileInfo] 对象中。

  • 仍然将对象强制转换为[string]的对象,只是使用了不同的属性.
  • ParameterBinding还好的理解。

长话短说,在这两种情况下都使用$g.FullName,因为它将输出一个[string]的对象,并且两个Path参数也通过管道接受整个值。

用一些背景信息补充:

从可用性的角度来看,$d | Remove-ItemRemove-Item $d应该工作相同;它们不存在的原因(从 PowerShell Core 7.2.3 开始编写):

    遗憾的是,基于
  • Argument 的参数绑定不如基于 pipeline 复杂绑定:

    • 值得注意的是,-LiteralPath 参数上的 ValueFromPipelineByPropertyName 属性,其别名是 -PSPath,仅适用于提供的输入对象 通过管道 ($d | Remove-Item) 而不是当作为 参数提供时 (Remove-Item $d).

    • 带管道输入,就是.PSPath属性那个PowerShell的provider cmdlets decorate their output objects with that robustly binds to -LiteralPath, because the .PSPath property value is a full, provider-qualified path - see 有详细的解释

    • 这种绑定不对称是 GitHub issue #6057 的主题。

  • 如果您将文件或directory-info对象作为参数传递并且您这样做了位置(即不在参数前面加上target-parameter名称):

    • 目标是基于wildcard-Path参数:

      • 这通常不是问题,但如果 intended-as-literal 路径恰好包含 [ 个字符(例如 c:\foo\file[1].txt),则确实会成为问题,因为[ 是 PowerShell 通配符语言中的元字符。

      • 使用明确针对 -LiteralPath 参数的 named 参数可避免此问题(注意 PowerShell (Core) 7+ 允许您缩短至 -lp):

        # Note: Still not robust in *Windows PowerShell* - see below.
        Remove-Item -LiteralPath $d
        
    • -Path-LiteralPath都是[string[]]类型的,即它们接受字符串数组,这在基于 argument 的参数绑定中意味着 any non-string 参数只是 stringified (松散地说话,就像在上面调用 .ToString())[1]:

      • Windows PowerShell 中 - 正如您所经历的 - 这可能会导致微妙但具有潜在破坏性的错误 - 因为 System.IO.FileInfo and System.IO.DirectoryInfo instances situationally stringify to only their .Name property, not to their .FullName property - see 了解详情。

      • PowerShell (Core) 7+ 中这不再是问题 - 字符串化现在始终使用 .FullName.

      • 但是,字符串化仍然会导致 PowerShell 提供程序返回的项目失败 other 而不是 file-system 一个,如注册表提供商:

        # !! The 2nd command fails, because $regItem stringifies to
        # !! "HKEY_CURRENT_USER\Console" (the registry-native path),
        # !! which Get-Item then misinterprets as a relative file-system path.
        $regItem = Get-Item HKCU:\Console
        Get-Item $regItem
        

结果:

对于具有 -Path / -LiteralPath 参数的 目标 cmdlets ,提供作为提供者项的参数,例如 Get-ChildItem and Get-Item 的输出:

  • 最好通过管道:

    # Robust, in both PowerShell editions.
    Get-ChildItem C:\temp\junk -file | Remove-Item
    
  • 如果您必须使用 参数,目标 -LiteralPath 显式,并且 使用 .PSPath 属性:

    # Robust, in both PowerShell editions.
    $d = Get-ChildItem C:\temp\junk -file
    Remove-Item -LiteralPath $d.PSPath
    

相比之下,如果您需要将提供程序项的路径传递给.NET API外部程序:

  • 因为 .PSPath 值在项目的本机路径前面包含一个 PowerShell 提供商前缀(例如,Microsoft.PowerShell.Core\FileSystem::),例如外部各方无法直接理解价值观。

  • 稳健的方法是 将提供程序项通过管道传输到 Convert-Path,以便 将其转换为本地路径隔离(再次管道导致 .PSPath 绑定到 -LiteralPath):

     $d | Convert-Path # -> 'C:\temp\junk\delete-me'
    
    • 这也适用于基于 PowerShell-only drives 的路径字符串,例如:

      Convert-Path -LiteralPath HKCU:\Console # -> 'HKEY_CURRENT_USER\Console'
      

[1] 对于大多数 .NET 类型,PowerShell 确实调用 .ToString(),但在支持的情况下请求 culture-invariant 格式,通过 IFormattable 接口;对于 select 类型,特别是数组和 [pscustomobject],它使用自定义字符串化代替调用 .ToString() - 详情请参阅 this answer