Pruning directory branches
How to delete a directory tree using REXX.
One of the many challenges posed in Down To Earth REXX (William F. Schindler, PerfectNiche, 2000) is to design and write a program that deletes a directory and all of the files and subdirectories under that directory. This article shows my solution, and explains how it works.
We have all used the MD command to make a directory, and RD to remove a directory when working from the command line. But what do we do if we want to trim a whole branch? Sure, we could go to the desktop and use the graphical interface to delete the branch by dragging its root directory to the shredder, but sometimes we want to stay on the command line and work from there.
I wrote a small REXX program, RDD.CMD, that trims a whole branch at once. It uses a couple of RexxUtil functions and recursion. Also, this program could be used to uninstall an application that doesn't have its own UNINSTALL routine.
Stripped of all options and error handling, and most comments, the core of the code looks like this. (I added line numbers to make it easier for me to refer to and explain the program lines. They aren't part of the program.)
1. IF LoadRexxUtil() THEN EXIT 2. ARG target 3. CALL next target /* enable new set of variables */ 4. parent = Directory(..) 5. rc = SysRmDir(target) 6. SAY 'Removed directory' target 7. EXIT 0
8. next: PROCEDURE /* do each level of each branch */ 9. ARG nextdir 10. thisdir = Directory(nextdir) /* files? */ 11. rc = SysFileTree('*.*', 'file.', 'FO',,'-*---') 12. DO f = 1 TO file.0 13. rc = SysFileDelete(file.f) /* delete it */ 14. END f /* subdirs? */ 15. rc = SysFileTree('*', 'dir.', 'DO',,'-*---') 16. DO d = 1 TO dir.0 17. CALL next dir.d /* recurse */ 18. newdir = Directory(..) /* back to parent */ 19. rc = SysRmDir(dir.d) 20. END d 21. RETURN 0 /* a leaf was reached */
Two situations prevent a subdirectory from being deleted: there are directories in the subdirectory we want to delete, or there are files there. In either of these situations, if we are in the parent directory and issue RD with the subdirectory name, the command will fail.
This is a moderately complex directory tree that I will use to explain the function of the program. Although they are not shown here, many or all of these directories could contain files.
D:\ +--COMM461 . +--PROGRAM . ¦ +--DYNFONTS . ¦ +--JAVA ¦ ¦ +--BIN ¦ ¦ +--CLASSES ¦ ¦ +--117 ¦ ¦ +--118 ¦ +--LANG ¦ ¦ +--en_US ¦ ¦ +--DEFAULTS ¦ ¦ +--NETHELP ¦ ¦ +--NETSCAPE ¦ ¦ +--COLLABRA ¦ ¦ +--SHARED ¦ ¦ +--TROUBLE ¦ +--LICENSES ¦ +--PLUGINS ¦ +--WIN16 ¦ +--PLUGINS +--SIUTIL +--USERS +--gsnider +--archive +--Cache +--News +--news.dir
To start off, imagine the current directory is the root of D:\ and COMM461 is one, of perhaps many, first level subdirectories.
Enter the command RDD COMM461 to prune the whole branch shown above. The numbers at the beginning of each line correspond to the line numbers in the program listing above.
Line 1: The LoadRexxUtil PROCEDURE is called to register functions. I used functions instead of command line commands to make error codes available and to eliminate informational messages, and for the extra power of included attribute changes. Line 2: Pick up the name of the subdirectory to be pruned from the command line and assign it to the variable target, in this case COMM461. Line 3: Call the recursive procedure NEXT: and pass it the subdirectory name. Line 8: Begin the procedure and use the PROCEDURE keyword to set up a new set of variables in the procedure. Variables from any earlier recursions are preserved and hidden, including the loop counters. Line 9: Assign the passed value, COMM461, to the variable nextdir. Line 10: Make that subdirectory, in this case COMM461, the current directory. Line 11: Check the directory for Files Only, and assign the name of each file found to the compound variable file.. Each file found will have a numeric index beginning at 1 and file.0 will be set to the number of files found. The system, read only and archive attribute bits of any file found will be cleared so the file can be deleted. Lines 12, 13, 14: Create a loop that deletes all files found. The first time any directory is touched by this procedure any files in the directory will be deleted. SysFileDelete() does not take wildcards. Line 15: Check the directory for subdirectories only, "DO". Clear the attribute bits so the directory can be deleted. Assign the names of any subdirectories to the compound variable dir. and set dir.0 to the number of subdirectories found. On this iteration with the current directory of COMM461
d will be set to 1 dir.1 will be set to PROGRAM, dir.2 will be set to SIUTIL, dir.3 will be set to USERS, dir.0 will be set to 3, the number of subdirectories found. Line 16: Because dir.0 is greater than 0 a loop is set up to handle the 3 directories found. Line 17: Because the loop has been entered, and therefore subdirectories exist, call the NEXT: procedure recursively and pass the name of the next unhandled subdirectory, in this case, PROGRAM. Line 8: The PROCEDURE statement forces the preserving and hiding of all the variable values created by the SysFileTree calls and the position in the directory handling loop and the variable nextdir. Line 9: Assign the passed name, PROGRAM, to a new instance of nextdir variable. Line 10: Change the current directory to PROGRAM. Lines 11, 12, 13, 14: Check for and delete all files in PROGRAM. Line 15: Check for subdirectories in PROGRAM, assign their names to a new instances of dir.. There are 6, DYNFONTS, JAVA, LANG, LICENSES, PLUGINS, WIN16. Set dir.0 to 6. Line 16: Set up a loop to handle these 6 directories. Line 17: Begin to process dir.1, DYNFONTS, by calling NEXTDIR PROCEDURE and passing DYNFONTS to it. Back to line 8. Lines 8, 9: Preserve and hide the file. and dir. variables along with the loop counters. Line 10: Make DYNFONTS the current directory. Lines 11, 12, 13, 14: Check for and delete any files. Line 15: Check for subdirectories. There are none. We have reached a "leaf" directory on this branch. It can be deleted. Lines 16, 17, 18, 19, 20: Because there are no subdirectories in DYNFONTS, this loop is not executed. Line 21: The first RETURN keyword is reached. Any file. variables and the dir.0 variable associated with DYNFONTS are abandoned. The flow of control moves to line 18 for the first time, and the set of variables that was created when PROGRAM was the current directory is re-activated. However this does not change the current directory. That is done in the next step. Line 18: The current directory is set to the parent of the leaf directory. Variable d has a value of 1 and dir.1 has a value of DYNFONTS again, as previous variable names are re-activated. Line 19: DYNFONTS directory is removed. Line 20: The loop ends and control goes to line 16 to see if the loop has counted up to its control value. It has not so dir.2 with a value of JAVA is processed.
From this point, files are checked for and deleted, subdirectories are checked for and BIN and CLASSES are found. The NEXT: procedure is called. (The program is looking for the next leaf directory.) BIN is made the current directory. Files are checked for and deleted. Subdirectories are checked for and none are found. Executing the RETURN keyword reactivates the set of variables created when JAVA was the current directory and BIN is removed. Then CLASSES is processed.
In this way the program makes its way down the whole chain. There may be a simpler way to do this but it hasn't occurred to me yet. Full Listing
The full program listing is available in rdd.cmd