sort -n の代わりに2つ以上の数値をlinuxシェルでソートする

`sort -n` では数値を1つ含むような文字列をソートできますが、その代わりに複数の数値含むような文字列をlinuxシェルでソートする方法について考えてみました。

## 2022/5/21追記

素晴らしいリプライを頂いてこの記事の全てを破壊していきました。`sort -V` でいい感じにソートできるそうです。

```
$ cat hoge.txt | sort -V
https://example.com/users/9999/posts/3
https://example.com/users/9999/posts/20
https://example.com/users/9999/posts/100
https://example.com/users/10000/posts/3
https://example.com/users/10000/posts/20
https://example.com/users/10000/posts/100
```

https://twitter.com/rithmety/status/1527912467283578881

`-V` というのはバージョニングをソートしてくれるオプションらしいです。全く知りませんでした……情報ありがとうございました！！

https://qiita.com/quryu/items/daa433a10a1be525db77

以下、この記事の残骸です。

## 結論

このどれかを使えば大体の場合に対応できそうです。

```
cat hoge.txt | sort -t "【区切り文字】" -k{1..99} -g
```

```
cat hoge.txt | sed 's/[^0-9]/\0\t/g' | sort -t$'\t' -k{1..99} -g | tr -d '\t'
```

```
cat hoge.txt | ruby -e 'print readlines.sort_by{|x| x.scan(/[0-9]+|./).map{|y| [y.to_i, y] } }.join'
```

以下で解説していきます。



## 問題定義

```:sort_input.txt
https://example.com/users/9999/posts/3
https://example.com/users/9999/posts/20
https://example.com/users/9999/posts/100
https://example.com/users/10000/posts/3
https://example.com/users/10000/posts/20
https://example.com/users/10000/posts/100
```

```
$ cat sort_input.txt | shuf > hoge.txt
```

これをソートします。環境は Ubuntu 20.04 です。

## 1. 区切り文字が判明している場合

https://unix.stackexchange.com/questions/167518/how-to-sort-on-two-fields-of-numbers

```
cat hoge.txt | sort -t "/" -k1 -k2 -k3 -k4 -k5 -k6 -k7 -k8 -k9 -g
```

`-t` で区切り文字を指定して、区切られた各部（`-k1` ～ `-k9`）に対して -g で数値順にソートを行っています。

これはbashのブレース展開を使って短く書き直すことも出来ます。

```
cat hoge.txt | sort -t "/" -k{1..99} -g 
```

### ※補足1

環境によっては `-k1 -k2 ...` ではなく `-k1,1 -k2,2 ...` と書く必要がある場合もあるかもしれません。Ubuntu 20.04 では問題有りませんでした。

https://stackoverflow.com/questions/357560/sorting-multiple-keys-with-unix-sort

### ※補足2: 既知の問題

```:temp.txt
https://example.com/users/10000/posts/3
https://example.com/users/10000/posts/20
https://example.com/users/10000/posts/100
https://example.com/users/10000/likes/3
https://example.com/users/10000/likes/20
https://example.com/users/10000/likes/100
```

```
$ cat temp.txt | shuf | sed 's/[^0-9]/\0\t/g' | sort -t$'\t' -k{1..99} -g | tr -d '\t'
https://example.com/users/10000/likes/3
https://example.com/users/10000/posts/3
https://example.com/users/10000/likes/20
https://example.com/users/10000/posts/20
https://example.com/users/10000/likes/100
https://example.com/users/10000/posts/100
```

上記のように数値ソートと文字列ソートを同時に行おうとすると問題が発生するようでした。解決策募集中です。

## 2. 区切り文字が不定の場合

上の例はスラッシュで区切っているので汎用性はありません。ちょっと改良します。

```
cat hoge.txt | sed 's/[^0-9]/\0\t/g' | sort -t$'\t' -k{1..99} -g | tr -d '\t'
```

`sed` でタブ区切りに変換した後、`sort` で各フィールドをソートします。これで任意の（タブ文字を含まない）文字列がソート出来るようになりました。

## 3. より一般の場合

複雑なことをしたいならワンライナーということで、Rubyのワンライナーも試してみましょう。

```
cat hoge.txt | ruby -e 'print readlines.sort_by{|x| x.scan(/[0-9]+|./).map{|y| [y.to_i, y] } }.join'
```

ちょっと長いですが、フィールドが有限個に限定されるのが嫌ならばこれを使うと良いかもしれません。また、この方法であれば文字列と数値が混在していても問題なくソートすることができます。他の方法もあるよ！ という方や、もっと短く出来るよ！ という方はコメントお待ちしております。

### 追記2

Ruby ワンライナーで書く方法の別のバージョンを頂きました。

```
ruby -e "puts ARGF.sort_by{ |x| x.gsub(/\d+/){ '%020d' % $&.to_i } }" test.txt
```

https://twitter.com/atami_urara/status/1527957683482923008