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

#**はじめに**
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](https://qiita-image-store.s3.amazonaws.com/0/234070/efe2a5f0-e04e-e4ab-387b-6424fe076c07.png)


























#####**【shスクリプト】**

~~~bash: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
~~~

#####**【実行】**

~~~bash:スクリプトの起動
sh basic_find_v1.sh d1
~~~
~~~bash:実行結果
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"
}
~~~

~~~bash: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](https://qiita-image-store.s3.amazonaws.com/0/234070/01d0817b-2ec3-dfbd-901a-61039450bb66.png)

#####**【shスクリプト】**

~~~bash: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
~~~

#####**【実行】**

~~~bash:スクリプトの起動
sh basic_find_v2.sh d1
~~~
~~~bash:実行結果
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"
}

~~~


~~~bash: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](https://qiita-image-store.s3.amazonaws.com/0/234070/1c17636d-742e-80df-80c8-bd9a1a4b6dcc.png)




































































#####**【shスクリプト】**

~~~bash: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}

~~~

#####**【実行】**

~~~bash:スクリプトの起動
sh  basic_find_v3.sh d1 png q.png

~~~
~~~bash:実行結果
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"
}

~~~


~~~bash: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スクリプト(フィルタ版)】

~~~bash: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

~~~

~~~bash: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)ノート　基礎編](https://qiita.com/EjiriAkira/items/ee5ad4b67f7caa161a61)


#**おわりに**

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