LoginSignup
2
1

More than 5 years have passed since last update.

Graphviz(dot)ノート 応用編 findの出力結果を図示する

Last updated at Posted at 2018-03-08

はじめに

findの出力結果をdotで図示する例を紹介します。

単純な例

【ファイル・ディレクトリ構成】
find d1 | xargs ls -ld
drwxrwxr-x. xxxxxxx d1
drwxrwxr-x. xxxxxxx d1/d11
drwxrwxr-x. xxxxxxx d1/d11/d111
drwxrwxr-x. xxxxxxx d1/d11/d112
-rw-rw-r--. xxxxxxx d1/d11/d112/f1121
drwxrwxr-x. xxxxxxx d1/d12
drwxrwxr-x. xxxxxxx d1/d12/d121

※ ユーザ名、グループ名、タイムスタンプはxxxxxxxで伏せています。

【出力イメージ】

q.png

【shスクリプト】
basic_find_v1.sh
#!/bin/sh
_find(){
    top_dir=$1
    find ${top_dir} -print -exec dirname {} \; -exec basename {} \; | paste - - -
}
_find $1 | gawk '
BEGIN{
    printf("digraph %s {\n", "xxx" );
}
{
    id = $1;
    parent = $2;
    label = $3;
    printf("\"%s\" [label=\"%s\"]\n", id, label );
    printf("\"%s\" -> \"%s\"\n", parent, id );
}
END{
    printf("}\n");
}
' | tee /dev/tty | dot -T png -o q.png
【実行】
スクリプトの起動
sh basic_find_v1.sh d1
実行結果
digraph xxx {
"d1" [label="d1"]
"." -> "d1"
"d1/d11" [label="d11"]
"d1" -> "d1/d11"
"d1/d11/d111" [label="d111"]
"d1/d11" -> "d1/d11/d111"
"d1/d11/d112" [label="d112"]
"d1/d11" -> "d1/d11/d112"
"d1/d11/d112/f1121" [label="f1121"]
"d1/d11/d112" -> "d1/d11/d112/f1121"
"d1/d12" [label="d12"]
"d1" -> "d1/d12"
"d1/d12/d121" [label="d121"]
"d1/d12" -> "d1/d12/d121"
}
shスクリプトの解説
  1 #!/bin/sh
  2 _find(){
  3     top_dir=$1
  4     find ${top_dir} -print -exec dirname {} \; -exec basename {} \; | paste 
    - - -
 ★4行★ find で出力される パス名、ディレクトリ、ファイル名が3行に別れるので
     paste - - -で1行にまとめています。

  5 }
  6 _find $1 | gawk '
  7 BEGIN{
  8     printf("digraph %s {\n", "xxx" );
  9 }
 10 {
 11     id = $1;
 12     parent = $2;
 13     label = $3;
 14     printf("\"%s\" [label=\"%s\"]\n", id, label );
 15     printf("\"%s\" -> \"%s\"\n", parent, id );
 ★14、15行★ 二重引用符(")で囲まれた文字列の中で二重引用符を使用するために、
       二重引用符を\でエスケープしています。
 16 }
 17 END{
 18     printf("}\n");
 19 }
 20 ' | tee /dev/tty | dot -T png -o q.png
 ★20行★ | tee /dev/tty はデバッグ用です。書かなくてもイメージの出力には影響しません。


 ★その他★ ファイル名、ディレクトリ名に二重引用符(")やバックスラッシュ(\)が含まれる場合
      は想定していません、おそらく正しく動作しません。対応するのはちょっと面倒です。

フォルダとファイルの図形を変える

【出力イメージ】

q.png

【shスクリプト】
basic_find_v2.sh
#!/bin/sh
_find(){
    top_dir=$1
    find ${top_dir} -print
}
_make_input(){
    while read path dummy
    do
        printf "%s %s %s" ${path} `dirname ${path}` `basename ${path}`
        if [ -d "${path}" ]
        then
            printf "\tdirectory\n"
        else
            printf "\tfile\n"
        fi
    done
}
_find $1 | _make_input | gawk '
BEGIN{
    printf("digraph %s {\n", "xxx" );
}
{
    id = $1;
    parent = $2;
    label = $3;
    kind = $4;
    if( kind == "file"){
        printf("\"%s\" [label=\"%s\" shape = box]\n", id, label );
    }
    if( kind == "directory"){
        printf("\"%s\" [label=\"%s\" shape = folder]\n", id, label );
    }
    else {
        printf("\"%s\" [label=\"%s\"]\n", id, label );
    }
    if( parent != "." )printf("\"%s\" -> \"%s\"\n", parent, id );
}
END{
    printf("}\n");
}
' | tee /dev/tty | dot -T png -o q.png
【実行】
スクリプトの起動
sh basic_find_v2.sh d1
実行結果
digraph xxx {
"d1" [label="d1" shape = folder]
"d1/d11" [label="d11" shape = folder]
"d1" -> "d1/d11"
"d1/d11/d111" [label="d111" shape = folder]
"d1/d11" -> "d1/d11/d111"
"d1/d11/d112" [label="d112" shape = folder]
"d1/d11" -> "d1/d11/d112"
"d1/d11/d112/f1121" [label="f1121" shape = box]
"d1/d11/d112/f1121" [label="f1121"]
"d1/d11/d112" -> "d1/d11/d112/f1121"
"d1/d12" [label="d12" shape = folder]
"d1" -> "d1/d12"
"d1/d12/d121" [label="d121" shape = folder]
"d1/d12" -> "d1/d12/d121"
}

shスクリプトの解説
  1 #!/bin/sh
  2 _find(){
  3     top_dir=$1
  4     find ${top_dir} -print
  5 }
  6 _make_input(){
  7     while read path dummy
  8     do
  9         printf "%s %s %s" ${path} `dirname ${path}` `basename ${path}`
 10         if [ -d "${path}" ]
 11         then
 12             printf "\tdirectory\n"
 13         else
 14             printf "\tfile\n"
 15         fi

 ★10-15行★ ファイルがディレクトリかどうか調べ、結果()を出力しています。
  ※結果: ディレクトリの場合 directory
       ファイルの場合   file

 16     done
 17 }
 18 _find $1 | _make_input | gawk '
 19 BEGIN{
 20     printf("digraph %s {\n", "xxx" );
 21 }
 22 {
 23     id = $1;
 24     parent = $2;
 25     label = $3;
 26     kind = $4;
 27     if( kind == "file"){
 28         printf("\"%s\" [label=\"%s\" shape = box]\n", id, label );

★28行★ ファイルの場合はshapeをboxにしています。

 29     }
 30     if( kind == "directory"){
 31         printf("\"%s\" [label=\"%s\" shape = folder]\n", id, label );

★31行★ ディレクトリの場合はshapeをfolderにしています。

 32     }
 33     else {
 34         printf("\"%s\" [label=\"%s\"]\n", id, label );
 35     }
 36     if( parent != "." )printf("\"%s\" -> \"%s\"\n", parent, id );
 37 }
 38 END{
 39     printf("}\n");
 40 }
 41 ' | tee /dev/tty | dot -T png -o q.png

シンボリックリンクを図示する

【ファイル・ディレクトリ構成】
find d1 | xargs ls -ld
drwxrwxr-x. xxxxxxx d1
drwxrwxr-x. xxxxxxx d1/d11
drwxrwxr-x. xxxxxxx d1/d11/d111
drwxrwxr-x. xxxxxxx d1/d11/d112
-rw-rw-r--. xxxxxxx d1/d11/d112/f1121
lrwxrwxrwx. xxxxxxx d1/d11/sd113 -> /etc
lrwxrwxrwx. xxxxxxx d1/d11/sd114 -> ../d12
lrwxrwxrwx. xxxxxxx d1/d11/sd115 -> /xxxx
drwxrwxr-x. xxxxxxx d1/d12
drwxrwxr-x. xxxxxxx d1/d12/d121
lrwxrwxrwx. xxxxxxx d1/d12/f122 -> ../d11/d112/f1121

※ ユーザ名、グループ名、タイムスタンプはxxxxxxxで伏せています。

【出力イメージ】

q.png

【shスクリプト】
basic_find_v3.sh

#!/bin/sh
_find(){
    local top_dir=$1
    find ${top_dir} -print
}
_make_input(){
    local path dummy link_to top_char dir_path dir_link_to file_link_to
    local w w2 

    while read path dummy
    do
        printf "%s %s %s" ${path} `dirname ${path}` `basename ${path}`
        if [ -h "${path}" ]
        then
            link_to=`file ${path} | sed -n 's/.*\`\(.*\)'\''/\1/p'`
            top_char=`sed -n 's/^\(.\).*/\1/p' <<< "${link_to}"`
            if [ "${top_char}" != "/" ]
            then
                dir_path=`dirname ${path}`
                dir_link_to=`dirname ${link_to}`
                file_link_to=`basename ${link_to}`
                w=`cd "${dir_path}";cd "${dir_link_to}";pwd`
                w2=`cd "${top_dir/..}";pwd`
                link_to=`expr "${w}/${file_link_to}" : "${w2}/\(.*\)"`
                link_to="${top_dir}"/"${link_to}"
            fi

            printf "\tsymbolic_link %s\n" "${link_to}"
            continue
        fi
        if [ -d "${path}" ]
        then
            printf "\tdirectory\n"
        else
            printf "\tfile\n"
        fi
    done
}
main(){
    local top_dir=$1
    local type=$2
    local output=$3
    _find ${top_dir} | _make_input | gawk '
    BEGIN{
        printf("digraph %s {\n", "xxx" );
    }
    {
        id = $1;
        parent = $2;
        label = $3;
        kind = $4;
        link_to = $5;
        if( kind == "file"){
            printf("\"%s\" [label=\"%s\" shape = box]\n", id, label );
        }
        if( kind == "directory"){
            printf("\"%s\" [label=\"%s\" shape = folder]\n", id, label );
        }
        else if( kind == "symbolic_link"){
            printf("\"%s\" [label=\"%s\" shape =doubleoctagon ]\n", id, label );
            printf("\"%s\" -> \"%s\" [label = \"ln -s\"]\n", id, link_to );
        }
        else {
            printf("\"%s\" [label=\"%s\"]\n", id, label );
        }
        if( parent != "." )printf("\"%s\" -> \"%s\"\n", parent, id );
    }
    END{
        printf("}\n");
    }
    ' | tee /dev/tty | dot -T ${type} -o ${output}
}

top_dir=$1
type=$2
output=$3
main ${top_dir} ${type} ${output}

【実行】
スクリプトの起動
sh  basic_find_v3.sh d1 png q.png

実行結果
digraph xxx {
"d1" [label="d1" shape = folder]
"d1/d11" [label="d11" shape = folder]
"d1" -> "d1/d11"
"d1/d11/d111" [label="d111" shape = folder]
"d1/d11" -> "d1/d11/d111"
"d1/d11/d112" [label="d112" shape = folder]
"d1/d11" -> "d1/d11/d112"
"d1/d11/d112/f1121" [label="f1121" shape = box]
"d1/d11/d112/f1121" [label="f1121"]
"d1/d11/d112" -> "d1/d11/d112/f1121"
"d1/d11/sd113" [label="sd113" shape =doubleoctagon ]
"d1/d11/sd113" -> "/etc" [label = "ln -s"]
"d1/d11" -> "d1/d11/sd113"
"d1/d11/sd114" [label="sd114" shape =doubleoctagon ]
"d1/d11/sd114" -> "d1/d12" [label = "ln -s"]
"d1/d11" -> "d1/d11/sd114"
"d1/d11/sd115" [label="sd115" shape =doubleoctagon ]
"d1/d11/sd115" -> "/xxxx" [label = "ln -s"]
"d1/d11" -> "d1/d11/sd115"
"d1/d12" [label="d12" shape = folder]
"d1" -> "d1/d12"
"d1/d12/d121" [label="d121" shape = folder]
"d1/d12" -> "d1/d12/d121"
"d1/d12/f122" [label="f122" shape =doubleoctagon ]
"d1/d12/f122" -> "d1/d11/d112/f1121" [label = "ln -s"]
"d1/d12" -> "d1/d12/f122"
}

shスクリプトの解説
  1 #!/bin/sh
  2 _find(){
  3     local top_dir=$1
  4     find ${top_dir} -print
  5 }
  6 _make_input(){
  7     local path dummy link_to top_char dir_path dir_link_to file_link_to
  8     local w w2 
  9 
 10     while read path dummy
 11     do
 12         printf "%s %s %s" ${path} `dirname ${path}` `basename ${path}`
 13         if [ -h "${path}" ]
 14         then
 15             link_to=`file ${path} | sed -n 's/.*\`\(.*\)'\''/\1/p'`
 16             top_char=`sed -n 's/^\(.\).*/\1/p' <<< "${link_to}"`
 17             if [ "${top_char}" != "/" ]
 18             then
 19                 dir_path=`dirname ${path}`
 20                 dir_link_to=`dirname ${link_to}`
 21                 file_link_to=`basename ${link_to}`
 22                 w=`cd "${dir_path}";cd "${dir_link_to}";pwd`
 23                 w2=`cd "${top_dir/..}";pwd`
 24                 link_to=`expr "${w}/${file_link_to}" : "${w2}/\(.*\)"`
 25                 link_to="${top_dir}"/"${link_to}"
 26             fi
 27 
 28             printf "\tsymbolic_link %s\n" "${link_to}"
 29             continue
 30         fi
 31         if [ -d "${path}" ]
 32         then
 33             printf "\tdirectory\n"
 34         else
 35             printf "\tfile\n"
 36         fi
 37     done
 38 }
 39 main(){
 40     local top_dir=$1
 41     local type=$2
 42     local output=$3
 43     _find ${top_dir} | _make_input | gawk '
 44     BEGIN{
 45         printf("digraph %s {\n", "xxx" );
 46     }
 47     {
 48         id = $1;
 49         parent = $2;
 50         label = $3;
 51         kind = $4;
 52         link_to = $5;
 53         if( kind == "file"){
 54             printf("\"%s\" [label=\"%s\" shape = box]\n", id, label );
 55         }
 56         if( kind == "directory"){
 57             printf("\"%s\" [label=\"%s\" shape = folder]\n", id, label );
 58         }
 59         else if( kind == "symbolic_link"){
 60             printf("\"%s\" [label=\"%s\" shape =doubleoctagon ]\n", id, labe
    l );

 ★60行★ シンボリックリンクの場合、shapeをdoubleoctagonにしています。


 61             printf("\"%s\" -> \"%s\" [label = \"ln -s\"]\n", id, link_to );

 ★61行★ シンボリックリンクのエッジのlabelを”ln -s”にしています。


 62         }
 63         else {
 64             printf("\"%s\" [label=\"%s\"]\n", id, label );
 65         }
 66         if( parent != "." )printf("\"%s\" -> \"%s\"\n", parent, id );
 67     }
 68     END{
 69         printf("}\n");
 70     }
 71     ' | tee /dev/tty | dot -T ${type} -o ${output}
 72 }
 73 
 74 top_dir=$1
 75 type=$2
 76 output=$3
 77 main ${top_dir} ${type} ${output}

【shスクリプト(フィルタ版)】

basic_find_v4.sh
#!/bin/sh
_find(){
    local top_dir=$1
    find ${top_dir} -print
}
_make_input(){
    local path dummy link_to top_char dir_path dir_link_to file_link_to
    local w w2 

    while read path dummy
    do
        printf "%s %s %s" ${path} `dirname ${path}` `basename ${path}`
        if [ -h "${path}" ]
        then
            link_to=`file ${path} | sed -n 's/.*\`\(.*\)'\''/\1/p'`
            top_char=`sed -n 's/^\(.\).*/\1/p' <<< "${link_to}"`
            if [ "${top_char}" != "/" ]
            then
                dir_path=`dirname ${path}`
                dir_link_to=`dirname ${link_to}`
                file_link_to=`basename ${link_to}`
                w=`cd "${dir_path}";cd "${dir_link_to}";pwd`
                w2=`cd "${top_dir/..}";pwd`
                link_to=`expr "${w}/${file_link_to}" : "${w2}/\(.*\)"`
                link_to="${top_dir}"/"${link_to}"
            fi

            printf "\tsymbolic_link %s\n" "${link_to}"
            continue
        fi
        if [ -d "${path}" ]
        then
            printf "\tdirectory\n"
        else
            printf "\tfile\n"
        fi
    done
}
mainx(){
    _make_input | gawk '
    BEGIN{
        printf("digraph %s {\n", "xxx" );
    }
    {
        id = $1;
        parent = $2;
        label = $3;
        kind = $4;
        link_to = $5;
        if( kind == "file"){
            printf("\"%s\" [label=\"%s\" shape = box]\n", id, label );
        }
        if( kind == "directory"){
            printf("\"%s\" [label=\"%s\" shape = folder]\n", id, label );
        }
        else if( kind == "symbolic_link"){
            printf("\"%s\" [label=\"%s\" shape =doubleoctagon ]\n", id, label );
            printf("\"%s\" -> \"%s\" [label = \"ln -s\"]\n", id, link_to );
        }
        else {
            printf("\"%s\" [label=\"%s\"]\n", id, label );
        }
        if( parent != "." )printf("\"%s\" -> \"%s\"\n", parent, id );
    }
    END{
        printf("}\n");
    }
    '
}
mainx

shスクリプト(フィルタ版)の使い方例
find d1 | sh basic_find_v4.sh | tee /dev/tty | dot -T svg -o q.svg

環境

Graphviz 2.38.0
ホスト Windows10 COREi7
VM   VirtualBox バージョン 5.2.6 r120293 (Qt5.6.2)
     CentOS Linux release 7.4.1708 (Core)

参考

Graphviz(dot)ノート 基礎編

おわりに

  • ここまで書いてから気付いたのですが、basic_find_v3.shの43行目の"_find \${top_dir} |"と71行目の"| tee /dev/tty | dot -T ${type} -o ${output}"を外せばフィルタになりますね。
    ということで、フィルタ版のbasic_find_v4.shを追加しました。
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1