Fundamentally, the complexity of the program is required only to make the output clean and neat:
for %a in (exe com bat pif) do dir *.%a
is the trivial command line way of getting multiple DIR listings (use "%%" instead of "%" in a
batch file) but there if there are more than a screen full of output lines,
including the headers and footers, then all but the last part will scroll
before you have a chance to read it - and just adding a pipe to MORE doesn't
help, nor is it possible to specify files with no extension. You also get
multiple headers and footers.
At each step in the process that involves either redirection or pipes, or substitution of a variable certain parts of a FOR command, it is always necessary to isolate the remaining code by CALLing a batch file that contains it. Here, all the code is in the same file, so the CALLs are to %0 (the parent program's name) and are combined with variable jumps to the correct section of the file. The reasons are that redirection applies to the first command on the line and FOR commands are parsed only once, with only the FOR variable itself being subsisted when the command actually runs.
DIRX.BAT
Turn off echoing of commands to the console - they would get mixed
in with the output
@echo off
Test for a recursive call to the program and vector to the given label
if !%1==!}{ goto %2
Clear the environment variables we are going to use to avoid any
possible confusion from left over variables - note that there is nothing
after the '=' except the end of line marker
set oldpath=
set exts=
If there is no second argument (whether there is a first or not) we
will have to use the default extension list since none was provided or
named
if !%2==! goto default
Here begins a bit of boilerplate code to normalize the argument to
upper case for string comparison: save the path for restoration later,
then use the path command to do the conversion of a string that is
free of delimiters because it is a single command tail argument
set oldpath=%path%
path %2
The tests for named types begins here: the second argument is in the
PATH variable in upper case.
if %path% == EXEC set exts=COM;EXE;BAT;PIF
if %path% == IMAGE set exts=GIF;JPG;TIF;BMP;WPG
You can add your own lists here - use the above semicolon
delimited format
:: tests for additional types go here
We cleared the EXTS variable earlier and set it only if the second
argument matched one of the known type names - if it is still empty, then
the argument was not the name of a known type and we need to treat it as
an element in a list of extensions (the list might be empty, but that
doesn't matter here)
if not !%exts%==! goto continue
:default
The label says "default", but that comes later - first we set the EXTS
variable to a semicolon delimited list built up from the second through
ninth command line arguments (extra semicolons don't matter). It is
necessary to delimit the list with semicolons because the PATH command
will barf if its argument isn't what it considers a sane PATH list
set exts=%2;%3;%4;%5;%6;%7;%8;%9
:continue
If we used the PATH before, clean up by restoring it to its original
state. This may not be absolutely necessary but it is good insurance
against loosing the PATH setting if the program abends
if not !%oldpath%==! set path=%oldpath%
This is where we actually define the default extensions list if
no list is provided
if !%2==! set exts=COM;EXE;BAT;PIF
So far this has been pretty tame stuff - here's where it gets ...
shall we say "interesting": the idea is to combine the outputs of
several (filtered) DIR commands into one stream and then to pipe
that through the MORE filter to display it by pages. That is non-trivial.
The solution is to put all the DIR outputs in a secondary command
processor and to pipe the output of that to MORE. To do that,
we restart the batch file in the secondary command processor and jump
to the subprogram (:pass2) that handles getting the DIR listings and
pass it the first argument to the original invocation, which is the
drive:directory
command /e:1024 /c %0 }{ pass2 %1 | more
We get here after the main part of the program has finished - this
is the cleanup code: it deletes the environment variables we used int
the base command processor - the ones used in the secondary command
processor disappeared when it did
set oldpath=
set exts=
Obviously we don't want to go through the subprograms code again -
we just want to terminate the program, so we jump to the :end label
goto end
This is the outer subprogram - it calls an inner subprogram once
for each extension in the list
:pass2
Here we clear the environment variables to be used in the secondary
command processor - if they already exist, they will still exist in the
base command processor when this one terminates. There is no need to
clear them when done since the changes we make will disappear when the
secondary command processor does
set base=
set wip=
See the comments above about using PATH as a toupper() function
set oldpath=%path%
This is where the semicolons in the list become important: there
can be many items in the list and they have to be delimited in such
a way that PATH will accept them and that FOR will see the individual
elements as separate items in its ( list ). The semicolon is the only
character that meets both requirements
path %exts%
set exts=%path%
set path=%oldpath%
Note that on this pass, the first argument is the recursion flag,
the second is the target label, and the third is what was originally
the first argument to the program. It goes into an environment variable
so that it doesn't get lost in all the recursion. Since this variable
is in the secondary environment but not the master, it will vanish
when the secondary copy of COMMAND.COM terminates - we don't need to
clear it specifically during the cleanup
set base=%3
FOR executes its DO clause once for each item in the delimited list,
that is, once for each extension in the list. It calls the program with
the usual recursion marker, the label to jump to, and the current item
in the list
for %%a in (%exts%) do call %0 }{ pass3 %%a
Since this is the end of the subprogram, we need to skip over the
other subs and go straight to the end of the file, which acts like
a RETURN command
goto end
The second level sub begins here - it determines if there is any need
to do a DIR for the current extension by testing for something with it as
an extension
:pass3
Copy the extension to the environment so it won't get lost
set wip=%3
Null extensions are a special case - they are the only exception to the
rule that extensions to be processed have from one to three characters and
type names have four or more. A blank cannot be recognized by earlier code,
so we use "NULL" as a placeholder - it gets resolved to a real blank here.
This is the reason that the test can't be used in the FOR statement itself -
the %%a FOR variable has to be changed
if %3 == NULL set wip=
This line test for something to do a DIR on - the test can't be
combined with the DIR command because the output, of which there isn't
any, from the test, not the DIR would be piped through the filters
if exist %base%\*.%wip% call %0 }{ pass4 %3
goto end
The innermost of the subprograms: the actual DIR command. It has to be
buried so many layers deep because there is no other convenient way to combine
the various elements: pipe through MORE, FOR with an eventual redirection,
the sanity test for something with the extension, and the DIR command with
its own output pipes
:pass4
The idea here is to do the DIR, eliminating the directory entries
with the /a:-d switch and the stuff in the DIR output that isn't a file
entry by passing only lines with '-' (from the date stamp) but not '\'
(from the "Directory of ..." line). It would appear that only the '-' filter is
actually needed, but '-' can appear in volume labels, and while '\' can't
dir %base%\*.%wip% /a:-d | find "-" | find /v "\"
:end
** Copyright 1995,1996, Ted Davis - all rights reserved **
Input and feedback from readers are welcome. NOTE: the subject of the message must contain the word "batch" for the message to get past the spam filter.
Back to the Table of Contents page
Back to my personal links page - back to my home page