<HTML>
<HEAD>
<TITLE>Commented Version of DIRSIZE</TITLE>
</HEAD>
<BODY>
<H2 ALIGN="center">Fully commented and explained version of the DIRSIZE program</H2>
Copyright 1998, Ted Davis, all rights reserved.

<BR>Code is <EM>emphasised</EM>; comments are not.  Each <EM>emphasised</EM> entry, except for the last few, is separated from its neighbors by comments - if a section of <EM>emphasised</EM> text is continued on the next line, word wrap is at work: it should be read as one line.<BR><BR>

Suppress display of batch commands in the main program. 
<BR>&nbsp;<EM>@echo off</EM><BR>
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.
<BR>&nbsp;<EM>dir %1 /a:d /b /s > }{.dat</EM><BR>
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.
<BR>&nbsp;<EM>echo.>> }{.dat</EM><BR>
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.
<BR>&nbsp;<EM>date < }{.dat | find "Enter " > }{.src</EM><BR>
}{.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.
<BR>&nbsp;<EM>echo exit>> }{.src</EM><BR>
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.
<BR>&nbsp;<EM>set arg=4</EM><BR>
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.
<BR>&nbsp;<EM>set sw=</EM><BR>
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.
<BR>&nbsp;<EM>set mark="</EM><BR>
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.
<BR>&nbsp;<EM>set e=/e:1024</EM><BR>
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.
<BR>&nbsp;<EM>if %os%!==Windows_NT! set arg=5</EM><BR>
We need to use the /x switch for NT4,
<BR>&nbsp;<EM>if %os%!==Windows_NT! set sw=/x</EM><BR>
But the /e switch for COMMAND.COM can't be used with NT4's
CMD.EXE, so we clear that variable.
<BR>&nbsp;<EM>if %os%!==Windows_NT! set e=</EM><BR>
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)
<BR>&nbsp;<EM>echo @echo off> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>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.
<BR>&nbsp;<EM>echo set quote=>> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>echo set dname=>> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>echo :loop>> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>echo set dname=%%dname%%%%%arg%>> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>echo shift>> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>echo if %%%arg%!==! goto cont>> enter.bat</EM><BR>
The next line adds a space, the one referred to in the comment, 
to the end of the string in work.
<BR>&nbsp;<EM>:: Note that there is a space following %%dname%% in the following line
<BR>&nbsp;<EM>echo set dname=%%dname%% >>enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>echo set quote=%%mark%%>> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>echo goto loop>> enter.bat</EM><BR>
This places the target of the GOTO :cont command above in its
proper place in the file.
<BR>&nbsp;<EM>echo :cont>>enter.bat</EM><BR>
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".
<BR>&nbsp;<EM>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 IF EXIST command 
(if the directory is not the root, it always contains at least two
items seen by IF EXIST under CMD.EXE).
<BR>&nbsp;<EM>echo if exist %%quote%%%%dname%%\*.*%%quote%% dir %sw% %%quote%%%%dname%%%%quote%%>> enter.bat</EM><BR>
If it is empty (DOS and Win95) just dummy in the response.
<BR>&nbsp;<EM>echo if not exist %%quote%%%%dname%%\*.*%%quote%% echo          0 file(s)              0 bytes>> enter.bat</EM><BR>
This :end label is the target of the bypass GOTO at the head of 
the ENTER.BAT file.  This ends ENTER.BAT.
<BR>&nbsp;<EM>echo :end>> enter.bat</EM><BR>
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.
<BR>&nbsp;<EM>%comspec% %e% < }{.src | find " bytes" | find /v " free">result.txt</EM><BR>
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.
<BR>&nbsp;<EM>del }{.dat</EM><BR>
<BR>&nbsp;<EM>del }{.src</EM><BR>
<BR>&nbsp;<EM>del enter.bat</EM><BR>
<BR>&nbsp;<EM>set sw=</EM><BR>
<BR>&nbsp;<EM>set arg=</EM><BR>
<BR>&nbsp;<EM>set e=</EM><BR>
<BR>&nbsp;<EM>set mark=</EM><BR>

</BODY>
</HTML>
