空フォルダの検出削除をする

解決


新米  2009-07-25 05:54:13  No: 35238

よろしくお願いします。
winXP  Delphi6Personalです。
あるフォルダの下にある無数のフォルダ内のファイルをある条件で削除しますが、
階層にかかわらずその下にファイルがないなら削除したいのです。
下の考え方は、まず階層+フォルダパス名のリストを作成する。
次にフォルダリストを階層でソートする。
次に階層の深い順にフォルダを取り出しファイルが一個でもないか調べる。
ただし、隠しファイルはあっても無視する。
ファイルがないとフォルダを削除する。

何とも見苦しいコードですが、もっとスマートな方法はないのでしょうか。
よろしくお願いします。

procedure TForm1.KaraFolderDel(Path: string);
var
    FolderList:TStringList;
    //*************フォルダを削除する***************
    procedure  DeleteRootin(FromFile:string);
    var
        hSHFileOpStruct:  TSHFileOpStruct;
    begin
      with  hSHFileOpStruct do
      begin
          wnd              := 0;
            wFunc            := FO_DELETE;
        pFrom              := PChar(FromFile + #0);
        pTo                := nil;//同じだったら新しい名前を付ける                                                  //FileNameを非表示
          fFlags             := FOF_NOCONFIRMMKDIR or FOF_NOCONFIRMATION;// or FOF_SIMPLEPROGRESS;   //上書きしない
         fAnyOperationsAborted :=   False;
        hNameMappings      := nil;
          lpszProgressTitle  := nil;
      end;
        SHFileOperation(hSHFileOpStruct);
    end;
    //*************フォルダリストを作成する***************
    procedure Build( Path: String; level:integer);
    var
         SearchRec:TSearchRec;
         level2:integer;
         str:string;
    begin
        Path :=  ExcludeTrailingPathDelimiter(Path);  //最後の¥をつける
        if  (FindFirst(Path + '\*.*', faAnyFile, SearchRec) = 0) then
        begin
            try
                repeat
                    if  ((SearchRec.Name <> '.') and (SearchRec.Name <> '..')) then
                    begin
                        if  ((SearchRec.Attr and faDirectory) > 0)  then
                        begin
                            str:= Path + '\'+ SearchRec.Name;
                            FolderList.Add(AnsiQuotedStr(IntToStr(Level), '"')+','+AnsiQuotedStr(str, '"'));
                            level2:=level+1;
                            Build(Path + '\'+ SearchRec.Name, level2)
                        end;
                    end;
                until (FindNext(SearchRec) <> 0);
            finally
                FindClose(SearchRec);
            end;
        end;
    end;
    //***********フォルダ内にフォルダかファイルの有無を調べる******************
    procedure FindFiles(Path:string; var DirList, FileList: integer);
     var
      SearchRec: TSearchRec;
      Attr:integer;
     begin
      Attr:=(faReadOnly +faSysFile+faVolumeID+faDirectory+faArchive);
      Path :=  ExcludeTrailingPathDelimiter(Path);
        if  (FindFirst(Path + '\*.*', Attr, SearchRec) = 0) then
        begin
            try
                repeat
                    if  ((SearchRec.Name <> '.') and (SearchRec.Name <> '..')) then
                    begin
                        if  ((SearchRec.Attr and faDirectory) > 0)  then
                          begin Inc(DirList);break;end
                        else
                          begin
                          if SearchRec.Attr<>faHidden then
                          begin Inc(FileList);break;end;
                          end;
                    end;
                until (FindNext(SearchRec) <> 0);
            finally
                FindClose(SearchRec);
            end;
        end;
     end;
     //******************************
var
  DirList,FileList:integer;
  i:integer;
  StrList:TStringList;
  FolderPath:string;
begin
    FolderList:=TStringList.Create;  //フォルダリストを作成する
    StrList:=TStringList.Create;
    try
       Build(Path,0);
       FolderList.Sorted:=true;//階層の深さでソートする
    //階層の深いフォルダ順にファイルがあるか調べて無ければ削除する
       for i:= FolderList.count-1 downto 0 do
       begin
           StrList.CommaText:=FolderList.Strings[i];
           FolderPath:= StrList.Strings[1];

           DirList:=0; FileList:=0;
           FindFiles(FolderPath, DirList,FileList);
      //ファイルもフォルダもなければ削除する
           if  (DirList=0) and (FileList=0) then
           begin
            DeleteRootin( FolderPath);
           end;
       end;
    finally
        FolderList.Free;
        StrList.Free;
    end;

end;


monaa  2009-07-25 06:40:02  No: 35239

手っ取り早く簡単にやりたいなら
SHFileOperationってのがあります。


ttt  2009-07-25 07:57:31  No: 35240

どうせ検索するのに再帰使ってるんだから

procedure hoge;
begin
  サブディレクトリ検索ループ
  begin
    hoge; ←再帰呼び出し
  end;

  ファイルがなかったらディレクトリを削除する
end;

で深い順から削除になりませんか?
ついでに FileExists('*.*') みたいにワイルドカードが使えるという裏技?があったようななかったような。


新米  2009-07-25 10:28:37  No: 35241

monaaさん、ttt さん有り難うございました。
いろいろトライしましたが、よく理解できませんでした。
すみません。


monaa  2009-07-25 23:13:48  No: 35242

てとりあしとり。
空ではないフォルダの削除サンプル。

//その1(FindFirst) 要:SysUtils.pas
procedure DeleteFolderA(path:string);
var
  sr:TSearchRec;
  pathr:string;
begin
  pathr:= IncludeTrailingPathDelimiter(path);
  if FindFirst(pathr+'*.*', faAnyFile, sr) = 0 then
  begin
    repeat
      if (sr.Name <> '.') and (sr.Name <> '..') then
      begin
        if ((sr.Attr or faDirectory) = sr.Attr)  then
          DeleteFolderA(pathr + sr.Name);
        DeleteFile(pathr + sr.Name);
      end;
    until FindNext(sr) <> 0;
    FindClose(sr);
  end;
  RemoveDirectory(PChar(path));
end;

//その2(SHFileOperation) 要:ShellAPI.pas
procedure DeleteFolderB(path:string);
var
  fs :TSHFileOpStruct;
begin
  FillChar(fs,SizeOf(fs),0);
  fs.Wnd   := Application.Handle;
  fs.wFunc := FO_DELETE;
  fs.pFrom := PChar(path + '\*.*' + #0#0);
  fs.pTo   := 0;
  fs.fFlags:=FOF_SIMPLEPROGRESS or FOF_NOCONFIRMATION;
  SHFileOperation(fs);
  RemoveDirectory(PChar(path));
end;


D  2009-07-25 23:27:27  No: 35243

tttさんの言われるとおり再帰で深い順から削除できると思いますよ。

↓はUnicode対応のAPIを使っていますが、やってることはDelphiのFindFirstなどと同じです。

function TForm1.DeleteEmptyFolder(sPath: WideString): Boolean;
//sPathが空のフォルダなら削除してTrueを返す。
var
  lh_File: THandle;
  lr_Info: TWin32FindDataW;
begin
  Result := True;

  if (sPath[Length(sPath)] <> '\') then begin
    sPath := sPath + '\';
  end;

  lh_File := FindFirstFileW(PWideChar(sPath + '*.*'), lr_Info);
  if (lh_File <> INVALID_HANDLE_VALUE) then begin
    try
      repeat
        if (BOOL(lr_Info.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)) then begin
          //フォルダ。
          if  (lr_Info.cFileName <> WideString('.'))  //カレントフォルダではなく
          and (lr_Info.cFileName <> WideString('..')) //親フォルダでもない
          then begin
            //サブフォルダ。
            if not(DeleteEmptyFolder(sPath + lr_Info.cFileName)) then begin
              //サブフォルダは空ではない。
              Result := False;
            end;
          end;
        end else begin
          //ファイルが存在するのでフォルダは空ではない。
          Result := False;
        end;
        Application.ProcessMessages;
      until not(FindNextFileW(lh_File, lr_Info));
    finally
      Windows.FindClose(lh_File);
    end;
  end;

  if (Result) then begin
    //リストに追加&削除
    MyListBox1.Items.Add(sPath);
    RemoveDirectoryW(PWideChar(sPath));
  end;
end;


D  2009-07-25 23:36:18  No: 35244

あら・・・かぶっちゃった。


新米  2009-07-26 02:55:24  No: 35245

monaa さん、Dさん手取り足取りで教えて頂き有り難うございました。
うまくいきました。
再帰は何度か使ったことがありましたが
ファイルの有無を調べる理屈が理解できていませんでした。
今後も汎用的に使えるコードであり勉強になりました。
有り難うございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加