IPv4アドレスのソート方法あれこれ

はてブ数 2021/05/21 05:34 その他::技術情報

IPv4アドレスのソート方法あれこれ
IPv4 のリストは、ソートしたくなる時があります。
いろいろな場面で使えるソート方法を書いてみました。

とは言ってもいまのところ、そのままで IPv4 形式を扱えるコマンドオプションを利用するか、ロングIPアドレスの形式に直して扱うかの2択なんですが。

Linux 編

Bash (シンプルなIPアドレスソート)

ソート対象のサンプルファイルが、これです。

sample.txt
1.2.3.4
1.1.1.1
20.10.5.3
3.5.7.9
7.10.5.4
60.70.80.90
9.8.7.6
150.150.150.150
7.6.5.4
ソートしたい対象が、シンプルに IP アドレスだけなら簡単。 bash ならね。

sort コマンドに --version-sort オプションがあります。
(ほら、バージョン情報もドットで区切る数値の塊だから)
$ sort --version-sort sample.txt
1.1.1.1
1.2.3.4
3.5.7.9
7.6.5.4
7.10.5.4
9.8.7.6
20.10.5.3
60.70.80.90
150.150.150.150
こちらは、まったく同じオプション(省略形)です。
$ sort -V sample.txt
1.1.1.1
1.2.3.4
3.5.7.9
7.6.5.4
7.10.5.4
9.8.7.6
20.10.5.3
60.70.80.90
150.150.150.150
bash バージョンがちょっと古いなら、こんなオプションでも可能みたい。
$ sort -n -t '.' -k1,1 -k2,2 -k3,3 -k4n,4 sample.txt
1.1.1.1
1.2.3.4
3.5.7.9
7.6.5.4
7.10.5.4
9.8.7.6
20.10.5.3
60.70.80.90
150.150.150.150

Bash (CSV のIPアドレスソート(PHP ip2long を利用))

対象のリストが CSV 形式のうえ、ドットが別の場所にあったりすると最悪です。

sampel2.txt
example.jp,1.2.3.4
example.com,1.1.1.1
example.co.jp,20.10.5.3
work.example.jp,3.5.7.9
auction.example.com,7.10.5.4
www.example.jp,60.70.80.90
ftp.example.jp,9.8.7.6
mail.example.jp,150.150.150.150
auction.example.jp,7.6.5.4
こんなドメイン+IPアドレスになったりすると、上記のソートは使えませんね。
実際に試してみました。
$ sort --version-sort sample2.txt
auction.example.com,7.10.5.4
auction.example.jp,7.6.5.4
example.com,1.1.1.1
example.co.jp,20.10.5.3
example.jp,1.2.3.4
ftp.example.jp,9.8.7.6
mail.example.jp,150.150.150.150
work.example.jp,3.5.7.9
www.example.jp,60.70.80.90
IPアドレスでソートしたいのに、IPアドレスではなくドメインでのソートになっています。

ということで、キレイではありませんが*1ロングIPアドレスをリストに付与して並べるようにしてみます。
$ unset list
$ for i in $(cat sample2.txt)
do
  tmp=$(echo $i | cut -d ',' -f 2)
  tmp2=$(php -r 'echo sprintf ("%u", ip2long("$argv[1]"));' $tmp)
  list=$(printf '%s\n%s,%u' "$list" $i $tmp2)
done
$ echo "$list" | sort -t ',' -n -k3 | grep -v "^$"
example.com,1.1.1.1,16843009
example.jp,1.2.3.4,16909060
work.example.jp,3.5.7.9,50661129
auction.example.jp,7.6.5.4,117835012
auction.example.com,7.10.5.4,118097156
ftp.example.jp,9.8.7.6,151521030
example.co.jp,20.10.5.3,336200963
www.example.jp,60.70.80.90,1011241050
mail.example.jp,150.150.150.150,2526451350
ここでは PHP のワンライナーを使ってますが、php コマンドが使えない場合はオクテット毎に桁の重みを計算しないとダメでしょうね。

Bash (CSV のIPアドレスソート(AWK を利用))

変化形として awk で計算してみます。

まずは、計算できるか確認……
$ awk -F '.' '{a=0;b=0;c=0;d=0;for(i=1;i<=NF;i++){a=$1*256*256*256;b=$2*256*256;c=$3*256;d=$4}; printf "%u\n", a+b+c+d}' sample.txt
16909060
16843009
336200963
50661129
118097156
1011241050
151521030
2147483647
117835012
$ # もしくは
$ awk -F '.' '{a=0;b=0;c=0;d=0;for(i=1;i<=NF;i++){a=$1*256^3;b=$2*256^2;c=$3*256;d=$4}; printf "%u\n", a+b+c+d}' sample.txt
16909060
16843009
336200963
50661129
118097156
1011241050
151521030
2147483647
117835012
$ # もう少し短くできそう
$ awk -F '.' '{a=0;for(i=1;i<=NF;i++){a=$1*256^3+$2*256^2+$3*256+$4}; printf "
%u\n", a}' sample.txt
16909060
16843009
336200963
50661129
118097156
1011241050
151521030
2147483647
117835012
できたので組み込んでみます。
$ unset list
$ for i in $(cat sample2.txt)
do
  tmp=$(echo $i | cut -d ',' -f 2)
  tmp2=$(echo $tmp | awk -F'.' '{a=$1*256^3+$2*256^2+$3*256+$4}{printf "%u", a}')
  list=$(printf '%s\n%s,%u' "$list" $i $tmp2)
done
$ echo "$list" | sort -t ',' -n -k3 | grep -v "^$"
example.com,1.1.1.1,16843009
example.jp,1.2.3.4,16909060
work.example.jp,3.5.7.9,50661129
auction.example.jp,7.6.5.4,117835012
auction.example.com,7.10.5.4,118097156
ftp.example.jp,9.8.7.6,151521030
example.co.jp,20.10.5.3,336200963
www.example.jp,60.70.80.90,1011241050
mail.example.jp,150.150.150.150,2147483647
……なんとなく、awk だけでも IP アドレスでソートできそうな予感をひしひしと感じてたりしますが、ここでは考えません。
(Separator を2つ、個別に指定する方法が思い浮かびません……)

余分なロングIPアドレス列を消したい場合は、そのまま cut するとよいですかね。
$ echo "$list" | sort -t ',' -n -k3 | grep -v "^$" | cut -d "," -f 1,2
example.com,1.1.1.1
example.jp,1.2.3.4
work.example.jp,3.5.7.9
auction.example.jp,7.6.5.4
auction.example.com,7.10.5.4
ftp.example.jp,9.8.7.6
example.co.jp,20.10.5.3
www.example.jp,60.70.80.90
mail.example.jp,150.150.150.150

*1 : というかかなりキタナイ……。シェルスクリプトなのに、書いてあることを理解できる人は少ないのではなかろうか……

Windows 編

Excel

基本的には、IPアドレスをロングIPアドレスに変換して、Excel のソート機能「並べ替えとフィルター」で昇順に並べ替えます。
  • A1をアクティブセルにして、sample.txt の内容を貼り付けます。
  • B1セルに、つぎの内容を入力します。
=LEFT(A1,SEARCH(".",A1,1)-1)*(256*256*256)+MID(A1,SEARCH(".",A1,1)+1,SEARCH(".",A1,SEARCH(".",A1,1)+1)-SEARCH(".",A1,1)-1)*(256*256)+MID(A1,SEARCH(".",A1,SEARCH(".",A1,1)+1)+1,SEARCH(".",A1,SEARCH(".",A1,SEARCH(".",A1,1)+1)+1)-SEARCH(".",A1,SEARCH(".",A1,1)+1)-1)*256+MID(A1,SEARCH(".",A1,SEARCH(".",A1,SEARCH(".",A1,1)+1)+1)+1,SEARCH(".",A1,SEARCH(".",A1,SEARCH(".",A1,1)+1)+1)-SEARCH(".",A1,SEARCH(".",A1,1)+1)-1)
この関数、やっていることは . の位置を調べて mid で数値を抜き出し、各オクテットの重みを掛けてロングIPアドレスを算出しています。
  • B1セルの右下隅をダブルクリックして、数式をコピペ(オートフィル)します。
  • B列のどこかのセルをクリックしてオートフィルを利用した後の範囲指定状態を解除します。
  • 「並べ替えとフィルター」で昇順に並べ替えます。

その他

IPv6 は?

IPv6 は変数にいれるのも大変なので、なかなか難しいもよう。

IPv4 は 32bit なので、符号なし int なら何とか扱える感じ。
実際、符号あり int で計算しようとすると負の値になってしまうので、このページで使ってる printf / sprintf でも %u を指定しているのは、そのため。

128bit を格納する変数って、スクリプトだとどうやるんだろう?
誰か教えて!