Fully commented and explained version of the DIRSIZE program
Copyright 1998, Ted Davis, all rights reserved.
Code is emphasised; comments are not. Each emphasised entry, except for the last few, is separated from its neighbors by comments - if a section of emphasised text is continued on the next line, word wrap is at work: it should be read as one line. Each code line is followed by a blank line.
Suppress display of batch commands in the main program.
@echo off
The branch of the file system to scan is passed as the
only argument to this program - we need tp get a list of
all of the directories and sub-directories: the following
line does that, and puts the list in the file }{.dat, the
name of which is completely arbitrary.
dir %1 /a:d /b /s > }{.dat
Before we can give this file to the DATE command as a series
of bad dates to be echoed back with an error message, we must
add something at the end that DATE will accept - a blank line
which causes DATE to terminate without resetting the date.
echo.>> }{.dat
Now we can give that file to DATE as input and filter DATE's
output to keep only the error message lines that have the
directory name at the end - the output from the FIND filter
goes into the file }{.SRC, the name of which is completely
arbitrary.
date < }{.dat | find "Enter " > }{.src
}{.SRC is going to be used as input to a secondary command
processor through redirection - it is essential that the
secondary command processor terminate, which requires an
EXIT command, so we add one to the end of the file.
echo exit>> }{.src
At this point, we have a file (}{.SRC) that contains a
list of commands that the command processor understands -
provided we make ENTER a valid command (which we will do later)
followed by an EXIT command to make it terminate and return to
this batch program.
The directory name in the }{.SRC file follows the error
message from the DATE command. Unfortunately, the error
message has a different number of words in different operating
systems so the number that eventually will be used to define
the position of the directory name has to be a variable - we
initialize it here to the value required for DOS and Win95.
set arg=4
In order for NT4 to produce a directory listing in the same
format (more or less) as that produced by DOS and Win95, we
have to give the DIR command a special /x switch - the other
operating systems consider that an error, so that too has to
be a variable, initialized here to NULL for DOS and Win95.
set sw=
Redirection arrows, as are used to build the ENTER.BAT file
don't work inside quotes - since it is necessary to put one
just before a redirection arrow, we have to make it appear
only after the redirection operation has been completed.
We have to place, not the quote mark itself, but a reference
to a quote mark that will be substituted when ENTER.BAT
is actually run.
set mark="
To be sure of enough environment space, we have to use
the /e=nnn switch to COMMAND.COM, but this is an error
if the the command processor is CMD.EXE (NT4) - this must
also be a variable, initialized here for DOS and Win95.
set e=/e:1024
Now we see just what operating system we are using - it isn't
necessary to distinguish between DOS and Win95, but it is
necessary to detect NT4. In NT4, the OS environment
variable is set to "Windows_NT", otherwise the variable is
null - if the OS is NT, we need to change the values of the
OS dependent variables. The directory name begins with the
fifth field of the DATE error message in NT4, not the fourth
as in DOS and Win95.
if %os%!==Windows_NT! set arg=5
We need to use the /x switch for NT4,
if %os%!==Windows_NT! set sw=/x
But the /e switch for COMMAND.COM can't be used with NT4's
CMD.EXE, so we clear that variable.
if %os%!==Windows_NT! set e=
Begin building the ENTER.BAT file. This file could have been
created separately and just called, but that means that this
program would require two files, one of which is sure to be lost
as the program is passed around and moved from directory to directory.
Keep in mind that the comments below refer to ENTER.BAT, not to
the main program - they are simply ECHOed from this program to
the ENTER.BAT file. When we need to put a reference to a variable,
rather than the value of the variable, into the file, we have to
double all '%' signs because the main command processor strips
one level of '%' characters off at each pass - we need to leave
the last level for when the ENTER.BAT program actually executes.
When the secondary command processor processes the lines in }{.SRC
that are the augmented error messages, it will attempt to execute
first an internal command called ENTER, and not finding that, will
attempt to execute ENTER.COM, ENTER.EXE, and ENTER.BAT in that
order - we can't provide it with an executable program, but we
can provide it with a batch file named ENTER, which we build below.
ENTER.BAT runs in a secondary command processor, so we need to
suppress display of its commands separately, to be sure that
no command processor will decide to display them - if not
suppressed, they would appear in the output file (RESULT.TXT)
echo @echo off> enter.bat
If the argument that should contain the directory (or part of it)
is null, then the line in }{.SRC that invoked this instance of
ENTER.BAT contained no directory name, and the ENTER.BAT program
should no nothing - if the argument is NULL, just skip to the end.
echo if %%%arg%!==! goto end>> enter.bat
Clear the variable used to hold quote marks if the directory name
contains one or more spaces - directory and file names with
spaces must be quoted when used as arguments to commands, but
directory names for real DOS must never be; if the name does
not contain spaces, it doesn't matter in any of the three OSs.
Note that there is no space between the '=' and the ">>" - this
ensures that the variable will be deleted instead of being set
to a space.
echo set quote=>> enter.bat
Clear the variable that will hold the name of the directory.
If not cleared, the names will just pile up, one at the end of
the previous pile, until we run out of environment space.
echo set dname=>> enter.bat
It is necessary to use a loop to rebuild the directory name
if it contains spaces because it is spread out over several
argument positions. This would not be necessary if there were
a way to quote it before passing it as a bad date to the DATE
command, but there isn't, so we have to rebuild the string.
Note that this is not a general solution because there is no
way to distinguish between spaces and other command line
argument delimiters at this point - the necessary information
has been irrecoverably lost. :loop is the label used as the
target of a GOTO at the bottom of the loop that splices the
elements of the name back together with spaces between elements.
echo :loop>> enter.bat
On the first pass, the only pass if there are no spaces, this line
sets the variable to nothing followed by the string in the Nth
argument, where N is the field in work - initially either the
fourth or fifth, depending on the OS.
echo set dname=%%dname%%%%%arg%>> enter.bat
Move the argument list one place to the left so that the next
argument (field, string) now has the same number as the original one.
echo shift>> enter.bat
If the new field is blank, we have reached the end of the
directory name and need not add a space at the end of the
string in work - we need to jump to the :cont label that
follows the end of the loop.
echo if %%%arg%!==! goto cont>> enter.bat
The next line adds a space, the one referred to in the comment,
to the end of the string in work.
:: Note that there is a space following %%dname%% in the following line
echo set dname=%%dname%% >>enter.bat
If a space has been added, it is necessary to quote the string
when it is later passed to commands as arguments - this is done
by setting the QUOTE variable to a double quote mark.
echo set quote=%%mark%%>> enter.bat
Since we added a space, we have to add another element to the string,
so we jump back up to the top of the loop. This is the last line
in the loop.
echo goto loop>> enter.bat
This places the target of the GOTO :cont command above in its
proper place in the file.
echo :cont>>enter.bat
Here we are ECHOing an ECHO command into the file. This ECHO
command generates the first line of the report for each directory:
the line with the directory name and a bit of a trick - the
string in () contains " bytes" which will allow the lien to pass
through the same FIND filter that passes the line that contains the
size of the directory because that one is filtered on " byte".
echo echo %%dname%% (size in bytes)>> enter.bat
This is a rather complicated bit of gibberish: when written to the
ENTER.BAT file, one level of '%' will be removed, leaving
"if exist %quote%%dname%\*.*%quote% etc." which will be expanded
to (for names with spaces) 'if exist "foo bar'*.*" etc.' when
ENTER.BAT runs ("foo bar" represents any directory name).
Test for whether the directory is empty or not, if not, skip the
directory listing because we already know what it is. This part
doesn't work as expected in NT4 because self and parent directories
(the '.' and ".." entries count as files to the DIR command
(if the directory is not the root, it always contains at least two
items seen by DIR under CMD.EXE).
echo if exist %%quote%%%%dname%%\*.*%%quote%% dir %sw% %%quote%%%%dname%%%%quote%%>> enter.bat
If it is empty (DOS and Win95) just dummy in the response.
echo if not exist %%quote%%%%dname%%\*.*%%quote%% echo 0 file(s) 0 bytes>> enter.bat
This :end label is the target of the bypass GOTO at the head of
the ENTER.BAT file. This ends ENTER.BAT.
echo :end>> enter.bat
Now we feed the script file we built earlier (}{.SRC) to the
secondary command processor - we don't need to know whether it
is COMMAND.COM or CMD.EXE, because the COMSPEC environment
variable contains that part of the command line we wish to
generate here. This is where the business that all of the above
code has lead up to - this executes the DATE error messages followed
by directory names to invoke ENTER.BAT once for each line and pass
it the rest of the line as arguments. The output of the entire
secondary command processor is then filtered to pass only those
lines containing " bytes" (the name line and the line at the
end of the directory listings that contains the number of
bytes in the files in the directory being examined) and written
into the target file, RESULT.TXT.
%comspec% %e% < }{.src | find " bytes" | find /v " free">result.txt
From here out, it's just cleanup: delete the temporary files,
but not the output file, and clear the environment variables
used by the main program (there is no need to clear the ones used
only by ENTER.BAT since they disappeared when the secondary
command processor terminated.
del }{.dat
del }{.src
del enter.bat
set sw=
set arg=
set e=
set mark=