【问题标题】:Why different ways of Get-ChildItem filtering give same objects that are actually different?为什么不同的 Get-ChildItem 过滤方式会给出实际上不同的相同对象?
【发布时间】:2019-10-12 14:22:16
【问题描述】:

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

我创建了一个简短的代码来举例说明我的意思。当我使用不同的方式来获取同一个项目时,即使每次都是同一个项目,项目的“字符串化”也会根据它被发现的方式而不同。

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

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

#Initialize 7zip
if (-not (test-path "$env:ProgramFiles\7-Zip\7z.exe")) {throw "$env:ProgramFiles\7-Zip\7z.exe needed"}
set-alias 7zip "$env:ProgramFiles\7-Zip\7z.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]$7ZFile1 = 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]$7ZFile2= 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]$7ZFile3 = Get-ChildItem -File -LiteralPath $directory3 | Where-Object {$_.Extension -eq ".7z"}

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

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

#Lets compare them using -eq. Damn, they are not equal
if($7ZFile1 -eq $7ZFile2){"7ZFile1=7ZFile2"}Else{"7ZFile1!=7ZFile2"}
if($7ZFile2 -eq $7ZFile3){"7ZFile2=7ZFile3"}Else{"7ZFile2!=7ZFile3"}
if($7ZFile3 -eq $7ZFile1){"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 $7ZFile1
Write-Host $7ZFile2
Write-Host $7ZFile3
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 $7ZFile1 *.txt -scrc     #Success
7zip t $7ZFile2 *.txt -scrc     #Fail, can't find 7ZFile2
7zip t $7ZFile3 *.txt -scrc     #Fail, can't find 7ZFile3

我使用$7ZFile.FullName 始终如一地获得我想要的字符串,但是我想知道为什么会发生这种情况?首先为什么会有差异?

【问题讨论】:

  • 为什么会发生what?我们看不到您的屏幕
  • 你说得对,我添加了一些说明,但是我已经编写了如何设置所有内容并附上了一个注释脚本,详细解释了整个事情。您不需要查看我的屏幕,您只需要执行相同的脚本并查看您自己的屏幕。在任何情况下,问题是过滤 Get-ChildItem 管道的不同方式会导致完全相同的项目,由于某种原因表现不同。它们看起来一样,指向同一个文件,但是当“字符串化”时,它们会返回不同的东西。
  • 与问题没有直接关系,但您可能想使用where.exe 来检查7z,例如:if (!(where.exe 7z 2>$null)) {<# 7-zip does not exist #>;exit}
  • 使用@AP 有什么好处?
  • 这允许您验证7z 是否在路径中的某个位置并且可以执行,但不限于您硬编码的路径。 (支持:自定义安装、非标准windows驱动等)

标签: powershell filtering get-childitem


【解决方案1】:

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

  • Windows PowerShell - 但幸运的是不再在 PowerShell Core - System.IO.DirectoryInfoSystem.IO.FileInfo 实例 @ 987654326@ 输出 situationally 字符串化 不同 - 仅仅是文件名与完整路径 - 取决于 Get-ChildItem 调用的具体情况。

    • 详情请见this answer
    • 根本原因是所涉及的 .NET 类型不一致,如 this comment on GitHub 中所述,该问题已在 .NET Core(构建 PowerShell Core 的基础上)中得到纠正。
    • 为安全起见,将实例作为参数传递给其他命令或意图通过全名

    • 问题的简单演示:

# 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引用类型,这意味着它们的实例使用引用相等进行比较:即两个包含实例的变量只有在它们指向内存中完全相同的对象时才比较相同

    • 因此,$7ZFile1 -eq $7ZFile2从不 $true 如果两个实例是通过不同 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.

【讨论】:

  • 非常感谢!我不知道我是否应该提出一个新问题,但我已经用谷歌搜索了很多东西,但我一无所获。有没有办法比较两个对象?我的意思是,我知道Compare-Object,但它只检查相同的“名称”,可以这么说(对不起我的菜鸟),是否有一些内置函数迭代每个参数和方法并比较它们?我尝试比较“相同声明”对象的哈希值 (GetHashCode()),但它们返回的哈希值不同,所以我的想法已经不多了。
  • @rovda:没有保证方法可以比较任何类型的两个对象。对于值类型,标准的-eq 就足够了,但对于引用类型,就不行了。如果给定的引用类型恰好实现了IEquatable-eq 就足够了,但绝不是所有引用类型都可以。如果您碰巧知道一个类型的 特定属性 可以唯一标识它——例如手头的.FullName——你可以使用它。在所有其他情况下,您必须滚动自己的比较逻辑,例如通过递归比较所有属性的值(如果可行)。
  • 谢谢,就我而言,我确实有一些独特的东西。我正在编写的脚本遍历了我备份的整个 PS2 游戏库,它对所有这些游戏执行 CRC32,因此我有一种快速检查文件完整性的方法。然后我将其与基于第三方网站的手动创建的数据库 (csv) 进行比较。我想清理这个数据库,我只查找重复的哈希(这很容易),但如果我找到重复的哈希,我希望它完全比较每个,如果它们相等,删除一个,如果它们没有记录错误所以我可以手动处理它。所以是的,我必须迭代。
【解决方案2】:

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

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

【讨论】:

  • 我很欣赏这个回复,但这并没有回答我关于为什么会发生这种情况的问题,而是会发生什么,我已经知道了。至少它扩展了它开始发生的版本,所以还是谢谢你。
猜你喜欢
  • 1970-01-01
  • 2021-02-15
  • 2021-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-13
  • 1970-01-01
相关资源
最近更新 更多