sort -u ではなく sort | uniq を使おう
—
これは、TSG Advent Calendar 2025 の 9 日目の記事です。
Linux でデータの重複排除をする際、タイプ数が少ないので sort -u を使っていたが、特定の文字(ローマ数字や絵文字)が含まれる場合に集計結果がおかしいことに気づいた。
sort -u はロケールの影響を強く受けるため、厳密な重複排除には sort | uniq を使う方がよい。
Ⅰ, Ⅱ, Ⅲ が Ⅰ になってしまう
以下のようなローマ数字を含むリストを sort -u で処理すると、なぜか「Ⅰ」しか残らない。
$ sort -u <<EOF
Ⅰ
Ⅱ
Ⅲ
EOF
# ⅠGNU coreutils 8.32 環境で確認した。
原因は sort コマンドが参照している LC_COLLATE の設定にありそう。 挙動を確認したサーバーでは、以下のように設定されていた。
$ locale | grep COLLATE
LC_COLLATE="ja_JP.UTF-8"sort -u は「ソート順序が同じなら重複とみなす」という挙動をする。 現在の設定だと、ローマ数字の1から3をすべて同じものとして扱っている。結果、最初の1行だけを残して他を削除してしまう。これはローマ数字に限らず、丸数字(①)でも同様の現象が起こる。
$ sort -u <<EOF
①
②
③
EOF
# ①よって、sort -u はロケール設定によっては危険なので、他の方法を使うほうがよい。
方法1: uniq コマンドを使う
最も安全で確実な方法は、uniq コマンドを使うこと。
sort | uniqsort は順番を比較した結果で重複排除しているのに対して、uniq は「完全に同じ行かどうか」を比較する。
方法2: LC_COLLATE=C を指定する
sort を使いたい場合は、ロケールを C(バイト順)に強制することで回避できる。
LC_COLLATE=C sort -u <<EOF
Ⅰ
Ⅱ
Ⅲ
EOF
# Ⅰ
# Ⅱ
# Ⅲ今回の場合だと、英語でも正常に扱ってくれるので、以下でもよかった。
$ LC_COLLATE="ja_JP.UTF-8" sort -u <<EOF
①
②
③
EOF
# ①
# ②
# ③これなら文字コード順(バイト列の数値順)で比較されるため、正しく3行出力される。
積み残し
ということで、一見整合性のある説明はできた。 できたのだが、なんとなく他のサーバー(サーバーB)で以下のコマンドを打ったら、普通に3行出力されてしまった。
$ LC_COLLATE="ja_JP.UTF-8" sort -u <<EOF
①
②
③
EOF
# ①
# ②
# ③日本語環境では変な挙動になる、という話だったのに、なぜか普通に動いてしまう。
サーバーの環境情報は以下の通りで、ほとんど同じだった。
サーバーA
$ cat /etc/os-release
VERSION="22.04.1 LTS (Jammy Jellyfish)"
$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.11) 2.35
$ sort --version
sort (GNU coreutils) 8.32
サーバーB
$ cat /etc/os-release
VERSION="22.04.4 LTS (Jammy Jellyfish)"
$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.8) 2.35
$ sort --version
sort (GNU coreutils) 8.32Ubuntu のマイナーバージョンの違いが影響しているのか、あるいは他の何らかの設定が影響しているのかは不明。 まあとりあえず sort | uniq を使うのが無難、という結論は変わらないし、LC_COLLATE=C を指定すれば挙動が変わったということも事実なので、今回はこれでよしとする。