再帰を用いたディレクトリツリー(フォルダツリー)

Copyright(C) 28Dec2014 coskx TNCT

1.ディレクトリツリー


WindowsやLinuxではフォルダ(ディレクトリとも呼ばれる)でファイルを整理している。フォルダの中にフォルダが何重にもなって整理されているような場合,すべてを一覧して見たいことがある。このような時にはツリー表示をすると便利である。

ツリー表示は例えば次のような表示である。フォルダ中にフォルダがあるのでこのような構造は再帰構造と呼ばれる。再帰構造を表現するには,プログラムも再帰になる。
 |-[folder] tnct1
 |  |-[folder] test
 |  |  |-test1.txt
 |  |  |-test2.txt
 |  |  |-test3.txt
 |  |  |-test4.txt
 |  |  |-test5.txt
 |  |
 |  |-test1.jpg
 |  |-test2.jpg
 |  |-test3.jpg
 |  |-test4.jpg
 |
 |-[folder] tnct2
 |  |-test11.txt
 |  |-test12.txt
 |  |-test13.txt
 |  |-test14.txt
 |  |-test15.txt
 |
 |-sample1.c
 |-sample2.c
 |-sample3.c
 |-sample4.c

ツリー表示をするためには,フォルダ内のファイルやフォルダ名を取得する必要がある。
例えばwindowsでは,io.hをincludeした上で _findfirst(),_findnext(),_findclose()の3つの関数を用いることで,フォルダ内のファイルやフォルダ名を取得することができる。
これ以降の記述はWindows向けのものである

ツリー表示をするためには,ツリーを作りたいフォルダ名から,その中のファイル名,フォルダ名を取得し,ファイル名だったら表示するだけ,フォルダ名だっ たら再びその中のファイル名,フォルダ名を所得し,・・・となるので,フォルダ名が見つからなくなるまで再帰で表現できる。

本ページは次の3つで構成されている
(1)最初は,与えられたフォルダ中のファイルとフォルダ名を抜き出すだけのプログラムによって,
   _findfirst(),_findnext(),_findclose()の3つの関数の使い方を学ぶ。
(2)次にファルダ名だけのツリー構造を表示するプログラムにより,再帰の記述を学ぶ。
(3)最後に,ファイル・フォルダのツリー表示を行うプログラムを作成することにする。

デモファイル


2.フォルダ内のフォルダ・ファイル一覧表示


list 2.1は与えられたフォルダ中のファイルとフォルダ名を抜き出すだけのプログラムである。
hFile =
_findfirst( searchstr, &object ) ではserchstrで指示された対象フォルダ内のフォルダやファイルを1つだけ見つけてくる。
"c:\abc\abc\*.*" がserchstrに設定された時は,フォルダc:\abc\abcについて調べることになる。
"*.*"が設定されたら,.exeファイルのあるフォルダが対象となる。
ファイルやフォルダが見つかった場合は_finddata_t構造体のobjectに情報が入る。
また,作業領域のID(おそらく先頭アドレス)を関数の返す値として持ち帰る。
何も見つからない場合は返す値は-1である。
見つかった場合,_finddata_t構造体のobjectには名前や見つかったものの種類の情報が入っている。

(1)object.attrib 構造体であるなどの情報が入っている。
 if ((object->attrib&_A_SUBDIR)==_A_SUBDIR)が真であれば見つかったものは構造体
(2)object.name 見つかったものの名前
 ここでは「.」「..」も見つかるが,これらは特別な名前で,自分自身のフォルダと,
 自分自身の親フォルダを示しているので,これらは表示しない。

もし _findfirst( searchstr, &object ) が-1以外のID値を持ち帰っていたなら,フォルダ内にはまだ他のものがあるかもしれないので,更に探すことになる。
_findnext( hFile, &object ) で2つ目以降を探すことになるが,この関数が0を返してきたら,何かが見つかったことになるので,見つからなくなるまで繰り返しこの関数を呼び出す。

見つからなくなったら,_findclose( hFile ) で,作業領域を開放する。


List 2.1 フォルダ内にあるファイル名とフォルダ名を表示 dir0.c
#include <stdio.h>
#include <io.h>
#include <string.h>

/*ファイルだったら1を,フォルダだったら2をそうでなかったら0を返す*/
int checkObj(struct _finddata_t *object)
{
    if (strcmp(object->name,".")==0) return 0;
    if (strcmp(object->name,"..")==0) return 0;
    if ((object->attrib&_A_SUBDIR)) return 2;
    if ((object->attrib&(_A_HIDDEN|_A_SYSTEM))) return 0;
    return 1;
}

void listDir(char *orgdir)
{
    struct _finddata_t object;
    long   hFile;
    char searchstr[256]="";
    int flag=1;             /*フォルダ内になにかがあったら1,なかったら0*/

    strcpy(searchstr,orgdir);

    if (strlen(searchstr)==0) strcat(searchstr,"*.*");
    else if (searchstr[strlen(searchstr)-1]!='\\') strcat(searchstr,"\\*.*");
    else strcat(searchstr,"*.*");

    //ディレクトリ(フォルダ)・ファイルを探します。
    //printf("searchstr= %s\n",searchstr); //学習用
    if( (hFile = _findfirst( searchstr, &object )) == -1L ) {
        printf( "**カレント ディレクトリにはフォルダは存在しません。\n" );
    } else {
        if (checkObj(&object)==2) printf("[folder] %s\n", object.name);
        else if (checkObj(&object)==1) printf("[ file ] %s\n", object.name);

        // 残りのファイルを探します
        while( _findnext( hFile, &object ) == 0 ){
            if (checkObj(&object)==2) printf("[folder] %s\n", object.name);
            else if (checkObj(&object)==1) printf("[ file ] %s\n", object.name);
        }
        _findclose( hFile );
    }
}

int main(int argc, char *argv[])
{
    char dir[256]="";
    if (argc==2) {
        strcpy(dir,argv[1]);
    }
    if (strlen(dir)!=0) printf("%s\n",dir);
    listDir(dir);
    getchar();
    return 0;
}

実行方法
(1)デモフォルダ中のdir0.exeのアイコンをダブルクリックすると,デモフォルダ内のファイル名と
フォルダ名の一覧を表示する。(dir0.exeが置かれているフォルダの情報を調べることになる。もしdir0.exeを他のフォルダにコピーしてからアイコンをダブルクリックすると,そのフォルダ内のファイル名とフォルダ名の一覧を表示する。)
(2)適当なフォルダアイコンをdir0.exeのアイコンにドラッグ&ドロップすると,そのフォルダ内のファイル名とフォルダ名の一覧を表示する。USBメモリのアイコンをドロップすることもできる。その際,非表示設定のファイルも見えるかもしれない。

List 2.1 では,フォルダ内で見つかった順に表示されている。見つかったフォルダを先に表示し,ファイルを後でまとめて表示するには,検索作業を2回行なえばよい。
フォルダとファイルを分けて表示するプログラムを List 2.2 に示す。


List 2.2 フォルダ内にあるファイル名とフォルダ名を表示 dir.c
#include <stdio.h>
#include <io.h>
#include <string.h>

/*ファイルだったら1を,フォルダだったら2をそうでなかったら0を返す*/
int checkObj(struct _finddata_t *object)
{
    if (strcmp(object->name,".")==0) return 0;
    if (strcmp(object->name,"..")==0) return 0;
    if ((object->attrib&_A_SUBDIR)) return 2;
    if ((object->attrib&(_A_HIDDEN|_A_SYSTEM))) return 0;
    return 1;
}

void listDir(char *orgdir)
{
    struct _finddata_t object;
    long   hFile;
    char searchstr[256]="";
    int flag=1;             /*フォルダ内になにかがあったら1,なかったら0*/

    strcpy(searchstr,orgdir);

    if (strlen(searchstr)==0) strcat(searchstr,"*.*");
    else if (searchstr[strlen(searchstr)-1]!='\\') strcat(searchstr,"\\*.*");
    else strcat(searchstr,"*.*");

    //ディレクトリ(フォルダ)を探します。
    //printf("searchstr= %s\n",searchstr); //学習用
    if( (hFile = _findfirst( searchstr, &object )) == -1L ) {
        printf( "**カレント ディレクトリにはフォルダは存在しません。\n" );
        flag=0;
    } else {
        if (checkObj(&object)==2) printf("[folder] %s\n", object.name);

        // 残りのファイルを探します
        while( _findnext( hFile, &object ) == 0 ){
            if (checkObj(&object)==2) printf("[folder] %s\n", object.name);
        }
        _findclose( hFile );
    }

    //ファイルを探します。
    if( flag ) {
        hFile = _findfirst( searchstr, &object );
        if (checkObj(&object)==1) printf("%s\n", object.name);

        // 残りのファイルを探します
        while( _findnext( hFile, &object ) == 0 ){
            if (checkObj(&object)==1) printf("%s\n", object.name);
        }
        _findclose( hFile );
    }
}

int main(int argc, char *argv[])
{
    char dir[256]="";
    if (argc==2) {
        strcpy(dir,argv[1]);
    }
    if (strlen(dir)!=0) printf("%s\n",dir);
    listDir(dir);
    getchar();
    return 0;
}

実行方法
(1)デモフォルダ中のdir.exeのアイコンをダブルクリックすると,デモフォルダ内のファイル名とフォルダ名の一覧を表示する。(dir.exeが 置かれているフォルダの情報を調べることになる。もしdir.exeを他のフォルダにコピーしてからアイコンをダブルクリックすると,そのフォルダ内の ファイル名とフォルダ名の一覧を表示する。)

(2)適当なフォルダアイコンをdir.exeのアイコンにドラッグ&ドロップすると,そのフォルダ内のファイル名とフォルダ名の一覧を表示する。USBメモリのアイコンをドロップすることもできる。その際,非表示設定のファイルも見えるかもしれない。



3. フォルダだけをツリー表示


対象フォルダ内のフォルダのみ探して,その内部のフォルダを探すという再帰処理によって,フォルダだけをツリー表示する。
再帰関数 void listDir(char *orgdir, int level) がフォルダが見つかるたびに自分自身を呼び出している。
levelは再帰の深さを表している。表示の時にも再帰の深さをわかるようにしている。

searchstr の生成は面倒だが,コメントアウトされている
//printf("searchstr= %s\n",searchstr); //学習用
を有効化すると,どのような文字列を使ってフォルダ内を探しているのかわかる。

List 3.1 フォルダだけをツリー表示 dirtree.c
#include <stdio.h>
#include <io.h>
#include <string.h>

/*ファイルだったら1を,フォルダだったら2をそうでなかったら0を返す*/
int checkObj(struct _finddata_t *object)
{
    if (strcmp(object->name,".")==0) return 0;
    if (strcmp(object->name,"..")==0) return 0;
    if ((object->attrib&_A_SUBDIR)) return 2;
    if ((object->attrib&(_A_HIDDEN|_A_SYSTEM))) return 0;
    return 1;
}

void printLevel(int level, int print)
{
    int i;
    for (i=0; i<level-1; i++) printf(" | ");
    if (print) printf(" |-");
    else printf("\n");
}

void listDir(char *orgdir, int level)
{
    struct _finddata_t object;
    long   hFile;
    char dir[256]="";       /*必要な場合最後に\がついている (再帰のため)*/
    char nextdir[256]="";   /*作業領域*/
    char searchstr[256]=""; /*検索用文字列 最後は*.* */

    if (strlen(orgdir)==0) { /*カレントが対象*/
        /*dirはそのまま""*/
        strcpy(searchstr,"*.*");
    } else if (dir[strlen(orgdir)-1]!='\\') { /*末尾が\でなかった*/
        strcpy(dir,orgdir);
        strcat(dir,"\\");
        strcpy(searchstr,dir);
        strcat(searchstr,"*.*");
    } else { /*末尾が\だった*/
        strcpy(dir,orgdir);
        strcpy(searchstr,orgdir);
        strcat(searchstr,"*.*");
    }

    //ディレクトリ(フォルダ)を探します。
    //printf("searchstr= %s\n",searchstr); //学習用
    if( (hFile = _findfirst( searchstr, &object )) != -1L ) {
        if (checkObj(&object)==2) {
            printLevel(level,1);
            printf( "[folder] %s\n", object.name);
            strcpy(nextdir,dir);
            strcat(nextdir,object.name);
            listDir(nextdir,level+1);
        }

        // 残りのファイルを探します
        while( _findnext( hFile, &object ) == 0 ){
            if (checkObj(&object)==2){
                printLevel(level,1);
                printf( "[folder] %s\n", object.name);
                strcpy(nextdir,dir);
                strcat(nextdir,object.name);
                listDir(nextdir,level+1);
            }
        }
        _findclose( hFile );
    }
    printLevel(level,0);
}

int main(int argc, char *argv[])
{
    char dir[256]="";
    if (argc==2) {
        strcpy(dir,argv[1]);
    }
    if (strlen(dir)!=0) printf("%s\n",dir);
    listDir(dir,1);
    getchar();
    return 0;
}

実行方法
(1)デモフォルダ中のdirtree.exeのアイコンをダブルクリックすると,デモフォルダ内のフォルダ名の一覧を再帰表示する。(再帰表示らしく見えないかもしれない)(
dirtree.exeが置かれているフォルダの情報を調べることになる。もしdirtree.exeを他のフォルダにコピーしてからアイコンをダブルクリックすると,そのフォルダ内のフォルダ名の一覧を再帰表示する。)
(2)適当なフォルダアイコンをdirtree.exeのアイコンにドラッグ&ドロップすると,そのフォルダ内のフォルダ名の一覧を再帰表示する。
USBメモリのアイコンをドロップすることもできる。

PCのセキュリティの設定によっては,実行時にエラーになることがある。
その場合は,dirtree.exeのアイコンをマウス右ボタンプレスで,「管理者として実行」で起動する。
これは(1)についての対処方法で,(2)については対処できるが,手間がかかる。

4.フォルダの完全ツリー構造


List 3.1 で表示しなかった,フォルダ内のファイル名も含めてすべて再帰表示する。

List 4.1 フォルダの完全ツリー構造 tree.c
#include <stdio.h>
#include <io.h>
#include <string.h>

/*ファイルだったら1を,フォルダだったら2をそうでなかったら0を返す*/
int checkObj(struct _finddata_t *object)
{
    if (strcmp(object->name,".")==0) return 0;
    if (strcmp(object->name,"..")==0) return 0;
    if ((object->attrib&_A_SUBDIR)) return 2;
    if ((object->attrib&(_A_HIDDEN|_A_SYSTEM))) return 0;
    return 1;
}

void printLevel(int level, int print)
{
    int i;
    for (i=0; i<level-1; i++) printf(" | ");
    if (print) printf(" |-");
    else printf("\n");
}

void listDir(char *orgdir, int level)
{
    struct _finddata_t object;
    long   hFile;
    char dir[256]="";       /*必要な場合最後に\がついている (再帰のため)*/
    char nextdir[256]="";   /*作業領域*/
    char searchstr[256]=""; /*検索用文字列 最後は*.* */
    int flag=1;             /*フォルダ内になにかがあったら1,なかったら0*/

    if (strlen(orgdir)==0) { /*カレントが対象*/
        /*dirはそのまま""*/
        strcpy(searchstr,"*.*");
    } else if (dir[strlen(orgdir)-1]!='\\') { /*末尾が\でなかった*/
        strcpy(dir,orgdir);
        strcat(dir,"\\");
        strcpy(searchstr,dir);
        strcat(searchstr,"*.*");
    } else { /*末尾が\だった*/
        strcpy(dir,orgdir);
        strcpy(searchstr,orgdir);
        strcat(searchstr,"*.*");
    }

    //ディレクトリ(フォルダ)を探します。
    //printf("searchstr= %s\n",searchstr); //学習用
    if( (hFile = _findfirst( searchstr, &object )) == -1L ) {
        printLevel(level,1);
        printf( "**空です\n" );
        flag=0;
    } else {
        if (checkObj(&object)==2) {
            printLevel(level,1);
            printf( "[folder] %s\n", object.name);
            strcpy(nextdir,dir);
            strcat(nextdir,object.name);
            listDir(nextdir,level+1);
        }

        // 残りのファイルを探します
        while( _findnext( hFile, &object ) == 0 ){
            if (checkObj(&object)==2){
                printLevel(level,1);
                printf( "[folder] %s\n", object.name);
                strcpy(nextdir,dir);
                strcat(nextdir,object.name);
                listDir(nextdir,level+1);
            }
        }
        _findclose( hFile );
    }

    //ファイルを探します。
    if ( flag==1 ) {
        hFile = _findfirst( searchstr, &object );
        if (checkObj(&object)==1) {
            printLevel(level,1);
            printf( "%s\n", object.name);
        }

        // 残りのファイルを探します
        while( _findnext( hFile, &object ) == 0 ){
            if (checkObj(&object)==1){
                printLevel(level,1);
                printf( "%s\n", object.name);
            }
        }
        _findclose( hFile );
    }
    printLevel(level,0);
}

int main(int argc, char *argv[])
{
    char dir[256]="";
    if (argc==2) {
        strcpy(dir,argv[1]);
    }
    if (strlen(dir)!=0) printf("%s\n",dir);
    listDir(dir,1);
    getchar();
    return 0;
}
実行方法
(1)デモフォルダ中のtree.exeのアイコンをダブルクリックすると,デモフォルダ内のファイル名とフォルダ名の一覧を再帰的に表示する。 (tree.exeが置かれているフォルダの情報を調べることになる。もしtree.exeを他のフォルダにコピーしてからアイコンをダブルクリックする と,そのフォルダ内のファイル名とフォルダ名の一覧を再帰的に表示する。)
(2)適当なフォルダアイコンをtree.exeのアイコンにドラッグ&ドロップすると,そのフォルダ内のファイル名とフォルダ名の一覧を表示する。USBメモリのアイコンをドロップすることもできる。その際,非表示設定のファイルも見えるかもしれない。)

PCのセキュリティの設定によっては,実行時にエラーになることがある。
その場合は,dirtree.exeのアイコンをマウス右ボタンプレスで,「管理者として実行」で起動する。
これは(1)についての対処方法で,(2)については対処できるが,手間がかかる。