IPv4アドレスのソート方法あれこれ
2021/05/21 05:34
IPv4アドレスのソート方法あれこれ
IPv4 のリストは、ソートしたくなる時があります。
いろいろな場面で使えるソート方法を書いてみました。
とは言ってもいまのところ、そのままで IPv4 形式を扱えるコマンドオプションを利用するか、ロングIPアドレスの形式に直して扱うかの2択なんですが。
Linux 編
Bash (シンプルなIPアドレスソート)
ソート対象のサンプルファイルが、これです。sample.txt
1.2.3.4ソートしたい対象が、シンプルに IP アドレスだけなら簡単。 bash ならね。
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
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.150bash バージョンがちょっと古いなら、こんなオプションでも可能みたい。
$ 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こんなドメイン+IPアドレスになったりすると、上記のソートは使えませんね。
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
実際に試してみました。
$ 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.90IPアドレスでソートしたいのに、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
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 を格納する変数って、スクリプトだとどうやるんだろう?
誰か教えて!