sortコマンドで複数キーによるソート

sort コマンドの挙動がわかりにくかったのでメモです。

サンプルとして次のような 3 つのフィールドを持ったデータを使います。

sort_sample.tsv
5	aab	5
1	abb	5
10	aab	10
5	abb	1
1	baa	10
1	aba	5
10	bab	5
10	abb	10
1	bab	1
1	aab	5

sort コマンドは指定されたキーを基準に、現在の locale の辞書式順序でソートを行います。このキーはデフォルトで 1 行全体です。

$ sort sort_sample.tsv
1	aab	5
1	aba	5
1	abb	5
1	baa	10
1	bab	1
10	aab	10
10	abb	10
10	bab	5
5	aab	5
5	abb	1

上記の結果からも、第 1 フィールドに関して辞書式順序の昇順にソートされ、第 1 フィールドの同じものは第 2 フィールドに関して辞書式順序の昇順にソートされ、第 1 フィールドも第 2 フィールドも同じものは第 3 フィールドに関して辞書式順序の昇順にソートされたものとなっていることがわかります。

-k オプションを使ったソート

このソートをもっと柔軟に行うために用意されているのが -k オプションで、次のように指定します。

-k POS1[,POS2]

ここで、POS1 はキーの最初となる位置、POS2 はキーの最後となる位置です。POS2 を指定しないと POS1 以降全体がキーとなります。

POS1 や POS2 は次のように指定します。

フィールドの位置[.文字の位置][ソートのオプション]

フィールドの位置も文字の位置も 1-origin です。POS1 の文字の位置を省略すると実質的に 1 を指定するのと同じになり、POS2 の文字の位置を省略するとそのフィールドの最後の文字までが対象になるみたいです。

例えば、第 2 フィールドでソートして、第 2 フィールドが同じものは第 3 フィールドでソートしたければ次のように指定します。

$ sort -k 2 sort_sample.tsv
10	aab	10
1	aab	5
5	aab	5
1	aba	5
5	abb	1
10	abb	10
1	abb	5
1	baa	10
1	bab	1
10	bab	5

もし 第 2 フィールドでソートして、第 2 フィールドが同じものは第 3 フィールドで数値としてソートしたければ次のように指定します。

$ sort -k 2,2 -k 3n sort_sample.tsv
1	aab	5
5	aab	5
10	aab	10
1	aba	5
5	abb	1
1	abb	5
10	abb	10
1	baa	10
1	bab	1
10	bab	5

-k 2,2 のように範囲を絞るのがポイントです。これが -k 2 のままだと、次のように所望の結果は得られません。

$ sort -k 2 -k 3n sort_sample.tsv
10	aab	10
1	aab	5
5	aab	5
1	aba	5
5	abb	1
10	abb	10
1	abb	5
1	baa	10
1	bab	1
10	bab	5

これはおそらく、第 2 フィールドを辞書式順序でソートし、第 2 フィールドが同じものは第 3 フィールドを辞書式順序ででソートし、第 2 フィールドと第 3 フィールドが同じものは第 3 フィールドを数値順でソートする、という無意味な指定になっているんだと思います。

文字の位置についての注意点

前述のとおり、文字の位置は 1-origin なんですが、第 1 フィールド以外については注意が必要です。
というのも、区切り文字を -t オプションで指定するか、-b オプションを指定するかしない限り、whitespace が 1 文字目とみなされるからです。

例えば、次のコマンドでは第 2 フィールドの 3 文字目以降でソートするよう指定していますが、第 2 フィールドの 1 文字目はタブなので、印字文字だけを見ると第 2 フィールドの 2 文字目以降でソートされることになります。

$ sort -k 2.3 sort_sample.tsv
1       baa     10
1       bab     1
10      aab     10
1       aab     5
10      bab     5
5       aab     5
1       aba     5
5       abb     1
10      abb     10
1       abb     5

印字文字の 3 文字目以降でソートしたい場合、-k 2.4 と指定するか、-t オプションまたは -b オプションを指定する必要があります。

$ sort -b -k 2.4 sort_sample.tsv
1       baa     10
1       aba     5
1       bab     1
5       abb     1
10      aab     10
10      abb     10
1       aab     5
1       abb     5
10      bab     5
5       aab     5
$ sort -t $'\t' -k 2.3 sort_sample.tsv
1       baa     10
1       aba     5
1       bab     1
5       abb     1
10      aab     10
10      abb     10
1       aab     5
1       abb     5
10      bab     5
5       aab     5
$ sort -b -k 2.3 sort_sample.tsv
1       baa     10
1       aba     5
1       bab     1
5       abb     1
10      aab     10
10      abb     10
1       aab     5
1       abb     5
10      bab     5
5       aab     5

まとめ

そんなわけで、-k オプションを駆使すれば第 1 フィールドは辞書式順序で昇順にソートして、第 2 フィールドは 2 文字目に以降に関して辞書式順序で降順にソートして、第 3 フィールドは数値順で降順にソートするということまでできるわけです。

$ sort -k 1,1 -k 2.3,2.3r -k 3,3n sort_sample.tsv
1	aba	5
1	abb	5
1	baa	10
1	aab	5
1	bab	1
10	abb	10
10	aab	10
10	bab	5
5	abb	1
5	aab	5

※区切り文字を指定した場合は -k 2.2,2.2r になります

sort コマンド便利ですねっ!!!!

参考