tomyamaのブログ

日記・雑記。

ドメイン名の階層順にソートするコマンド

昔に書いたPerlスクリプトです。

サーバー管理をしていたころ、大量のドメイン名やIPアドレスを扱う事が多く、読み易くしたいので階層順に並べ替えるスクリプトを書いて使っていました。

 

 


目次


ドメイン名とは?ドメイン名の構造

ドメイン名というのは「blog.hatena.ne.jp」を例にすると、次のような階層構造になっています。

 

ドメイン名「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アドレス(の階層順)で並べ替え

$ 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 Tree

SYNOPSIS
    $ 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 references

        perl(1), sort(1)

BUGS
    * none noted

    Report 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