为什么不同的 Get-ChildItem 过滤方式会给出实际上不同的相同对象?

Why different ways of Get-ChildItem filtering give same objects that are actually different?

由于几个不相关的原因,我正在使用几种方法来处理在使用 Get-ChildItem 命令时过滤管道输出。

我创建了一个简短的代码来举例说明我的意思。当我使用不同的方式获取相同的项目时,该项目根据找到的方式得到不同的 "stringified",即使每次都是相同的项目。

假设我们有这个文件夹 C:\Folder1\Folder1-a,里面有 File1.7zFile2.txtFolder1里面还有更多的File1-X个文件夹,每个里面有一个.7z和一个.txt文件,它们的名字可以有一些特殊的字符,比如方括号。这与这个问题无关,但这就是为什么我更愿意使用某种特定的过滤方式而不是另一种过滤方式的原因(在所附代码的注释中解释)。

下面的代码可以证明我的观点:

#Initialize 7zip
if (-not (test-path "$env:ProgramFiles-Zipz.exe")) {throw "$env:ProgramFiles-Zipz.exe needed"}
set-alias 7zip "$env:ProgramFiles-Zipz.exe"

#this is a placeholder, $TXTFile would be the result of an iteration over a previous Item array
$TXTFile = Get-Item -Path "C:\Folder1\Folder1-a\File2.txt"

#Now there comes 3 different ways to select the 7z file in the same directory as File2.txt

# I use this to modify the path name so square brackets are properly escaped
$directory1 =$TXTFile.DirectoryName -replace "\[","`````[" -replace "\]","`````]"
[array]ZFile1 = Get-ChildItem -File -Path "$directory1\*.7z"

# This option uses LiteralPath so no modification is needed.
# More convenient since it supports any kind of special character in the name.
$directory2=$TXTFile.DirectoryName
[array]ZFile2= Get-ChildItem -File -LiteralPath $directory2 -Filter *.7z

# This option uses LiteralPath so no modification is needed.
# More convenient since it supports any kind of special character in the name.
$directory3=$TXTFile.DirectoryName
[array]ZFile3 = Get-ChildItem -File -LiteralPath $directory3 | Where-Object {$_.Extension -eq ".7z"}

#Lets see each item. They all seem equal
ZFile1
ZFile2
ZFile3
Write-Host "`n"

#Lets see how they have he same FullName
Write-Host ZFile1.FullName
Write-Host ZFile2.FullName
Write-Host ZFile3.FullName
Write-Host "`n"

#Lets compare them using -eq. Damn, they are not equal
if(ZFile1 -eq ZFile2){"7ZFile1=7ZFile2"}Else{"7ZFile1!=7ZFile2"}
if(ZFile2 -eq ZFile3){"7ZFile2=7ZFile3"}Else{"7ZFile2!=7ZFile3"}
if(ZFile3 -eq ZFile1){"7ZFile3=7ZFile1"}Else{"7ZFile3!=7ZFile1"}
Write-Host "`n"

#This is relevant if we "stringify" each object. First one returns FullName, the two others return Name
Write-Host ZFile1
Write-Host ZFile2
Write-Host ZFile3
Write-Host "`n"

#Example of this being relevant. Inside File1.7z is a txt file. If you use 7zip por example like this:
7zip t ZFile1 *.txt -scrc     #Success
7zip t ZFile2 *.txt -scrc     #Fail, can't find 7ZFile2
7zip t ZFile3 *.txt -scrc     #Fail, can't find 7ZFile3

我正在使用 ZFile.FullName 始终如一地获取我想要的字符串,但是我想知道为什么会这样?为什么首先会有差异?

这是 PS 5 中的一个常见烦恼,其中 what get-childitem returns 的字符串版本没有完整路径。它在 PS 的更高版本中已更改。 Get full path of the files in PowerShell

get-childitem .   | foreach tostring  # not full path
get-childitem .\* | foreach tostring  # full path

这里有两个不相关的问题:

  • WindowsPowerShell中——但幸运的是不再在PowerShellCore中 - System.IO.DirectoryInfoSystem.IO.FileInfo 实例 Get-ChildItem 输出 情境 字符串化 不同 - 纯文件名与完整路径 - 取决于 Get-ChildItem 调用的具体情况。

    • 详情见
    • 根本原因是所涉及的 .NET 类型不一致,这已在 .NET Core 中得到纠正(在其上构建了 PowerShell Core),如 this comment on GitHub.
    • 为了安全起见,在将实例作为 参数 传递给其他命令时,始终使用 .FullName 属性 或当意图是 按全名 .

    • 进行字符串化时
    • 问题的简单演示:

# Full-name stringification, due to targeting a *file* (pattern).
PS> (Get-ChildItem $PSHOME\powershell.exe).ToString()
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

# Name-only stringification, due to targeting a *directory* by *literal* name 
# (even though a filter to get a file is applied) and 
# not also using -Include / -Exclude
PS> (Get-ChildItem $PSHOME -Filter powershell.exe).ToString()
powershell.exe
  • System.IO.DirectoryInfoSystem.IO.FileInfo引用类型,这意味着它们的实例是使用引用相等性:也就是说,两个包含实例的变量只有当它们指向内存中完全相同的对象时才会比较相同.

    • 因此,ZFile1 -eq ZFile2never $true 如果两个实例是通过 different[=73] 获得的=] Get-ChildItem 来电; 最好的方法是通过 .FullName 属性.

    • 来比较实例
    • 有关引用相等与值相等的详细信息,请参阅 this answer

    • 引用相等行为的简单演示:

PS> (Get-ChildItem $PSHOME\powershell.exe) -eq (Get-ChildItem $PSHOME\powershell.exe)
False  # Distinct FileInfo objects, which aren't reference-equal.