Comparing two folder structures with PowerShell

huangapple go评论53阅读模式
英文:

Comparing two folder structures with PowerShell

问题

# 用 PowerShell 比较两个文件夹结构。最后,应该能看到所有只存在于文件夹 A 或文件夹 B 中的文件/文件夹的完整路径的概述。

# 我还想实现一个开关,可以切换以排除文件扩展名。
# 比较应该仅在路径级别进行。所以,当一个文件具有相同的名称但不同的大小时,应将其视为“相等”。

# 到目前为止,代码如下:

# 提示输入要比较的两个文件夹的路径
$folder1 = Read-Host "输入文件夹 A 的路径"
$folder2 = Read-Host "输入文件夹 B 的路径"

# 提示是否忽略文件扩展名
$ignoreExtensions = Read-Host "忽略文件扩展名? (y/n)"
$ignoreExtensions = $ignoreExtensions.ToLower() -eq "y"

# 获取每个文件夹中的文件并将其名称和路径存储在数组中
$dir1Dirs = Get-ChildItem -Recurse -Name $folder1 | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace '\.[^.]*$'
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}
$dir2Dirs = Get-ChildItem -Recurse -Name $folder2 | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace '\.[^.]*$'
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}

# 比较两个文件名数组并显示不同的文件的路径
$diff = Compare-Object -ReferenceObject $dir1Dirs -DifferenceObject $dir2Dirs | Where-Object { $_.SideIndicator -eq "=>" }

if ($diff) {
    Write-Host "不同的文件:"
    $diff | Select-Object -ExpandProperty FullName
} else {
    Write-Host "未找到差异。"
}

但我遇到了错误:

PS C:\Windows\system32> D:\compare4.ps1
输入文件夹 A 的路径: D:\folder1
输入文件夹 B 的路径: D:\folder2
忽略文件扩展名? (y/n): y

不同的文件:
Select-Object : 无法找到属性“FullName”。
位于 D:\compare4.ps1:36 字符:13
+     $diff | Select-Object -ExpandProperty FullName
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (@{InputObject=; SideIndicator==>}:PSObject) [Select-Object], PSArgumen
   tException
    + FullyQualifiedErrorId : ExpandPropertyNotFound,Microsoft.PowerShell.Commands.SelectObjectCommand

Select-Object : 无法找到属性“FullName”。
位于 D:\compare4.ps1:36 字符:13
+     $diff | Select-Object -ExpandProperty FullName
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (@{InputObject=; SideIndicator==>}:PSObject) [Select-Object], PSArgumen
   tException
    + FullyQualifiedErrorId : ExpandPropertyNotFound,Microsoft.PowerShell.Commands.SelectObjectCommand

当将上面的比较行更改为以下行时,错误消失,但没有生成任何输出:

$diff = Compare-Object $dir1Dirs $dir2Dirs -Property Name -IncludeEqual -PassThru | Where-Object { $_.SideIndicator -eq "=>" }

我在这里漏掉了什么?

先谢谢。


<details>
<summary>英文:</summary>

I am trying to compare two folder structures with PowerShell. At the end, I should see an overview of all files/folders with the full path, which are just in folder A or folder B.

I also want to implement a switch which can be toggled to exclude the file extensions.
The comparison should just be done at the path level. So when a file has the same name but a different size, it should be seen as &quot;equal&quot;.


So far, it looks like this:

```lang-ps1
# Prompt for the paths of the two folders to compare
$folder1 = Read-Host &quot;Enter the path for Folder A&quot;
$folder2 = Read-Host &quot;Enter the path for Folder B&quot;

# Prompt for whether to ignore file extensions
$ignoreExtensions = Read-Host &quot;Ignore file extensions? (y/n)&quot;
$ignoreExtensions = $ignoreExtensions.ToLower() -eq &quot;y&quot;

# Get the files in each folder and store their names and paths in arrays
$dir1Dirs = Get-ChildItem -Recurse -Name $folder1 | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace &#39;\.[^.]*$&#39;
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}
$dir2Dirs = Get-ChildItem -Recurse -Name $folder2 | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace &#39;\.[^.]*$&#39;
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}

# Compare the two arrays of file names and display the paths to files that are different
$diff = Compare-Object -ReferenceObject $dir1Dirs -DifferenceObject $dir2Dirs | Where-Object { $_.SideIndicator -eq &quot;=&gt;&quot; }

if ($diff) {
    Write-Host &quot;Files that are different:&quot;
    $diff | Select-Object -ExpandProperty FullName
} else {
    Write-Host &quot;No differences found.&quot;
}

but I get an error:

PS C:\Windows\system32&gt; D:\compare4.ps1
Enter the path for Folder A: D:\folder1
Enter the path for Folder B: D:\folder2
Ignore file extensions? (y/n): y

Files that are different:
Select-Object : Property &quot;FullName&quot; cannot be found.
At D:\compare4.ps1:36 char:13
+     $diff | Select-Object -ExpandProperty FullName
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (@{InputObject=; SideIndicator==&gt;}:PSObject) [Select-Object], PSArgumen
   tException
    + FullyQualifiedErrorId : ExpandPropertyNotFound,Microsoft.PowerShell.Commands.SelectObjectCommand

Select-Object : Property &quot;FullName&quot; cannot be found.
At D:\compare4.ps1:36 char:13
+     $diff | Select-Object -ExpandProperty FullName
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (@{InputObject=; SideIndicator==&gt;}:PSObject) [Select-Object], PSArgumen
   tException
    + FullyQualifiedErrorId : ExpandPropertyNotFound,Microsoft.PowerShell.Commands.SelectObjectCommand

When change the compare line from above with this one, the error is gone, but no output generated:

$diff = Compare-Object $dir1Dirs $dir2Dirs -Property Name -IncludeEqual -PassThru | Where-Object { $_.SideIndicator -eq &quot;=&gt;&quot; }

What am I missing here?

Thanks in advance.

答案1

得分: 2

你正在尝试通过它们内部文件的_相对路径_来比较文件夹树。

虽然 Get-ChildItem-Name 参数会输出相对路径,但它是_唯一_输出的内容,即它输出的是_字符串_而不是通常的 System.IO.FileInfo(以及 System.IO.DirectoryInfo)实例,这意味着输出不会包含 .FullName 等属性。

如果不考虑可选忽略扩展名的要求,您将能够通过推断要添加的根路径来重建原始完整路径,具体取决于从 Compare-Object 输出中的 .SideIndicator 的值。

为了满足您的要求,您需要:

  • 向您构造的 [pscustomobject] 实例添加一个 .RelativePath 属性,并通过从每个文件的 .FullName 属性中删除_根_路径的子字符串来手动确定相对路径。

  • 使用 -Property RelativePath 按该属性比较自定义对象的数组,还要确保将自定义对象作为整体传递,必须添加 -PassThru

将它们全部整合在一起:

$folder1 = Read-Host "Enter the path for Folder A"
$folder2 = Read-Host "Enter the path for Folder B"

# 提示是否忽略文件扩展名
$ignoreExtensions = 'y' -eq (Read-Host 'Ignore file extensions? (y/n)')

# 获取每个文件夹中的文件,并将它们的相对路径和完整路径存储在数组中,可选地不包括扩展名。
$dir1Dirs, $dir2Dirs = $folder1, $folder2 | 
  ForEach-Object {
    $fullRootPath = Convert-Path -LiteralPath $_
    # 构建当前文件夹树的自定义对象数组,并将其作为单个对象*输出*,使用数组构造运算符的一元形式,","
    , @(
      Get-ChildItem -File -Recurse -LiteralPath $fullRootPath |
        ForEach-Object {
          $relativePath = $_.FullName.Substring($fullRootPath.Length + 1)
          if ($ignoreExtensions) { $relativePath = $relativePath -replace '\.[^.]*$' }
          [PSCustomObject] @{
            RelativePath = $relativePath
            FullName = $_.FullName
          }
        }
    )
  }

# 比较这两个数组。
# 请注意使用了 -Property RelativePath 和 -PassThru,
# 以及 Where-Object SideIndicator -eq '=>' 过滤器,就像您的问题中一样,只报告与 -DifferenceObject 集合不同的内容。
# 要报告与*任一*集合不同的内容,只需删除该过滤器。
$diff = 
  Compare-Object -Property RelativePath -PassThru $dir1Dirs $dir2Dirs | 
  Where-Object SideIndicator -eq '=>'

# 输出结果。
if ($diff) {
    Write-Host "Files that are different:"
    $diff | Select-Object -ExpandProperty FullName
} else {
    Write-Host "No differences found."
}

注意:

  • 使用 , @(...) 确保 Get-ChildItem 输出对象作为数组_整体输出_(单个对象)的方式在此答案中有解释。
英文:

<!-- language-all: sh -->

You're trying to compare folder trees by the relative paths of the files in them.

While Get-ChildItem's -Name parameter does output relative paths, it is all that it outputs, i.e. it emits strings rather than the usual System.IO.FileInfo (and System.IO.DirectoryInfo) instances, which means that the output won't have properties such as .FullName

If the requirement to optionally ignore extension weren't in the picture, you would be able to reconstruct the original full paths, by inferring what root path to prepend, depending on the value of .SideIndicator in the output from Compare-Object.

To satisfy your requirements, you'll have to:

  • Add a .RelativePath property to the [pscustomobject] instances you construct, and manually determine the relative path, by removing the root path's substring from the .FullName property of each file.

  • Compare the arrays of custom objects by that property, using -Property RelativePath. Additionally, in order to ensure that your custom objects are passed through as a whole, you must add -PassThru

To put it all together:

$folder1 = Read-Host &quot;Enter the path for Folder A&quot;
$folder2 = Read-Host &quot;Enter the path for Folder B&quot;

# Prompt for whether to ignore file extensions
$ignoreExtensions = &#39;y&#39; -eq (Read-Host &#39;Ignore file extensions? (y/n)&#39;)

# Get the files in each folder and store their relative and full paths
# in arrays, optionally without extensions.
$dir1Dirs, $dir2Dirs = $folder1, $folder2 | 
  ForEach-Object {
    $fullRootPath = Convert-Path -LiteralPath $_
    # Construct the array of custom objects for the folder tree at hand
    # and *output it as a single object*, using the unary form of the 
    # array construction operator, &quot;,&quot;  
    , @(
      Get-ChildItem -File -Recurse -LiteralPath $fullRootPath |
        ForEach-Object {
          $relativePath = $_.FullName.Substring($fullRootPath.Length + 1)
          if ($ignoreExtensions) { $relativePath = $relativePath -replace &#39;\.[^.]*$&#39; }
          [PSCustomObject] @{
            RelativePath = $relativePath
            FullName = $_.FullName
          }
        }
    )
  }

# Compare the two arrays.
# Note the use of -Property RelativePath and -PassThru
# as well as the Where-Object SideIndicator -eq &#39;=&gt;&#39; filter, which
# - as in your question - only reports differences
# from the -DifferenceObject collection.
# To report differences from *either* collection, simply remove the filter.
$diff = 
  Compare-Object -Property RelativePath -PassThru $dir1Dirs $dir2Dirs | 
  Where-Object SideIndicator -eq &#39;=&gt;&#39;

# Output the results.
if ($diff) {
    Write-Host &quot;Files that are different:&quot;
    $diff | Select-Object -ExpandProperty FullName
} else {
    Write-Host &quot;No differences found.&quot;
}

Note:

  • The use of , @(...) to ensure that the Get-ChildItem output objects are output as an array as a whole (a single object) is explained in this answer.

答案2

得分: 1

你的代码中有两个问题。

  1. 在使用 Compare-Object 时,你还需要提供 -Property 参数来指示要检查的属性值。
  2. 在使用 Get-ChildItem 时,你使用了 -Name 属性,这是不起作用的。

以下是更新后的代码:

# 提示输入要比较的两个文件夹的路径
$folder1 = Read-Host "输入文件夹 A 的路径"
$folder2 = Read-Host "输入文件夹 B 的路径"

# 提示是否忽略文件扩展名
$ignoreExtensions = Read-Host "忽略文件扩展名?(y/n)"
$ignoreExtensions = $ignoreExtensions.ToLower() -eq "y"

# 获取每个文件夹中的文件,并将它们的名称和路径存储在数组中
$dir1Dirs = Get-ChildItem $folder1 -Recurse | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace '\.[^.]*$'
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}
$dir2Dirs = Get-ChildItem $folder2 -Recurse | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace '\.[^.]*$'
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}

# 比较两个文件名数组,并显示不同的文件的路径
$diff = Compare-Object -ReferenceObject $dir1Dirs -DifferenceObject $dir2Dirs -Property FullName | Where-Object { $_.SideIndicator -eq "=>" }

if ($diff) {
    Write-Host "不同的文件:"
    $diff | Select-Object -ExpandProperty FullName
} else {
    Write-Host "未发现差异。"
}

希望这有所帮助。

英文:

You were missing two things in your code.

  1. When using Compare-Object you also need to provide the -Property to indicate which property value to check.
  2. When using Get-ChildItem you was using -Name property, which is not going to work.

Here is the updated code:

# Prompt for the paths of the two folders to compare
$folder1 = Read-Host &quot;Enter the path for Folder A&quot;
$folder2 = Read-Host &quot;Enter the path for Folder B&quot;

# Prompt for whether to ignore file extensions
$ignoreExtensions = Read-Host &quot;Ignore file extensions? (y/n)&quot;
$ignoreExtensions = $ignoreExtensions.ToLower() -eq &quot;y&quot;

# Get the files in each folder and store their names and paths in arrays
$dir1Dirs = Get-ChildItem $folder1 -Recurse | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace &#39;\.[^.]*$&#39;
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}
$dir2Dirs = Get-ChildItem $folder2 -Recurse | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace &#39;\.[^.]*$&#39;
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}

# Compare the two arrays of file names and display the paths to files that are different
$diff = Compare-Object -ReferenceObject $dir1Dirs -DifferenceObject $dir2Dirs -Property FullName | Where-Object { $_.SideIndicator -eq &quot;=&gt;&quot; }

if ($diff) {
    Write-Host &quot;Files that are different:&quot;
    $diff | Select-Object -ExpandProperty FullName
} else {
    Write-Host &quot;No differences found.&quot;
}

huangapple
  • 本文由 发表于 2023年5月24日 19:18:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76322976.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定