使用Powershell删除重复文件

11

我有几千个重复的文件(以jar文件为例),我想使用PowerShell来

  1. 递归地搜索文件系统
  2. 找到副本(仅按名称或使用校验和方法或两者同时)
  3. 删除所有重复文件,只保留一个。

我是powershell的新手,希望能得到PS专家的帮助。

5个回答

17

3
保留文件字典,当下一个文件名已经在之前出现过时删除:
$dict = @{};
dir c:\admin -Recurse | foreach {
  $key = $_.Name #replace this with your checksum function
  $find = $dict[$key];
  if($find -ne $null) {
    #current file is a duplicate
    #Remove-Item -Path $_.FullName ?    
  }
  $dict[$key] = 0; #dummy placeholder to save memory
}

我使用文件名作为键,但如果需要可以使用校验和(或两者都用)- 请参见代码注释。


一个数组和一个-contains检查就足够了,不需要字典。 - Ansgar Wiechers
1
@AnsgarWiechers:许多文件的性能问题?我的意思是每次都需要逐个迭代数组,并且每一步还需要重新创建数组,对吧? - Victor Zakharov
@notec:不确定为什么你想要那个,因为你的列表将是动态的。而且,不,你需要为每一个指定值——哈希表是键值对。请参阅此链接 - Victor Zakharov
@Neolisk - 我运行了你的脚本。我看到它找到了所有的重复项并将它们的值赋为零。现在我该如何删除那些没有被赋值为零的重复项呢?我看到你注释掉了一行 Remove-Item 命令,并询问了管道对象 FullName。感谢你帮我解决了这个问题。 - notec
@notec: 除非您指定了“-Confirm”参数,否则不应该出现确认对话框。请参阅此。您可以尝试使用“-Force”参数,看看是否有帮助。 - Victor Zakharov
显示剩余9条评论

3

即使这个问题很老,我仍然需要根据内容清理所有重复的文件。这个想法很简单,但是实现的算法并不直接。以下是一个代码示例,它接受一个参数“路径”,用于删除其中的重复文件。

 Function Delete-Duplicates {
    param(
    [Parameter(
    Mandatory=$True,
    ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True
    )]
    [string[]]$PathDuplicates)

    $DuplicatePaths = 
        Get-ChildItem $PathDuplicates | 
        Get-FileHash |
        Group-Object -Property Hash |
        Where-Object -Property Count -gt 1 |
        ForEach-Object {
            $_.Group.Path |
            Select -First ($_.Count -1)}
    $TotalCount = (Get-ChildItem $PathDuplicates).Count
 Write-Warning ("You are going to delete {0} files out of {1} total. Please confirm the prompt" -f $DuplicatePaths.Count, $TotalCount)    
 $DuplicatePaths | Remove-Item -Confirm

    }

该脚本:

a) 列出所有ChildItems

b) 从中检索FileHash

c) 按照Hash属性对它们进行分组(因此所有相同的文件都在单个组中)

d) 过滤已唯一的文件(组数 -eq 1)

e) 循环遍历每个组并列出除最后一个路径之外的所有路径-确保每个"Hash"文件总是留下一个

f) 在继续之前发出警告,说明总共有多少个文件以及将删除多少个文件。

这可能不是最高效的选项(对每个文件进行SHA1),但可以确保该文件是重复的。 对我而言,完美地运行 :)


0

@KaiWang的回答升级版,新版本实现:

  1. 通过首先比较文件长度来避免计算每个单独文件的哈希值;
  2. 允许选择你所需要的文件(这里保留最长名称的文件)。
Get-ChildItem *.ttf -Recurse |
  Group -Property Length |
  Where { $_.Count -gt 1 } |
  ForEach { $_.Group } |
  ForEach { $_ } |
  Get-FileHash -Algorithm 'MD5' |
  Group -Property Hash |
  Where { $_.Count -gt 1 } |
  ForEach {
    $_.Group |
      Sort -Property @{ Expression = { $_.Path.Length } } |
      Select -SkipLast 1
  } |
  ForEach { $_.Path } |
  ForEach {
    Write-Host $_
    Del -LiteralPath $_
  }

-1

不要仅仅删除重复的文件,你可以用快捷方式替换它们

#requires -version 3
<#
    .SYNOPSIS
    Script de nettoyage des doublons
    .DESCRIPTION
    Cherche les doublons par taille, compare leur CheckSum MD5 et les regroupes par Taille et MD5
    peut remplacer chacun des doubles par un lien vers le 1er fichier, l'original

    .PARAMETER Path
    Chemin ou rechercher les doublon

    .PARAMETER ReplaceByShortcut
    si specifier alors les doublons seront remplacé

    .PARAMETER MinLength
    ignore les fichiers inferieure a cette taille (en Octets)

    .EXAMPLE
    .\Clean-Duplicate '\\dfs.adds\donnees\commun'

    .EXAMPLE
    recherche les doublon de 10Ko et plus
    .\Clean-Duplicate '\\dfs.adds\donnees\commun' -MinLength 10000

    .EXAMPLE
    .\Clean-Duplicate '\\dpm1\d$\Coaxis\Logiciels' -ReplaceByShortcut
#>
[CmdletBinding()]
param (
    [string]$Path = '\\Contoso.adds\share$\path\data',
    [switch]$ReplaceByShortcut = $false,
    [int]$MinLength = 10*1024*1024 # 10 Mo
)

$version = '1.0'

function Create-ShortCut ($ShortcutPath, $shortCutName, $Target) {
    $link = "$ShortcutPath\$shortCutName.lnk"
    $WshShell = New-Object -ComObject WScript.Shell
    $Shortcut = $WshShell.CreateShortcut($link)
    $Shortcut.TargetPath = $Target
    #$Shortcut.Arguments ="shell32.dll,Control_RunDLL hotplug.dll"
    #$Shortcut.IconLocation = "hotplug.dll,0"
    $Shortcut.Description ="Copy Doublon"
    #$Shortcut.WorkingDirectory ="C:\Windows\System32"
    $Shortcut.Save()
    # write-host -fore Cyan $link -nonewline; write-host -fore Red ' >> ' -nonewline; write-host -fore Yellow $Target 
    return $link
}

function Replace-ByShortcut {
    Param(
        [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
            $SameItems
    )
    begin{
        $result = [pscustomobject][ordered]@{
            Replaced = @()
            Gain = 0
            Count = 0
        }
    }
    Process{
        $Original = $SameItems.group[0]
        foreach ($doublon in $SameItems.group) {
            if ($doublon -ne $Original) {
                $result.Replaced += [pscustomobject][ordered]@{
                    lnk = Create-Shortcut -ShortcutPath $doublon.DirectoryName -shortCutName $doublon.BaseName -Target $Original.FullName
                    target = $Original.FullName
                    size = $doublon.Length
                }
                $result.Gain += $doublon.Length
                $result.Count++
                Remove-item $doublon.FullName -force
            }
        }
    }
    End{
        $result
    }
}

function Get-MD5 {
    param (
        [Parameter(Mandatory)]
            [string]$Path
    )
    $HashAlgorithm = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
    $Stream = [System.IO.File]::OpenRead($Path)
    try {
        $HashByteArray = $HashAlgorithm.ComputeHash($Stream)
    } finally {
        $Stream.Dispose()
    }

    return [System.BitConverter]::ToString($HashByteArray).ToLowerInvariant() -replace '-',''
}

if (-not $Path) {
    if ((Get-Location).Provider.Name -ne 'FileSystem') {
        Write-Error 'Specify a file system path explicitly, or change the current location to a file system path.'
        return
    }
    $Path = (Get-Location).ProviderPath
}

$DuplicateFiles = Get-ChildItem -Path $Path -Recurse -File |
    Where-Object { $_.Length -gt $MinLength } |
        Group-Object -Property Length |
            Where-Object { $_.Count -gt 1 } |
                ForEach-Object {
                    $_.Group |
                        ForEach-Object {
                            $_ | Add-Member -MemberType NoteProperty -Name ContentHash -Value (Get-MD5 -Path $_.FullName)
                        }
                    $_.Group |
                        Group-Object -Property ContentHash |
                            Where-Object { $_.Count -gt 1 }
                }

$somme = ($DuplicateFiles.group | Measure-Object length -Sum).sum
write-host "$($DuplicateFiles.group.count) doublons, soit $($somme/1024/1024) Mo" -fore cyan

if ($ReplaceByShortcut) {
    $DuplicateFiles | Replace-ByShortcut
} else {
    $DuplicateFiles
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接