サーバー管理をしていたころ、大量のドメイン名やIPアドレスを扱う事が多く、読み易くしたいので階層順に並べ替えるスクリプトを書いて使っていました。
目次
- 目次
- ドメイン名とは?ドメイン名の構造
- 実行例
- 操作説明書【マニュアル】(の参照方法)
- スクリプト
ドメイン名とは?ドメイン名の構造
ドメイン名というのは「blog.hatena.ne.jp」を例にすると、次のような階層構造になっています。
省略される事が多いのですが、「.jp」の前、一番先頭にある「.(ドット)」の部分はドメインの根にあたります。「root(ルート)」や「" "」と表記される事も多いです。このルートドメインが、国を表す「com」や「jp」などのトップレベルドメインを束ねています。ドメインはツリー状に階層が連なり構成されているのです。
上位のドメインと同じように「hatena.ne.jp.」を管理しているhatenaのサイトは、「blog」等のサブドメインを包含しています。他にも「s」,「f」,「mailinbound」,「mail」というサブドメインを管理しているようです。
- hatena.ne.jp.
- blog.hatena.ne.jp.
- s.hatena.ne.jp.
- f.hatena.ne.jp.
- mailinbound.hatena.ne.jp.
- mail.hatena.ne.jp.
これを読み易いように並べ替える時、普通にソートすると以下のような順番になってしまいます。
$ cat <<__EOD__ | sort
> hatena.ne.jp.
> blog.hatena.ne.jp.
> s.hatena.ne.jp.
> f.hatena.ne.jp.
> mailinbound.hatena.ne.jp.
> mail.hatena.ne.jp.
> __EOD__
blog.hatena.ne.jp. ← ソート結果
f.hatena.ne.jp. ↓
hatena.ne.jp.
mail.hatena.ne.jp.
mailinbound.hatena.ne.jp.
s.hatena.ne.jp.
$
期待している順番は、最初に説明したドメインの階層順です。次のような感じ。
hatena.ne.jp.
blog.hatena.ne.jp.
f.hatena.ne.jp.
mail.hatena.ne.jp.
mailinbound.hatena.ne.jp.
s.hatena.ne.jp.
そこで、Perlでちょこちょこと書いて作成したのが「domsort」というscriptです。名前の通り、「sort」コマンドに似せた仕様にしています。
ドメイン名以外にも、IPアドレスも階層順に並べ替える事ができます。
PATHの通っている「/usr/local/bin/」に入れて使っていました。
実行例
ドメイン名(の階層順)でソート
ドメイン名は大文字・小文字の区別が無いので、このスクリプトのデフォルトの動作も大文字・小文字の違いを無視する仕様にしています。(区別したい場合は「-f」オプションを使います。)
データ[test_data_1_domain_name.txt]
mail2.abc.com
mail1.abc.com
mail1.abc.com
abc.com
freemail.abc.com
a.b.c.d.co.jp
a.b.c.co.jp
X.Y.Z.COM
freemail.abc.com
freemail.abc.com
freemail.abc.com
X.Y.Z.COM
freemail.abc.com
x.y.z.com
並べ替え
$ domsort test_data_1_domain_name.txt
abc.com
freemail.abc.com
freemail.abc.com
freemail.abc.com
freemail.abc.com
freemail.abc.com
mail1.abc.com
mail1.abc.com
mail2.abc.com
X.Y.Z.COM
X.Y.Z.COM
x.y.z.com
a.b.c.co.jp
a.b.c.d.co.jp
$
説明し易いように階層ごとに色分けしました。トップレベルドメイン, 第2レベルドメイン, 第3レベルドメイン, 第4レベルドメイン, ... という優先順位で並べ替えをおこなっています。
メールアドレスをソート
データ[test_data_2_mail_addrerss.txt]
user01@mail2.abc.com
user10@mail1.abc.com
user01@mail1.abc.com
user09@abc.com
user11@freemail.abc.com
user05@a.b.c.d.co.jp
user08@a.b.c.co.jp
user20@X.Y.Z.COM
user04@freemail.abc.com
user03@freemail.abc.com
user15@freemail.abc.com
user07@X.Y.Z.COM
user13@freemail.abc.com
user16@x.y.z.com
並べ替え
$ domsort test_data_2_mail_addrerss.txt
user09@abc.com
user03@freemail.abc.com
user04@freemail.abc.com
user11@freemail.abc.com
user13@freemail.abc.com
user15@freemail.abc.com
user01@mail1.abc.com
user10@mail1.abc.com
user01@mail2.abc.com
user07@X.Y.Z.COM
user16@x.y.z.com
user20@X.Y.Z.COM
user08@a.b.c.co.jp
user05@a.b.c.d.co.jp
$
フィールドを指定してソート(TAB区切りファイルの2列目)
データ[test_data_3_users.tab]
user01 mail2.abc.com
user10 mail1.abc.com
user01 mail1.abc.com
user09 abc.com
user11 freemail.abc.com
user05 a.b.c.d.co.jp
user08 a.b.c.co.jp
user20 X.Y.Z.COM
user04 freemail.abc.com
user03 freemail.abc.com
user15 freemail.abc.com
user07 X.Y.Z.COM
user13 freemail.abc.com
user16 x.y.z.com
並べ替え
デフォルトのフィールドセパレーターは半角スペースとタブ文字なので、TAB区切り形式であればフィールドセパレーターの指定は不要です。キーとなるデータ(ドメイン名)が2列目であることを示す為、「-k 2」を指定します。
$ domsort test_data_3_users.tab -k 2
user09 abc.com
user03 freemail.abc.com
user04 freemail.abc.com
user11 freemail.abc.com
user13 freemail.abc.com
user15 freemail.abc.com
user01 mail1.abc.com
user10 mail1.abc.com
user01 mail2.abc.com
user07 X.Y.Z.COM
user16 x.y.z.com
user20 X.Y.Z.COM
user08 a.b.c.co.jp
user05 a.b.c.d.co.jp
$
フィールドを指定してソート(カンマ区切りファイルの2列目)
データ[test_data_4_users.csv]
user01,mail2.abc.com
user10,mail1.abc.com
user01,mail1.abc.com
user09,abc.com
user11,freemail.abc.com
user05,a.b.c.d.co.jp
user08,a.b.c.co.jp
user20,X.Y.Z.COM
user04,freemail.abc.com
user03,freemail.abc.com
user15,freemail.abc.com
user07,X.Y.Z.COM
user13,freemail.abc.com
user16,x.y.z.com
並べ替え
CSVファイルのフィールドセパレーターである「,(カンマ)」と、キー列の「2」を指定します。
$ domsort test_data_4_users.csv -k 2 -t ,
user09,abc.com
user03,freemail.abc.com
user04,freemail.abc.com
user11,freemail.abc.com
user13,freemail.abc.com
user15,freemail.abc.com
user01,mail1.abc.com
user10,mail1.abc.com
user01,mail2.abc.com
user07,X.Y.Z.COM
user16,x.y.z.com
user20,X.Y.Z.COM
user08,a.b.c.co.jp
user05,a.b.c.d.co.jp
$
IPアドレスとドメイン名の一覧ファイルをソート
データ[test_data_5_ip_and_domain.tab]
142.250.196.100 www.google.com.
142.250.196.142 google.com.
142.250.196.99 www.google.co.jp.
142.250.207.3 google.co.jp.
172.217.26.229 gmail.com.
54.192.76.212 amazon.jp.
99.86.128.253 amazon.jp.
23.60.109.197 www.amazon.jp.
52.119.168.48 amazon.co.jp.
52.119.161.5 amazon.co.jp.
52.119.164.121 amazon.co.jp.
23.60.109.197 www.amazon.co.jp.
52.94.236.248 amazon.com.
54.239.28.85 amazon.com.
205.251.242.103 amazon.com.
23.210.221.28 www.amazon.com.
IPアドレス(の階層順)で並べ替え
- IPアドレスは1列目なのでキーの指定は省略
$ domsort test_data_5_ip_and_domain.tab
23.60.109.197 www.amazon.co.jp.
23.60.109.197 www.amazon.jp.
23.210.221.28 www.amazon.com.
52.94.236.248 amazon.com.
52.119.161.5 amazon.co.jp.
52.119.164.121 amazon.co.jp.
52.119.168.48 amazon.co.jp.
54.192.76.212 amazon.jp.
54.239.28.85 amazon.com.
99.86.128.253 amazon.jp.
142.250.196.99 www.google.co.jp.
142.250.196.100 www.google.com.
142.250.196.142 google.com.
142.250.207.3 google.co.jp.
172.217.26.229 gmail.com.
205.251.242.103 amazon.com.
$
ドメイン名(の階層順)で並べ替え
- キーを2列目に指定
$ domsort test_data_5_ip_and_domain.tab -k 2
205.251.242.103 amazon.com.
52.94.236.248 amazon.com.
54.239.28.85 amazon.com.
23.210.221.28 www.amazon.com.
172.217.26.229 gmail.com.
142.250.196.142 google.com.
142.250.196.100 www.google.com.
54.192.76.212 amazon.jp.
99.86.128.253 amazon.jp.
23.60.109.197 www.amazon.jp.
52.119.161.5 amazon.co.jp.
52.119.164.121 amazon.co.jp.
52.119.168.48 amazon.co.jp.
23.60.109.197 www.amazon.co.jp.
142.250.207.3 google.co.jp.
142.250.196.99 www.google.co.jp.
$
操作説明書【マニュアル】(の参照方法)
$ domsort --help
usage: domsort [<OPTIONS...>] [<FILE...>]
Try `perldoc domsort' for more information.$ perldoc domsort | cat -
NAME
DOMSORT - Sort by TreeSYNOPSIS
$ domsort [OPTIONS...] [FILE...]DESCRIPTION
Sort the list of domain names, email addresses, and IP addresses in tree
order.FILEs specifies the input file name. If it is a standard input, "-" is
given.OPTIONS
-f Force case sensitivity.-h, --help
Simple help is displayed.-k column
Specifies the column to use for sorting.-r Reverse the result of comparisons.
-t SEP
Specify SEP as the field separator.ADVANCED USAGE
$ cat <<__EOD__ | domsort -
mail2.abc.com
mail1.abc.com
mail1.abc.com
abc.com
freemail.abc.com
a.b.c.d.co.jp
a.b.c.co.jp
X.Y.Z.com
freemail.abc.com
freemail.abc.com
freemail.abc.com
X.Y.Z.com
freemail.abc.com
x.y.z.com
__EOD__
$ dig amazon.com. | grep '^amazon\.com\.' | ./domsort -k 5 -SEE ALSO
Other more basic referencesperl(1), sort(1)
BUGS
* none notedReport bugs to tomyama.
AUTHOR
Written by tomyama
スクリプト
[domsort]
#!/usr/bin/perl -w
################################################################################
## DOMSORT -- Sort by Hierarchy
##
## - author : tomyama
## - only for personal use !
################################################################################
use strict;
use File::Basename qw{ dirname basename };
exit( &main_pl( @ARGV ) );
##########
## プログラムのエントリポイント
sub main_pl( @ ){my %param;
## 初期化
&initialize();## 引数処理
&parse_arg( \%param, @_ );## データ読み込み
my @data;
foreach my $file ( @{$param{ 'FILE_INPUT' }} ){
&open_data( \@data, $file );
}## ソート処理
my @data_sort = sort myCompareFunc ( @data );
## 出力する
if( defined( $main::reverse ) ){
print( reverse( @data_sort ) );
}else{
print( @data_sort );
}
}##########
## 初期化
sub initialize(){
##### GLOBAL #################
$main::apppath = dirname( $0 );
$main::appname = basename( $0 );
################################### DEFAULT PARAMETER ######
$main::start_field = 0;
$main::delimiter = '\s+';
$main::ignorecase = 1;
##############################
}##########
## 引数処理
sub parse_arg( \%@ ){
my $pPM = shift( @_ );
my @myArgv = @_;## 引数解析
while( my $myValue = shift( @myArgv ) ){
if( ( "$myValue" eq '-h' ) || ( "$myValue" eq '--help' ) ){
&usage( 0 );
exit( 0 );
## 「電話帳」順でソートする
}elsif( "$myValue" eq '-d' ){
$main::sortbydial = 1;
## 大文字小文字の違いを強制する
}elsif( "$myValue" eq '-f' ){
undef( $main::ignorecase );
## 逆順に並べる
}elsif( "$myValue" eq '-r' ){
$main::reverse = 1;
## 着目するフィールド
}elsif( "$myValue" eq '-k' ){
$main::start_field = shift( @myArgv ) || die( "usage: domsort -k <column> <file...>\n" );
if( !( $main::start_field =~ m/^[0-9]+$/o ) ){
die( "$main::start_field is not a number\n" );
}
$main::start_field -= 1;
## フィールドセパレータ
}elsif( "$myValue" eq '-t' ){
$main::delimiter = shift( @myArgv ) ||
die( "usage: domsort -t <delimiter> <file...>\n" );
}else{
push( @{${$pPM}{ 'FILE_INPUT' }}, $myValue );
}
}## ファイルが指定されていなかったら、標準入力から受け付ける
${$pPM}{ 'FILE_INPUT' }[0] = '-' if( $#{${$pPM}{ 'FILE_INPUT' }} le -1 );
}##########
## 入力データを処理
sub open_data( \@$ ){
my $pData = shift( @_ );
my $inFile = shift( @_ );open( hFI_IN, "<$inFile" ) ||
die( "$inFile: cannot open file: $!\n" );
@{$pData} = ( @{$pData}, <hFI_IN> );
close( hFI_IN );return 0;
}##########
## ソート用コンペア関数
sub myCompareFunc{
##########
local *mkStr = sub( $ ){
my $myStr = $_[0];## sortbydialが指定されていたら、「電話帳」順でソートする
## アルファベット、数字、空白以外のキャラクタをすべて無視
$myStr =~ s/[^a-zA-Z0-9 \t]//go if( defined( $main::sortbydial ) );
## ignorecaseが指定されていたら、小文字に変換する
$myStr = lc( $myStr ) if( defined( $main::ignorecase ) );
## フィールドに分解する
if( my @field = split( /$main::delimiter/, $myStr ) ){
##
return '' if( ! defined( $field[ $main::start_field ] ) );
## IPアドレスかどうか検証
if( $field[ $main::start_field ] =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/o) {
if( ( ( 0 <= $1 ) && ( $1 <= 255 ) ) &&
( ( 0 <= $2 ) && ( $2 <= 255 ) ) &&
( ( 0 <= $3 ) && ( $3 <= 255 ) ) &&
( ( 0 <= $4 ) && ( $4 <= 255 ) ) ){
return sprintf( "%03d.%03d.%03d.%03d", $1, $2, $3, $4 );
}
}
## メールアドレスかどうか検証
my $username = '' ;
my $domain = "$field[ $main::start_field ]";
if( $field[ $main::start_field ] =~ m/^(.*)\@(.*)$/o ){
$username = $1;
$domain = $2;
}
## ドメインを分解
if( my @item_dom = split( /\./, $domain ) ){
$domain = join( ".", reverse( @item_dom ) );
}
#printf( "OK %s %s OK\n", $domain, $username );
return sprintf( "%s %s", $domain, $username );
}
return $myStr;
} ;
##########
if( ( my $ret = &mkStr( $a ) cmp &mkStr( $b ) ) != 0 ){
return $ret;
}
return $a cmp $b;
}##########
## 書式表示
sub usage( $ ){
my $msg = "usage: $main::appname [<OPTIONS...>] [<FILE...>]\n" .
"Try `perldoc $main::apppath/$main::appname' for more information.\n";if( $_[0] ){
print STDERR ( $msg );
} else {
print STDOUT ( $msg );
}return 0;
}
__END__
## http://perldoc.jp/docs/perl/5.22.1/perlpod.pod=pod
=head1 NAME
DOMSORT - Sort by Tree
=head1 SYNOPSIS
$ domsort [I<OPTIONS...>] [I<FILE...>]
=head1 DESCRIPTION
Sort the list of domain names, email addresses, and IP addresses in tree order.
I<FILE>s specifies the input file name.
If it is a standard input, "B<->" is given.=head1 OPTIONS
=over 4
=item -f
Force case sensitivity.
=item -h, --help
Simple help is displayed.
=item -k I<column>
Specifies the I<column> to use for sorting.
=item -r
Reverse the result of comparisons.
=item -t I<SEP>
Specify I<SEP> as the field separator.
=back
=head1 ADVANCED USAGE
=for text $ cat <<__EOD__ | domsort -
mail2.abc.com
mail1.abc.com
mail1.abc.com
abc.com
freemail.abc.com
a.b.c.d.co.jp
a.b.c.co.jp
X.Y.Z.com
freemail.abc.com
freemail.abc.com
freemail.abc.com
X.Y.Z.com
freemail.abc.com
x.y.z.com
__EOD__$ dig amazon.com. | grep '^amazon\.com\.' | ./domsort -k 5 -
=head1 SEE ALSO
Other more basic references
=over 4
L<perl>(1), sort(1)
=back
=head1 BUGS
* none noted
Report bugs to tomyama.
=head1 AUTHOR
Written by tomyama
=cut