Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Remove Empty Directories


  • Please log in to reply
33 replies to this topic
jballi
  • Members
  • 1029 posts
  • Last active:
  • Joined: 01 Oct 2005
This script was created from an obscure and infrequent requirement that I have. Every once in a while I have a project where I end up with a bunch of extraneous (and usually) empty directories. It makes it much easier to go through all of the directories and subdirectories if I can find and delete the empty folders.

A couple of notes...

1. This script was designed to run from the command line in the directory where you want the search for empty directories to begin. You can't just double-click on the script and expect it to work the way you want it to. The script could easily be modified to request the starting folder. Since I use this routine infrequently, this enhancement would just slow me down, so... I didn't add it.

2. This routine will not delete an empty directory if a program or process has a hold on it. Windows Explorer does this every once in a while.

3. Directories that only contain empty directories are also deleted.

4. Not all empty directories are evil. Some are required. Please, I'm begging you, please use this script with care.

5. This script contains a bunch of code to display splash messages. Feel free to delete it. You don't really need it. I kept it in because it's a nice-to-have when dealing with large folder structures.

Here's the code. I hope you find it useful:
;RemoveEmptyDirectories.ahk

;-- Confirm
MsgBox 1,,About to remove all empty directories from:`r`n%A_WorkingDir%\
IfMsgBox Cancel
    return

;-- Initialize
$MinimumSplashTime=850


;[========================]
;[                        ]
;[  Build Directory List  ]
;[                        ]
;[========================]
;-- Splash
SplashImage,, W258 B2 ,,Building directory list...
$SplashStartTime=%A_TickCount%
Sleep 10  ;-- Allow splash image to render

;-- Build directory list
$DirectoryList=
Loop *.*,2,1
    $DirectoryList=%$DirectoryList%%A_LoopFileFullPath%`r`n

;-- Sort it
Sort $DirectoryList

;-- Compute elapsed time. Sleep if necessary
$SplashElapsedTime:=A_TickCount - $SplashStartTime
if $SplashElapsedTime<%$MinimumSplashTime%
    {
    $SleepTime:=$MinimumSplashTime - $SplashElapsedTime
    Sleep %$SleepTime%
    }


;[==================]
;[                  ]
;[  Remove Empties  ]
;[                  ]
;[==================]
;-- Splash
SplashImage,, W354 B2 ,,Searching for directories to delete...
$SplashStartTime=%A_TickCount%

;--- Loop until all empty directories have been deleted
$DeleteTotal=0
$LoopCount=0
Loop 99
    {
    $LoopCount:=$LoopCount + 1
    $DeleteCount=0
    Loop parse,$DirectoryList,`n,`r
        {
        if A_LoopField=  ;-- Ignore blank item
            continue
        IfExist %A_LoopField%
            {
            FileRemoveDir %A_LoopField%
            if errorlevel=0
               $DeleteCount:=$DeleteCount + 1
            }
        }
    $DeleteTotal:=$DeleteTotal + $DeleteCount
    If $DeleteCount=0
        break
    }

;-- Compute elapsed time. Sleep if necessary
$SplashElapsedTime:=A_TickCount - $SplashStartTime
if $SplashElapsedTime<%$MinimumSplashTime%
    {
    $SleepTime:=$MinimumSplashTime - $SplashElapsedTime
    Sleep %$SleepTime%
    }

;-- Splash off
SplashImage off


;[===========]
;[           ]
;[  Results  ]
;[           ]
;[===========]
If $DeleteTotal=0
    msgbox No empty directories found
 else
    msgbox %$DeleteTotal% empty directories removed


;-- Return to sender
return


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Decarlo110 also posted a very long script for this. Yours is much shorter and very nicely laid out. However, we can make it even shorter
DelEmpty("C:\backup")

DelEmpty(dir)
{
   Loop %dir%\*.*, 2
      DelEmpty(A_LoopFileFullPath)
   FileRemoveDir %dir%
}
The trick is to try to delete the bottommost subdirectory in the directory tree, then the bottommost subdirectory of its siblings. When there are no more subdirectories in a level, we backtrack to higher levels in the tree. If a branch becomes empty, we prune it. In this order you don't have to sort or re-scan the tree. And the recursive calls ensure this depth-first traversal.

(Loop %dir%\*.*, 2, 1 scans the tree in breadth-first order, which prevents a two-line solution, but as you did, one can repeat this loop until no more directory is deleted. This is much slower, but still can be coded in less than 10 lines.)

Edit: fixed Decarlo110's name

jballi
  • Members
  • 1029 posts
  • Last active:
  • Joined: 01 Oct 2005
Neat trick. I'll just add a counter and it'll be a done deal. Thanks for the feedback.

One other thing I might add will be a check for hidden or system folders. May not want to delete them even if they are empty.

I did a search on this topic b/4 I posted. I'm surprised I didn't find the DeCarlo111 post. Obviously, didn't look hard enough.

Thanks.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
I noticed that this function also deletes the main folder itself if empty, as well as sub-directories. To keep the main folder passed to the function, I made the following change:


DelEmpty_Folder = <<<put the folder name here>>>
DelEmpty(DelEmpty_Folder)

DelEmpty(dir) ; deletes empty sub-directories within the target
{
  Global DelEmpty_Folder
  Loop %dir%\*.*, 2
    {
    DelEmpty(A_LoopFileFullPath)
    }
  If dir !=%DelEmpty_Folder%
    {
    FileRemoveDir, %dir%
    }
}


p.s. This came in really handy for what I've been doing with jballi's playlist making script.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
...or you could just re-create the starting directory. FileCreateDir does nothing if the directory has not been deleted.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Yes I had debated about just re-creating the directory afterwards, but this way seemed a bit neater to me and less of a "fix".

I can't think of any real difference in the outcome of doing it either way, other than that this way it's all contained quite neatly in the function. I doubt there's any big speed difference as the hard disk operations would be the limiting factor in speed I would have thought.

jballi
  • Members
  • 1029 posts
  • Last active:
  • Joined: 01 Oct 2005
...or you call the function only after you begin to loop through the root folder. That way, the root folder is not deleted. Something like this:
Loop %p_UserDefinedDir%\*.*,2
    DelEmpty(A_LoopFileLongPath)

Them be my thoughts...

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Actually I realised an even simpler solution to not deleting the starting directory, which is to change the FileRemoveDir path to %A_LoopFileFullPath%.

However, for the life of me, I can't figure out exactly how "%dir%" ends up containing the currently looped directory, or is this just some feature of "Loop" that I've overlooked?


DelEmpty_Folder =c:\folder_to_delete ; folder to process
DelEmpty(DelEmpty_Folder) 
return

DelEmpty(dir) 
{ 
   Loop %dir%\*.*, 2 
     DelEmpty(A_LoopFileFullPath) 
   FileRemoveDir %A_LoopFileFullPath% 
}


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
%dir% references the function parameter, which does not need the % signs around it when it is in the parameter list or in expressions, but it needs the % signs if it represents a variable.

The starting directory is not deleted with A_LoopFileFullPath or A_LoopFileLongPath, because these loop variables become empty outside of the loop. In deeper recursion levels (not in a loop) they look like global variables (no local value is assigned to them), so they inherit the value of the variable in the calling function. It happens to be the same as dir, the function argument, except at the topmost level. There it refers to a global variable, which normally does not exist.

This is how it should work, which shows how good a job Chris did. However, there is a danger: if there is a global variable A_LoopFileFullPath or A_LoopFileLongPath, the corresponding (empty) directories will be deleted. It happens if you call the DelEmpty function from a loop. The other two proposals don't have this problem.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Laszlo,

Yes I did understand that already about referencing variables with %, etc - the specific part that I couldn't really see, was how that variable is given a new value in this function, since it isn't assigned one directly. (I understand why A_LoopFileFullPath, etc get new values because of the way they are linked to the Loop command, but I can't see how this is the case with "dir")

I followed your explanation about my last suggestion - I guess it's probably best not to do it that way really then. I've been noticing how AutoHotKey likes to "misbehave" in other situations when it doesn't get valid paths, etc, such as traversing through a whole drive instead of aborting (kind of worrying, depending on what file operations you're doing!)

"if there is a global variable A_LoopFileFullPath or A_LoopFileLongPath" - I thought that those variables were only valid inside of loops and couldn't be defined as global anyhow?

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Inside the function the parameter, whatever it was at the call, gets a new name: dir, in our case. It is an implicit assignment, like

(local copy of) dir := parameter (at the function call)

You are right, A_LoopFileFullPath or A_LoopFileLongPath are not allowed to be changed or assigned a value. But they are defined in file loops, when they would look as normal variable. If inside that loop you call a function, it sees them as normal global variables.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Thanks for your explanation, think that cleared things up for me (especially the implicit assignment bit, surprised that isn't mentioned in the manual - not that I could see at least).

Decarlo110
  • Members
  • 303 posts
  • Last active: Feb 12 2006 02:15 AM
  • Joined: 15 Dec 2004

that cleared things up for me (especially the implicit assignment bit, surprised that isn't mentioned in the manual

Purposeful use of any function requires use of labels to manipulate contents of passed parameters. Since it is impractical to require using the same variable name for the function and the caller, and it is impossible for the function writer to know the desired variable names of the function user, the assignment of the function's variable label to the passed parameter contents is naturally required.

---

By the way, the 6-line solution becomes unusable if ever you require a user prompt as to what to do with identified empty directories. Further, code size increases exponentially to handle identification + user prompt. It is possible to add 2 lines to the 6-line solution to identify the folders. There's one small problem with that.. the folders are already deleted and any prompting for further handling becomes meaningless.
1) The Open Source Definition http://www.opensourc...ition_plain.php

2) Intuitive. Logical. Versatile. Adaptable. <>

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

the folders are already deleted and any prompting for further handling becomes meaningless

It is, of course, not true. If the user wants to keep a particular empty directory, we simply re-create it. That is, check the ErrorLevel after FileRemoveDir %dir%. If it shows that the directory was not deleted, we just go on. If ErrorLevel = 0, we pop up a MsgBox,4… to ask the user, if s/he wants to keep it, and if it is necessary: FileCreateDir %dir%. This makes the function 10 lines long. (With FileSetTime and FileSetAttrib you could make them indistinguishable from the original, if needed.)

If you want to display the directory attributes, its creation or last access time, they add an extra line each. You could also update a list of deleted directories, a list of empty directories kept, or all the directories, they also add a line to the script each. Showing this statistics at the end needs another line, but all together the script must be shorter than 20 lines.

You can make it, however, arbitrarily complex with progress bars, GUI's of different colors and font sizes, defining hotkeys for specific actions, saving defaults in .ini files, showing help or "about" windows, etc. These could make sense if you want a uniform look of several tools, or certain hotkeys should perform a similar action in them, or, if you want to sell an application, which should look complex, professional and worth the money. We must not forget, however, that the underlying code is only a handful AHK commands.

Decarlo110
  • Members
  • 303 posts
  • Last active: Feb 12 2006 02:15 AM
  • Joined: 15 Dec 2004

the folders are already deleted and any prompting for further handling becomes meaningless


It is, of course, not true. If the user wants to keep a particular empty directory, we simply re-create it. That is, check the ErrorLevel after FileRemoveDir %dir%. If it shows that the directory was not deleted, we just go on. If ErrorLevel = 0, we pop up a MsgBox,4… to ask the user, if s/he wants to keep it, and if it is necessary: FileCreateDir %dir%. This makes the function 10 lines long. (With FileSetTime and FileSetAttrib you could make them indistinguishable from the original, if needed.)

My understanding of ErrorLevel=0 in this case is that the folder has actually been deleted, or is in the irreversible queue of O/S commands in waiting. Use of FileSetTime and FileSetAttrib can be applied after use of FileGetTime + FileGetAttrib... the puzzle is in the FileName parameter which is to be supplied.... If however, a command physically beneath FileRemoveDir is able to act prior to FileRemoveDir's completion, i was not aware of it. If you can verify your 20-line claim, i will stand corrected.
1) The Open Source Definition http://www.opensourc...ition_plain.php

2) Intuitive. Logical. Versatile. Adaptable. <>