Path: Computer > Idle > docmd

docmd

docmd is a tool (based on Idle) to search and process (possibly huge) trees of directories. It is only a small program but it can do almost anything to do with searching and manipulating files and directories, either directly by executing commands or by way of building complex batch files.

Following is the (not yet fully completed) documentation for docmd. There is also a zip file with the source code, the compiled versions of the program and the documentation available (the documentation in the zip file should a bit more up-to date).

docmd is released under the MIT licence. It is freeware and comes as-is, without any warranties. If you happen to find a bug or if you'd like to suggest a new feature please drop me a note.

Documentation

This small program is a tool to search and process drives and directories. docmd can do almost anything to do with searching and manipulating files and directories, either directly by executing commands or by way of building complex batch files.

However, with such power come two caveats: the more advanced features of docmd are not always terribly easy to use. And if used carelessly, docmd can do nasty things to your files faster than you can exclaim 'Oh, shit!'

docmd is told what to do via the command line (eg it runs from a CMD.EXE or 4NT.EXE command prompt). If you don't know the first thing about command-line tools, docmd may not be the best tool for you. Then again, as long as you don't use that blasted :EXEC switch… just play with it and experiment with its options and features, nothing untoward will happen.

This are the command line switches and arguments for docmd:

Looks pretty simple, doesn't it? Well, here are the details.

1. Basic usage

docmd accepts one or more filenames or filename patterns and searches a directory or a tree of directories, collecting matching files and/or directories along the way. It then either prints all selected files and/or directories or executes a command for them.

First, the simple switches and arguments:

:i inverts the result of the pattern matching (on a per-pattern basis)
:r the given pattern(s) are regular expression(s); docmd understands Perl-compatible REs
:s recursively traverses the subdirectories of the starting directory (see :path= further down)
:t test mode for :cond= and :cmd= switches (see later); this simply prints the parsed strings and quits
:mode= globally determines whether docmd works with files (f), directories (d) or both (df, that's the default)
pattern is a filename, a wildcard expression or a regular expression

A few examples should make these options a little clearer:

-> This simply prints a plain list of all files and directories in the current directory.

-> This prints a list of win.ini files, for the current directory and all its subdirectories. This can be used, among other things, to search for files (lost or otherwise).

-> This prints a list of all files in the current directory and its subdirectories that are NOT .exe files.

-> This prints a list of all *.exe and *.dll files in the current directory and its subdirectories. Note that the regular expression has to be enclosed in quotes because of the | operator.

-> This also prints a list of all *.exe and *.dll files in the current directory and its subdirectories. However, this list is built from two patterns: first all .exe files are processed and then the .dll files.

-> This prints a list of all files in the current directory and its subdirectories which start with a vowel and have an extension with exactly four, five or six letters.

-> This prints all directories in the current directory.

-> This prints all directories in the current directory and its subdirectories whose name is exactly 15 characters long.

2. The :path= switch

By default, docmd works with the current directory as its starting point. If you want to work with another path, you can use the :path= switch:

-> This prints a list of .ini files, for c:\windows and all its subdirectories.

-> This prints a list of files with a name part of 'setup', for the whole of drive c:\.

3. The :cond= switch

The patterns accepted by docmd (whether simple wildcards or the more complex but also more powerful regular expressions) are a flexible means to select files. However, sometimes files or directories have to be selected according to criteria above and beyond their names. For instance, you might want to produce a list of all files that changed today. Or a list of files with a length of zero bytes…

This can be accomplished with the :cond= switch. This switch accepts a boolean expression which is evaluated for every file and directory that matches the given pattern(s). If the result is true for a given file this file is selected (and printed or executed, if :EXEC is given).

-> Lists all files on drive c:\ with a size of 0. (The :mode=f switch is helpful here because directory entries have a length of zero as well.)

-> Lists all directories on drive c:\ with a date stamp of today.

-> Lists all .txt files on drive c:\ with a date stamp of today *and* a size of 0. Note the double quotes around :cond= which are required because the condition contains spaces.

The boolean expression following :cond= is, in fact, a tiny script. As such it can be much more complex than those few examples show. The expression syntax understood by docmd includes the usual boolean operators 'and', 'or' and 'not', a roster of comparison operators (!=, ==, >, <, ...) as well as subexpressions in parentheses and arithmetic and a few string operators.

3.1 Predefined variables and functions

This is a list of all predefined variables and functions you can use in :cond= expressions:

level current (sub-)directory level (1 is the start level); numerical
file complete matched name of the entry (file or directory); string
drive drive part of the entry (including colon); string
path path part; string
name name part; string
ext extension part (including dot); string
today initialised with today's date (in the form YYYYMMDD); numerical
date date stamp of the entry (YYYYMMDD); numerical
time time stamp of the entry (HHMM, 24 hour-based); numerical
tims time stamp of the entry (HHMMSS, 24 hour-based); numerical
size filesize (0 for directories); numerical
attr the entry's attributes ('acvdehinorpzst'); string
A true if the entry's archive bit is set; boolean
C true if compressed bit is set; boolean
D true if the entry is a directory; boolean
E true if encrypted bit is set; boolean
H true if hidden bit is set; boolean
R true if read-only bit is set; boolean
P true if the entry is a reparse point or a symlink; boolean
S true if system bit is set; boolean
range() function to check a numerical range. It is called with three parameters: range(var,lo,hi), eg "range(size,1024,1024*10)" will select files between 1kb and 10kb in size. Ranges can also be expressed by the syntax "field in (lo,hi)"; eg "size in (1024,1024*10)".
match() function to match a regular expression. It is called with two parameters: match(var,regex), eg "match(name,'\\.(exe|dll)$')"
3.2 Boolean variables

The boolean variables represent attributes of the file or directory currently under scrutiny. They can be combined with the usual boolean operators.

-> This returns true for entries that have either their archive bit or their system and hidden bit set.

3.3 Numerical values

Numerical values can be used in numerical calculations and comparisons. For instance, size and date or time (stamps) can be compared to other numbers:

However, the date and time variables require a word of warning. They are constructed in such a way that comparisons work correctly. However, adding or substracting values won't necessarily produce a sensible result. This means that the following is generally NOT a good idea:

Such an expression will only work for dates where the day is not the first in a month.

Numerical values can be used in range comparisons:

A shortcut for the range function is the in operator:

3.4 String values

String values can be compared with literal strings.

They can also be subjected to a pattern matching operation via the match() function. This function takes a value as first and a regular expression as second parameter. It returns true if the value matches the pattern:

-> This condition is true for all directories that end with the string 'system'.

3.5 :cond= and @FILE

Expressions for :cond= can get pretty complex and grow rather unwieldy. What's more, the need to escape all sorts of special characters on the command line means that the actual string can be diffcult to debug. In this case, you can use a so-called @file for the :cond= switch: simply store the whole expression part of the switch in a file (say 'myexpr.txt'):

-> This reads the file myexpr.txt into a string and uses this string as the parameter for :cond=. There is no need to escape characters that you would normally have to escape on the command line. It's also not necesssary to put the string itself into double quotes (although it will still be necesssary to use quotes for literal strings in the expression).

Assuming that myexpr.txt contains one line:

then the following command will recursively list all .exe files sized between 64KB and 1MB which have the Archive, System and Hidden attribute set:

4. The :cmd= switch

Up till now docmd has simply listed the files and directories it has found. Nice… but it would be even nicer to be able to build whole commands. That's the job of the :cmd= switch. This switch introduces command templates.

docmd supports two different template styles: a simple and a complex style.

4.1 Simple templates

For simple templates, you just append the command template (possibly including a few replacement macros) to the :cmd= switch and docmd does the rest.

-> This prints a template for a batch job that copies all .exe files from the current directory to z:\backup\. Note that the complete :cmd= switch has to be enclosed in double quotes because it contains spaces. Also note that the string '$(file)' in the command is a macro that will be replaced with the actual full filename. The result, for a hypothetical file '.\testthis.exe' would be the string 'copy .\testthis.exe z:\backup\.\testthis.exe'. (The single dot in the target is ignored by the copy command.)

The following replacement macros are defined:

$(file) is replaced with the complete matched filename
$(drive) is replaced with the drive part (including colon)
$(path) is replaced with the path part
$(name) is replaced with the name part
$(ext) is replaced with the extension part (including dot)
$' is replaced with a " (this is gets around possible problems if the whole command string is enclosed in double quotes)

These macros are case-sensitive: $(PATH) won't work.

A second, similar example:

-> The result, for a hypothetical file '.\testthis.exe' would be the string 'copy .\testthis.exe z:\testthis-backup.exe'.

A word of warning: command lines are notoriously fickle when it comes to escaping special characters. Depending on your specific command procossor and settings you will have to escape different character in different ways. Here is an example that doesn't work:

Why? Well, the \ that terminates 'z:\$(path)\backup' is interpreted by the shell as an escape for the following double quote. Escape the escape and all should work:

A second, related word of warning: the :cmd= strings so far given work alright for files/directory names without spaces in them. If you expect spaces (or are generally careful) you should enclose the filenames in double quotes. Either you go through the just-mentioned escape rigmarole or you use the $' macro:

4.2 Complex templates

Complex templates are in fact tiny programs. More precisely, they are expressions in Idle (that is the programming language in which docmd was implemented). I can't give a full explanation about the way Idle and its expressions work as that'd be a long manual in its own right (in fact, it is: see the Idle documentation). However, getting into the basics should cover most applications.

A complex template is an Idle expression (possible a very complicated one) prefixed by a '#' that produces a string. This string, in turn, is simply printed (or executed, if :EXEC is used). Within these limits, you can write whatever you want. Here is a complex template that mimics the simple template given above:

-> Let's deconstruct this command. After the '# prefix that signifies a complex template, the first part ('copy ') is a literal string. The second part (..) is the Idle operator to concatenate two strings. The third part (file) is a predefined variable called file (which contains the full name of the matched file or directory, just like $(file) does for simple templates). Next (..) once more a concatenation, then another literal string (' z:\\backup\\'; note the escaping of the backslash) and again a variable (file). That's it! The string, for our old friend .\testthis.exe would look like this: 'copy .\testthis.exe z:\backup\.\testthis.exe'.

So far, all this looks not much more powerful than simple templates. But with all the facilities of Idle expressions at our fingertips, we can do some interesting things. Check this:

-> This produces a string that renames a file with its reverse name (string.reverse() is a string function that simply returns its reversed argument). For .\testthis.exe the output would look like this: 'ren .\testthis.exe reversed\exe.sihttset'

Here is a list of the predefined variables:

level current (sub-)directory level (1 is the start level); numerical
file complete matched name of the entry (file or directory); string
drive drive part of the entry (including colon); string
path path part; string
name name part; string
ext extension part (including dot); string
today initialised with today's date (in the form YYYYMMDD); numerical
date date stamp of the entry (YYYYMMDD); numerical
time time stamp of the entry (HHMM, 24 hour-based); numerical
tims time stamp of the entry (HHMMSS, 24 hour-based); numerical
size filesize (0 for directories); numerical
attr the entry's attributes ('acvdehinorpzst'); string

And there are a great many standard Idle functions (like string.reverse()) that you can use in complex templates.

One more example. To recursively produce a daily zip file of the contents of the directories in c:\precious (say, as a backup), the following command could be used:

-> Let's say it's Oct 28, 2007 and let's assume c:\precious contains three subdirectories crap, prec1 and veryprec, with crap having two directories of its own (crap1 and crap2). Then docmd would produce the following output:

pkzip c:\precious\crap\crap20071028.zip c:\precious\crap\*
pkzip c:\precious\crap\crap1\crap120071028.zip c:\precious\crap\crap1\*
pkzip c:\precious\crap\crap2\crap220071028.zip c:\precious\crap\crap2\*
pkzip c:\precious\prec1\prec120071028.zip c:\precious\prec1\*
pkzip c:\precious\veryprec\veryprec20071028.zip c:\precious\veryprec\*
4.3 :cmd= and @FILE

Like the :cond= switch, :cmd= support @files. This allows you to store all sorts of complex commands in easily editable files. As with expressions, this also alleviates the need to escape special characters on the command line.

-> This reads the file mycommand.txt into a string and uses this string as the parameter for :cmd=. In the file there is no need to escape characters that you would normally have to escape on the command line. It's also not necesssary to put the string itself into double quotes (although it may well be necesssary to use quotes for individual parts of the command).

Assume that mycommand.txt contains one line:

Then the following command will copy all .exe files found in the current directory to d:\test\:

4.4 :cmd= and :EXEC

The :EXEC switch (yep, must be given ALL CAPS) will execute a given command template (whether simple or complex) once for every file or directory found. If you use :EXEC without also giving a :cmd= switch it will be ignored.

Given the capabilities of the :cmd= switch this allows you perform the most complex operations with a single, (relatively) simple command. However, if you use destructive commands unwisely this power may well come back to haunt you (believe me, I speak from experience).

You have been warned.

5. Global @FILE

Besides the @FILE parameter for the :cond= and :cmd= switches docmd also supports a global @FILE. Such a file may contain any and all switches, parameters and patterns of docmd, with exactly one switch, parameter or pattern per line. If used, this file has to be the first argument on the command line and its contents can be overridden by later command line arguments.

Assume the file myargs has the following content:

:s
:path=c:\
:cond=size>64*1024

-> Lists all .txt files bigger then 64KB in all directories in drive c:\.

-> Lists all .dll files bigger then 64KB in directory c:\windows\system32\ and its subdirectories.

6. Exit code

docmd returns an exit code. This is either the number of files processed (which is 0 or greater than 0) or -1 if one of the help screens was requested or if an error occured.


$updated from: docmd.htxt Fri 25 Apr 2008 16:35:25 thomasl$