【PowerShell】重複ファイルを検出する方法|Get-FileHashで中身が同じファイルを探す

【PowerShell】重複ファイルを検出する方法|Get-FileHashで中身が同じファイルを探す PowerShell

フォルダを整理していると、「中身は同じなのに名前が違うファイル」がいくつもできてしまうことがあります。ダウンロードの重複や、コピーの作りすぎが原因です。PowerShellを使えば、ファイル名ではなく中身(ハッシュ値)を基準に重複ファイルを検出でき、追加のソフトは要りません。

ポイントは、Get-FileHashで各ファイルのハッシュ値(中身から計算される指紋のような値)を求め、同じハッシュ値のファイルをGroup-Objectでグループ化することです。ハッシュ値が同じなら、名前が違っても中身は同一です。この記事では、実機のPowerShell 5.1で確認しながら、重複ファイルの検出と整理を整理します。

先に結論

  • 中身が同じかはハッシュ値で判定します。Get-FileHashで求めます。
  • 検出の基本はGet-ChildItem -Recurse -File | Get-FileHash | Group-Object Hash | Where-Object { $_.Count -gt 1 }です。
  • ファイル名が違っても、中身が同じなら検出できます。
  • 大量のファイルは、先にLength(サイズ)で絞ると高速になります。
  • 削除するときは各グループで1つ残し、必ず-WhatIfで確認してから実行します。
  • 既定のアルゴリズムはSHA256。速さ重視ならMD5も選べます。

絞り込みのWhere-ObjectGroup-ObjectWhere-Object・Select-Objectでデータを絞り込む、対象の取得はファイル・フォルダ操作、結果の処理はループ完全ガイドもあわせて参考になります。

スポンサーリンク

考え方:ハッシュで「中身が同じ」を判定する

重複検出のカギはハッシュ値です。ハッシュ値とは、ファイルの中身から計算される固定長の文字列で、中身が1ビットでも違えば全く別の値になり、中身が同じなら必ず同じ値になります。つまり、ファイル名や更新日時に関係なく、ハッシュ値を比べれば中身が同じかどうかを判定できます。

ファイルのハッシュを取得する Get-FileHash

1つのファイルのハッシュ値はGet-FileHashで求められます。結果のHashプロパティが、そのファイルの中身を表す値です。

Get-FileHash の基本
# ファイルのハッシュ値を求める(既定は SHA256)
Get-FileHash "C:\data\report.txt"

# ハッシュ値だけ取り出す
(Get-FileHash "C:\data\report.txt").Hash

# 出力例:
# Algorithm  Hash                                  Path
# ---------  ----                                  ----
# SHA256     A1B2C3...(64文字の16進数)           C:\data\report.txt

実機でも、Get-FileHashの既定アルゴリズムはSHA256で、.AlgorithmSHA256と表示されました。2つのファイルのHashが一致すれば中身は同一、違えば中身は異なる、と判断できます。あとはこれを全ファイルに適用し、同じハッシュ値のものをまとめれば、重複が見つかります。

重複を検出する基本パイプライン

フォルダ内の重複ファイルは、次のパイプライン1行で検出できます。各コマンドが「対象を集める→ハッシュを求める→ハッシュでグループ化→2件以上のグループだけ残す」という流れになっています。

重複検出の基本
# フォルダ内(サブフォルダ含む)の重複ファイルを検出
Get-ChildItem "C:\data" -Recurse -File |
    Get-FileHash |
    Group-Object Hash |
    Where-Object { $_.Count -gt 1 }

# 各ステップの意味:
# Get-ChildItem -Recurse -File … サブフォルダも含めてファイルを集める
# Get-FileHash               … それぞれのハッシュ値を求める
# Group-Object Hash          … 同じハッシュ値ごとにまとめる
# Where-Object Count -gt 1   … 2件以上(=重複)のグループだけ残す
名前が違っても中身で見つかる

実機で、a.txtcopy_of_a.txt・サブフォルダのdup.txtという名前もフォルダも違う3つの同一内容ファイルを用意して検出したところ、ハッシュ値が同じ1つのグループ(Countが3)として正しくまとまりました。Group-Object Hashはハッシュ値でグループ化するので、ファイル名はいっさい関係ありません。Where-Object { $_.Count -gt 1 }で「2件以上=重複」だけに絞り込んでいます。重複が無ければ結果は何も表示されません。

重複したファイルの一覧を見やすく表示する

検出結果をそのまま見ても分かりにくいので、各グループのファイルのパスを一覧で表示すると確認しやすくなります。Groupプロパティに、そのグループに含まれるファイルが入っています。

重複の一覧表示
$dups = Get-ChildItem "C:\data" -Recurse -File |
    Get-FileHash |
    Group-Object Hash |
    Where-Object { $_.Count -gt 1 }

# 各グループの重複ファイルを一覧表示
foreach ($group in $dups) {
    Write-Host "■ 重複($($group.Count) 件):"
    $group.Group | ForEach-Object { Write-Host "   $($_.Path)" }
}

$dupsに検出結果を入れておき、foreachで各グループを回して、$group.Groupに含まれるファイルのパスを表示しています。これで「どのファイルとどのファイルが同じ中身か」が一目で分かります。削除はこの一覧を確認してから行うのが安全です。

サイズで事前に絞り込んで高速化する

ファイル数が多いと、すべてのファイルのハッシュ計算に時間がかかります。中身が同じファイルは必ずサイズも同じなので、先にLength(サイズ)でグループ化し、同じサイズが2件以上あるものだけハッシュを計算すると、大幅に速くなります。

サイズで事前フィルタ
# まず同じサイズのファイルだけに絞ってから、ハッシュで確定する
Get-ChildItem "C:\data" -Recurse -File |
    Group-Object Length |
    Where-Object { $_.Count -gt 1 } |     # 同サイズが複数あるものだけ
    ForEach-Object { $_.Group } |          # そのファイル群を取り出す
    Get-FileHash |
    Group-Object Hash |
    Where-Object { $_.Count -gt 1 }
サイズ違いは中身も違う、を利用する

実機でも、まずGroup-Object Lengthで同じサイズのファイルをまとめ、複数あるものだけをハッシュ計算の対象にできました。サイズが違えば中身も必ず違うため、サイズが1つしかないファイルはハッシュ計算をスキップできます。ハッシュ計算はファイルを丸ごと読むため重い処理です。何千・何万ファイルもあるフォルダでは、この事前フィルタの有無で処理時間が大きく変わります。少数のファイルなら、最初の基本パイプラインのままで十分です。

重複を削除する(1つ残して消す)

重複を整理するときは、各グループで1つだけ残し、残りを削除します。Select-Object -Skip 1で「先頭の1つを除いた残り」を削除対象にできます。削除は取り返しがつかないため、必ず先に-WhatIfで確認してください。

重複の削除(まず -WhatIf で確認)
# 各グループで先頭1つを残し、残りを削除対象にする
$toDelete = Get-ChildItem "C:\data" -Recurse -File |
    Get-FileHash |
    Group-Object Hash |
    Where-Object { $_.Count -gt 1 } |
    ForEach-Object { $_.Group | Select-Object -Skip 1 }

# まず -WhatIf で「何が消えるか」だけ確認(実際には消さない)
$toDelete | ForEach-Object { Remove-Item $_.Path -WhatIf }

# 内容を確認して問題なければ、-WhatIf を外して実行
# $toDelete | ForEach-Object { Remove-Item $_.Path }
削除は必ず -WhatIf で確認してから

実機で確認したところ、Select-Object -Skip 1を使うと、3つの重複ファイル(1.txt2.txt3.txt)のうち先頭の1.txtを残し、2.txt3.txtの2つだけが削除対象になりました。-WhatIfを付けて実行すると「対象 … に対して操作 “ファイルの削除” を実行しています」と消す予定のファイルが表示されるだけで、実際には削除されません。表示された内容に問題がないことを必ず確認し、それから-WhatIfを外して本実行してください。大事なファイルを誤って消さないために、いきなり本実行しないことが重要です。削除前にバックアップを取っておくと、さらに安全です。

アルゴリズムの選択 SHA256とMD5

Get-FileHashの既定はSHA256ですが、-Algorithmで変更できます。重複検出の用途なら、より高速なMD5を使う選択肢もあります。

アルゴリズムの指定
# 既定(SHA256・安全性が高い・64文字)
Get-FileHash "C:\data\a.txt"

# MD5(高速・32文字)— 重複検出の用途なら十分
Get-FileHash "C:\data\a.txt" -Algorithm MD5

# 指定できる主な値: SHA256, SHA1, MD5 など

実機でも、-Algorithm MD5を指定すると32文字のハッシュ値(SHA256は64文字)が得られました。MD5はセキュリティ用途では非推奨ですが、「中身が同じかを調べる」だけの重複検出なら、高速なMD5でも実用上問題ありません。ファイル数が非常に多く速度を優先したいときは、MD5とサイズ事前フィルタを組み合わせると効率的です。確実性を重視するなら既定のSHA256のままで構いません。

主なコマンドまとめ

重複ファイル検出で使う要素をまとめます。

要素 働き
Get-ChildItem -Recurse -File サブフォルダ含めファイルを集める
Get-FileHash 中身からハッシュ値を求める(既定SHA256)
Group-Object Hash 同じハッシュ値ごとにまとめる
Where-Object { $_.Count -gt 1 } 2件以上(=重複)だけ残す
Group-Object Length サイズで事前に絞る(高速化)
Select-Object -Skip 1 1つ残して削除対象を作る
Remove-Item -WhatIf 削除内容を確認(実際には消さない)

よくある失敗

ファイル名で重複を判定しようとする

名前が違う同一ファイルは見つかりません。中身(ハッシュ値)で判定します。

大量ファイルをいきなり全部ハッシュ計算する

遅くなります。先にGroup-Object Lengthでサイズが同じものに絞ると高速です。

確認せずにいきなり削除する

必ず-WhatIfで消える対象を確認してから本実行します。バックアップも推奨です。

全グループを消してしまう

Select-Object -Skip 1で各グループ1つは残します。これを忘れると全部消えます。

-File を付けずフォルダまで対象にする

Get-ChildItem-Fileを付け、ファイルだけを対象にします。

よくある質問

Qファイル名が違う同じ中身のファイルも検出できますか?
Aできます。Get-FileHashはファイルの中身からハッシュ値を計算し、Group-Object Hashはそのハッシュ値でグループ化するため、ファイル名や置き場所に関係なく、中身が同じものを重複として検出できます。
Qファイルが多くて検出が遅いです。
A先にGroup-Object Lengthでサイズが同じファイルだけに絞り、複数あるものだけをハッシュ計算してください。中身が同じならサイズも必ず同じなので、サイズが単独のファイルはハッシュ計算を省けます。ハッシュは重い処理なので、この事前フィルタで大きく高速化できます。
Q重複を安全に削除するには?
A各グループでSelect-Object -Skip 1を使い、1つ残して残りを削除対象にします。そして必ずRemove-Item -WhatIfで「何が消えるか」を確認してから、-WhatIfを外して本実行してください。削除は取り消せないため、バックアップを取っておくとより安全です。
QSHA256とMD5はどちらを使うべき?
A重複検出の用途であれば、どちらでも中身の同一性を判定できます。確実性を重視するなら既定のSHA256、速度を優先したいならMD5が選べます。MD5はセキュリティ用途では非推奨ですが、ファイルが同じかを調べるだけなら実用上問題ありません。
Q重複が見つからないと結果が空になります。
A重複が無ければWhere-Object { $_.Count -gt 1 }で残るグループが無く、何も表示されないのが正常です。コマンド自体が失敗しているわけではありません。試しに同じファイルをコピーして実行すると、検出される様子を確認できます。

まとめ

  • 重複は中身(ハッシュ値)で判定します。名前は関係ありません。
  • 基本はGet-ChildItem -Recurse -File | Get-FileHash | Group-Object Hash | Where-Object { $_.Count -gt 1 }
  • 大量ファイルは先にGroup-Object Lengthでサイズ絞り込みで高速化します。
  • 削除は各グループでSelect-Object -Skip 1必ず-WhatIfで確認してから。
  • 既定はSHA256、速度重視ならMD5も使えます。

PowerShellなら、追加ソフトなしで重複ファイルを中身ベースで正確に検出できます。「ハッシュでグループ化する」という考え方さえつかめば、応用は簡単です。削除のときだけは-WhatIfでの確認を忘れず、安全にフォルダを整理しましょう。