【PowerShell】テキストファイルの重複行を削除する方法|順序保持・Get-Uniqueの罠

【PowerShell】テキストファイルの重複行を削除する方法|順序保持・Get-Uniqueの罠 PowerShell

ログやリスト、書き出したデータの中から重複した行を取り除いて、ユニークな行だけにしたい——PowerShellなら数行で実現できます。ただし、重複行を消す方法はいくつかあり、それぞれ「並び順が変わるかどうか」「大文字小文字を区別するかどうか」が違います。ここを知らずに使うと、思った結果になりません。

とくに注意したいのがGet-Uniqueです。名前から「重複を消すコマンド」に見えますが、Get-Uniqueは隣り合った重複しか消さないため、事前にソートしておかないと重複が残ってしまいます。この記事では、実機のPowerShell 5.1で確認しながら、重複行の削除方法を整理します。

先に結論

  • 並び順を保ちたいならSelect-Object -Unique(出現順のまま重複を削除)。
  • 並べ替えてよいならSort-Object -Unique(ソートしつつ重複を削除)。
  • Get-Uniqueは隣接した重複しか消しません。使うなら必ず先にSort-Objectします。
  • Select-Object -Uniqueは大文字小文字を区別Sort-Object -Uniqueは区別しません
  • ファイルに書き戻すときはSet-Content -Encodingでエンコードを指定します。

重複した「ファイル」を中身で探すなら重複ファイルの検出(Get-FileHash)、絞り込みや並べ替えの基本はWhere-Object・Select-Object・Sort-Object、ファイルの読み書きはファイル・フォルダ操作もあわせて参考になります。

スポンサーリンク

3つの方法と使い分け

重複行を消す主な方法は3つあります。実機でbanana, apple, banana, cherry, apple, bananaという6行に対して試した結果は次のとおりです。

  • Select-Object -Uniquebanana, apple, cherry出現順を保って重複削除)
  • Sort-Object -Uniqueapple, banana, cherryソートして重複削除)
  • Get-Unique(ソートなし) → 6行のまま(隣接重複が無いので何も消えない

並び順を維持したいか、ソートしてよいか、で選ぶのが基本です。順番に見ていきます。

順序を保って重複行を削除する Select-Object -Unique

もとの並び順を保ったまま重複を消したいならSelect-Object -Uniqueです。最初に出てきた行を残し、2回目以降の同じ行を削除します。ログのように順番に意味があるデータに向いています。

順序を保つ重複削除
# ファイルを読み込み、出現順のまま重複行を削除
Get-Content "C:\data\list.txt" | Select-Object -Unique

# 例: banana, apple, banana, cherry, apple, banana
#  → banana, apple, cherry(最初の出現順を保持)

実機でも、Select-Object -Uniquebanana, apple, cherryと、最初に登場した順番を保ったまま重複を削除しました。並び順が意味を持つデータ(時系列のログ、入力順のリストなど)では、この方法が第一候補になります。

ソートしてよいなら Sort-Object -Unique

並び順が変わってもよいなら、Sort-Object -Uniqueが簡単です。ソートと重複削除を一度に行います。アルファベット順・五十音順などに整列したい場合に便利です。

ソートしつつ重複削除
# ソートしながら重複行を削除
Get-Content "C:\data\list.txt" | Sort-Object -Unique

# 例: banana, apple, banana, cherry, apple, banana
#  → apple, banana, cherry(ソートされる)

実機でも、Sort-Object -Uniqueapple, banana, cherryと、並べ替えたうえで重複を削除しました。最終的にソート済みのユニークなリストがほしいなら、これが一番手軽です。逆に、元の順番を保ちたいときには使えません(並び替わってしまうため)。

【罠】Get-Uniqueは隣接した重複しか消さない

名前が紛らわしいのがGet-Uniqueです。「ユニークを取得」という名前から重複削除に使えそうですが、Get-Uniqueは隣り合った(連続した)重複しか消しません。離れた位置にある重複は残ってしまいます。

Get-Unique の罠と正しい使い方
# NG: ソートせずに Get-Unique → 隣接重複が無いので何も消えない
Get-Content "C:\data\list.txt" | Get-Unique
#  banana, apple, banana, ... → そのまま残る(重複が消えない!)

# OK: 先に Sort-Object してから Get-Unique
Get-Content "C:\data\list.txt" | Sort-Object | Get-Unique
#  → apple, banana, cherry(ソートで重複が隣接し、消える)
Get-Uniqueは単独では重複を消せない

実機で確認したところ、banana, apple, banana, cherry, apple, bananaそのままGet-Uniqueに渡しても、6行が一切減りませんでしたGet-Uniqueは「直前の行と同じなら消す」という動きのため、同じ行が隣り合っていないと重複と認識しないのです。一方、Sort-Object | Get-Uniqueのように先にソートして同じ行を隣接させてから渡すと、正しくapple, banana, cherryになりました。Get-Uniqueを使うときは必ず事前にソートが必要です。並び順を保ちたいなら、ソートを伴わないSelect-Object -Uniqueを使うのが簡単です。

大文字小文字の扱い(SelectとSortで違う)

意外と見落としがちなのが大文字小文字の扱いです。Select-Object -UniqueSort-Object -Uniqueで動きが違います。

大文字小文字の違い
$lines = "Apple", "apple", "APPLE"

# Select-Object -Unique は大文字小文字を区別する → 3つとも残る
$lines | Select-Object -Unique
#  → Apple, apple, APPLE

# Sort-Object -Unique は大文字小文字を区別しない → 1つにまとまる
$lines | Sort-Object -Unique
#  → APPLE(など1つだけ)
同じに見える行が消えない/消えるのはこのため

実機で確認したところ、Apple, apple, APPLEに対して、Select-Object -Uniqueは3つとも残し(大文字小文字を区別)Sort-Object -Uniqueは1つにまとめました(大文字小文字を区別しない)。「重複を消したつもりが、大文字小文字違いが残ってしまった」というときはSelect-Object -Uniqueを、「大文字小文字を無視してまとめたい」ときはSort-Object -Uniqueを使い分けてください。なお、順序を保ちつつ大文字小文字を区別したいという場合はSelect-Object -Uniqueがそのまま使えます。逆に、順序を保ちつつ大文字小文字を無視したい、といった細かい制御が必要なときは、System.Collections.Generic.HashSetなどを使う方法もあります。

ファイルに書き戻す(エンコード注意)

重複を消した結果をファイルに保存するにはSet-Contentを使います。日本語を含む場合は-Encodingの指定を忘れないようにします。

結果をファイルに保存
# 重複行を削除して別ファイルに保存(順序保持)
Get-Content "C:\data\in.txt" |
    Select-Object -Unique |
    Set-Content "C:\data\out.txt" -Encoding UTF8

# 同じファイルに上書きしたい場合は、いったん変数に受けてから書く
#(パイプの途中で同じファイルを読み書きすると壊れることがあるため)
$lines = Get-Content "C:\data\list.txt" | Select-Object -Unique
$lines | Set-Content "C:\data\list.txt" -Encoding UTF8
同じファイルへの上書きは変数に受けてから

実機では、5行(重複あり)のファイルをGet-Content | Select-Object -Unique | Set-Contentで処理し、3行のユニークなファイルに保存できることを確認しました。ただし注意点として、読み込みと書き込みを同じファイルに対してパイプで直接つなぐと、内容が壊れることがあります。Get-Content "list.txt" | ... | Set-Content "list.txt"のように同一ファイルを直接つなぐのは避け、いったん$lines = Get-Content ... | Select-Object -Unique変数に受け取ってから書き込むと安全です。また、日本語を含むファイルでは-Encoding UTF8などを指定して、文字化けを防いでください。

主な方法まとめ

重複行を消す方法の違いをまとめます。

方法 並び順 大文字小文字
Select-Object -Unique 出現順を保つ 区別する
Sort-Object -Unique ソートされる 区別しない
Sort-Object | Get-Unique ソートされる 区別しない
Get-Unique(単独) 隣接重複のみ(非推奨)

よくある失敗

Get-Uniqueを単独で使う

隣接した重複しか消えません。必ずSort-Object | Get-Uniqueとするか、Select-Object -Uniqueを使います。

順序を保ちたいのにSort-Objectを使う

ソートで並び順が変わります。順序維持ならSelect-Object -Uniqueです。

大文字小文字違いが残る/消える

区別したいならSelect-Object -Unique、無視したいならSort-Object -Uniqueを選びます。

同じファイルにパイプで直接上書きする

内容が壊れることがあります。いったん変数に受けてから書き込みます。

書き戻しでエンコードを指定しない

日本語が文字化けします。Set-Content -Encoding UTF8などを指定します。

よくある質問

Q並び順を変えずに重複行だけ消すには?
AGet-Content ファイル | Select-Object -Uniqueを使います。最初に出てきた行を残し、2回目以降の同じ行を削除するため、元の出現順が保たれます。ログのように順番に意味があるデータに向いています。
QGet-Uniqueを使ったのに重複が消えません。
AGet-Uniqueは隣り合った重複しか消さないためです。離れた位置の重複を消すには、先にSort-Objectで同じ行を隣接させる必要があります(Sort-Object | Get-Unique)。並び順を保ちたいなら、ソート不要のSelect-Object -Uniqueが簡単です。
Q大文字小文字の違う行はどう扱われますか?
ASelect-Object -Uniqueは大文字小文字を区別するため、Appleappleは別の行として両方残ります。Sort-Object -Uniqueは区別しないため、1つにまとまります。目的に応じて使い分けてください。
Q結果を同じファイルに上書きしたいです。
A読み込みと書き込みを同じファイルにパイプで直接つなぐと内容が壊れることがあります。$lines = Get-Content "list.txt" | Select-Object -Uniqueのように、いったん変数に受け取ってから$lines | Set-Content "list.txt" -Encoding UTF8で書き込むと安全です。
Q重複した「ファイル」を探したいのですが?
A行ではなくファイル単位の重複を探したい場合は、中身のハッシュ値を使います。Get-FileHashで各ファイルのハッシュを求め、同じハッシュのファイルをまとめる方法を別記事で解説しています。名前が違っても中身が同じファイルを検出できます。

まとめ

  • 順序を保つならSelect-Object -Unique(大文字小文字を区別)。
  • ソートしてよいならSort-Object -Unique(大文字小文字を区別しない)。
  • Get-Uniqueは隣接重複のみ。使うならSort-Object | Get-Uniqueとします。
  • 同じファイルへの上書きは、いったん変数に受けてから行います。
  • 書き戻しはSet-Content -Encoding UTF8で文字化けを防ぎます。

重複行の削除は、「並び順を保つか」「大文字小文字を区別するか」で方法を選ぶのがコツです。とくにGet-Uniqueの「隣接重複しか消さない」という挙動は引っかかりやすいので、迷ったら順序を保てるSelect-Object -Uniqueを使うのがおすすめです。