Linuxでコマンドの結果を効果的にソートする方法

sort は入力行を比較して並べ替え、既定では結果を標準出力へ書きます。期待順を再現するには「文字列・数値・指数・人間向け単位・版番号のどれか」「どの列をキーにするか」「同値行の順序を残すか」「locale を固定するか」をコマンドに明記します。見た目が数字でも既定は文字列比較なので、10 が 2 より前になるのは故障ではありません。 確認ポイント:sort結果を比較する前にlocale、key、numeric指定、stable条件を固定します。

目次

sortの比較方式をfixtureで切り替える

printf '2\n10\n1\n' | LC_ALL=C sort
printf '%s\n' '--- numeric ---'
printf '2\n10\n1\n' | LC_ALL=C sort -n
printf '%s\n' '--- general numeric ---'
printf '1e3\n20\n-4.5\n' | LC_ALL=C sort -g
printf '%s\n' '--- human numeric ---'
printf '2G\n900K\n1M\n' | LC_ALL=C sort -h

正確な順序は、既定が 1, 10, 2、-n が 1, 2, 10、-g が -4.5, 20, 1e3、-h が 900K, 1M, 2G です。-n は一般的な十進表記を比較し、指数表記や NaN、Infinity なども扱う必要がある GNU 環境では -g を選びます。-g は浮動小数点変換の影響を受け、同じ値に丸められる精密数の厳密順序には向きません。-h は K、M、G などの接尾辞を解釈する GNU の人間可読数値比較で、単位のない業務値に安易に使いません。

-r は最終比較結果を逆転します。sort -nr は数値の降順、sort -hr は容量の降順です。マイナス記号を列指定の前後へ分散させず、sort -t: -k3,3nr のようにキーへ比較方法を付けると、その列だけの意図が読みやすくなります。グローバルな -r は全キーと最後の行比較を反転します。

-t と -k で区切り・列・比較範囲を固定する

次のコロン区切りデータは、列1がID、列2が名前、列3が得点、列4が容量、列5が版文字列です。-t: で一文字の区切りを指定し、-k3,3n で第3フィールドだけを数値比較します。終点を省略した -k3 は第3フィールドから行末までがキーになるため、列だけを選ぶ用途では 3,3 と閉じます。

work=$(mktemp -d) || exit 1
trap 'rm -r -- "$work"' EXIT
cat >"$work/data.txt" <<'EOF'
1:beta:9:900K:app-2
2:alpha:10:1.2M:app-10
3:alpha:10:850K:app-1
4:gamma:-2:2G:app-2
EOF
LC_ALL=C sort -t: -k3,3n -k2,2 -k1,1n "$work/data.txt"

出力は 4:gamma:-2:2G:app-2、1:beta:9:900K:app-2、2:alpha:10:1.2M:app-10、3:alpha:10:850K:app-1 の順です。得点昇順、同点なら名前、さらに同じならID数値という比較を全て指定したため、入力順に依存しない結果になります。空白区切り入力では -t を省略した既定の field 分割が使えますが、固定幅の空白や空フィールドを持つデータでは列定義がずれるので、先に形式を確認します。

得点の降順だけが必要なら LC_ALL=C sort -t: -k3,3nr -k2,2 -k1,1n とします。この場合は 10 の alpha 2行、9 の beta、-2 の gamma です。最初の二行は名前も同じなので ID が2、3の順になります。sort -r -t: -k3,3n -k2,2 では副キーの名前まで逆順になるため、主キーだけ降順にしたい要件とは異なります。

-s で同値行の入力順を保つ

指定キーが全て同じ場合、GNU sort は既定で行全体を最後の比較に使います。この「last-resort comparison」を止め、同値キーの入力順を残すのが -s(stable)です。

printf '2:b\n1:b\n3:a\n' | LC_ALL=C sort -t: -k2,2
printf '%s\n' '--- stable ---'
printf '2:b\n1:b\n3:a\n' | LC_ALL=C sort -s -t: -k2,2

既定の結果は 3:a、1:b、2:b、stable の結果は 3:a、2:b、1:b です。b の二行が入力順のまま残ります。前段の時刻順や優先順位を保持したまま別キーでまとめるときに使います。逆に完全な決定順が必要なら、-s に頼らず一意な副キーを最後に指定します。

-u は重複行または重複キーを一つにする

sort -u は比較結果が等しい行を一つだけ出します。引数なしなら同一行の除去、-k を指定すればそのキーが同じ行の代表一件だけになります。代表行の他列が重要なら、どれが残るかに業務判断を任せてはいけません。先に優先順位で安定ソートし、その後に awk で最初のキーだけ残すなど、選択規則を明示します。

printf 'red\nblue\nred\nblue\n' | LC_ALL=C sort -u
printf '2:b\n1:b\n3:a\n' | LC_ALL=C sort -s -u -t: -k2,2

最初は blue、red の2行です。次はキー a の 3:a と、入力で先に出たキー b の 2:b です。-u は「最新を残す」「IDが小さい方を残す」といった意味を持たないので、その要件なら時刻やIDを前処理の優先キーにします。件数検証は before=$(wc -l <file) と after=$(sort -u file | wc -l) を比較し、減少数を記録します。

locale を固定して再現性を確保する

文字列の照合順序は LC_COLLATE、空白や文字クラスは LC_CTYPE など locale の影響を受けます。同じファイルでも ja_JP.UTF-8 と C で英字の大小や記号の位置が変わり得ます。機械的なID・ログ・プロトコルデータでは LC_ALL=C sort ... としてバイト順を固定します。人名や自然言語を利用者向けに並べるなら、期待 locale を明示し、実データの濁点、全角半角、結合文字を含む受入例で確認します。sort は Unicode 正規化を自動で同一化する道具ではありません。

版番号は -V、ただし移植性を確認する

printf 'app-2\napp-10\napp-1\napp-1.9\n' | LC_ALL=C sort
printf '%s\n' '--- version sort ---'
printf 'app-2\napp-10\napp-1\napp-1.9\n' | LC_ALL=C sort -V

文字列順は app-1、app-1.9、app-10、app-2、GNU version sort は app-1、app-1.9、app-2、app-10 です。-V は GNU coreutils の版順比較で、Semantic Versioning の優先規則を完全実装するという意味ではありません。POSIX sort の必須機能ではなく、OS の sort 実装によって未対応または規則差があり得るため、sort --version と小さな期待データで確認します。パッケージ版の厳密比較は、そのパッケージ管理システムの比較機能を使います。

ファイルを置換するときは sort -o を使う

sort data.txt >data.txt は実行しません。シェルが sort の読取り開始前に出力先を空にするため、入力を失います。GNU sort は -o OUTPUT で入力と同じパスを安全に指定でき、内部で出力を準備してから置換します。それでも内容変更なので、空き容量、権限、ハードリンク、監視プロセスへの影響を確認し、直前のコピーを同じファイルシステムに作ります。

cp -p -- "$work/data.txt" "$work/data.txt.before-sort" || exit 1
LC_ALL=C sort -t: -k3,3n -k1,1n -o "$work/data.txt" -- "$work/data.txt" || exit 1
LC_ALL=C sort -c -t: -k3,3n -k1,1n -- "$work/data.txt"
cmp -s -- "$work/data.txt" "$work/data.txt.before-sort"; printf 'changed=%d\n' "$?"

sort -c が無言で終了値0なら指定順です。元データは未整列なので cmp は終了値1、表示は changed=1 になります。期待件数は wc -l を前後で比較します。確認に失敗した場合の復元は mv -- "$work/data.txt.before-sort" "$work/data.txt" です。成功後だけバックアップを削除します。実運用ではバックアップ名の衝突を避け、symlink を入力にする場合はリンク対象を置換するのかリンク自体を扱うのか事前に決めます。

sort結果を再現する確認

  • 比較型を文字列、-n、-g、-h、-V から選び、サンプルの期待順を固定した。
  • -k start,end の終点を指定し、必要な副キーまで書いた。
  • 同値の入力順を残すときだけ -s、代表一件化が要件のときだけ -u を使った。
  • 機械データは LC_ALL=C、利用者向け言語順は対象 locale で受入確認した。
  • 原本置換は sort -o と事前コピーを使い、sort -c・件数・cmp で検証した。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次