Reginald offers a built-in Function to list the contents of a directory (ie, list the names of all files in the directory, as well as the names of any sub-directories inside of that directory). This Function is called MATCHNAME.

Note: In Windows terminology, a directory is a "Folder".

Because MATCHNAME supports wildcards, it can list only those names that fit a certain pattern (such as only those files whose names end with a .HTM extension).

But MATCHNAME also offers additional filtering options if you desire them. For example, MATCHNAME can list only those files/directories of a certain type (such as only those files/directories that have their archive bit set, or files/directories that are marked as "hidden", etc).

So, you have a bit of flexibility to tailor what files or directories are listed, thus saving you a lot of work weeding through information that you don't need.

Sometimes, when you're listing the contents of a directory, you'd like more information than simply the file or sub-directory's name. For example, if you're planning to read the contents of each file using CHARIN(), you'll probably want to know the file's size too. For this reason, MATCHNAME can also (optionally) return other information about each listed file/directory, including its size, the date it was last modified, its attributes, etc. Sure, you could use CHARS() or STREAM's QUERY SIZE command to get a file's size. But if you need to list the contents of a directory anyway, you may as well get all of the information you need via one function call (instead of making more work for yourself), and also, it can be faster to get all of the information with one call.

Another use of MATCHNAME is to check for the existance of a file with a particular name (in a particular location), and perhaps get some of the above information about it.


Listing the entire contents of a directory

A directory may contain many files and sub-directories.

When you call MATCHNAME once, it returns the name (and optionally, additional information) about only one file or sub-directory inside of the directory you're listing. MATCHNAME doesn't return a whole bunch of names all at once. Needless to say, if you want to list the entire contents of that directory, or list a group of files/sub-directories that match certain criteria, then you've got to call MATCHNAME numerous times (ie, once for each matching file/sub-directory). You may be thinking "That could entail a lot of calls to MATCHNAME.". Yes, it could, but you're going to call MATCHNAME inside of a loop. The first time through your loop, MATCHNAME will return the name (and additional information) about the first file or sub-directory that matches your criteria. The second time through the loop, MATCHNAME will return the next file or sub-directory that matches your criteria. Etc. So, the logic for your loop will look like this:

1) Get the name of the next file or sub-directory that matches your criteria. If there isn't any, then end the loop.

2) Do something with that name. (ie, You wanted the name. Now do something with it, dummy).

3) Repeat from step 1.

You may be wondering, "How will I know when there are no more files or sub-directories that match my criteria?". MATCHNAME will tell you when that happens.

Let's begin with a hypothetical example. Assume you wish to list the entire contents of a directory named MyDir2 which is inside of a directory named MyDir on the C drive. In other words, you wish the list the names of all files and sub-directories inside of C:\MyDir\MyDir2. For our simple example, the only thing that we'll do with each name is SAY it.

The first arg passed to MATCHNAME is a search number. This is needed only if you plan to list the contents of more than one directory simultaneously. We'll examine this later when we talk about recursively listing sub-directories. For right now, you can omit this arg.

MATCHNAME returns the next file/sub-directory's name in a variable of your choosing. The second arg is the name of the variable you want MATCHNAME to use. (Remember to put this in quotes so that REXX doesn't pass that variable's value instead). For example, let's say that you wish the name to be returned in the variable MyVar. You would therefore pass 'MyVar' to MATCHNAME.

The third arg is the template. This should contain the name of the directory that you wish to query. Its name should end with a \. If you wish to limit the listing to only those files and/or sub-directories whose names have something in common (for example, they all end with a .TXT extension), then you'll also append a wildcard template to the directory name. Conversely, if you simply want to list all files and/or sub-directories regardless of their names, then do not specify any wildcard template. For our first example, we'll list everything, so we won't specify any wildcard template. We'll simply pass a template of C:\MyDir\MyDir2\. (Note the backslash at the end of the directory).

You need to specify the fourth arg only if you want to limit the listing to those files and/or sub-directories that have certain attributes such as read-only, or are system files, or are hidden files, or are marked as archived, etc. If you omit this arg, MATCHNAME lists only files, not sub-directories. One of MATCHNAME's choices of attributes is to also list sub-directory names. So if you want to list sub-directory names, as well as file names, then you do need to pass this arg and specify the 'D' attribute (as well as other attributes if you don't want to exclude those others). For our example, we want to list everything, so we'll specify an arg that includes all possible attributes, 'DSNHCRAT'.

You need to specify the fifth arg only if you want more information returned than simply the name, for example, if you want the size, or last modified date, etc. For our example, we can omit this arg since we only need the name returned. Later, we'll examine using this arg.

Ok, we now know what to pass to MATCHNAME in order to list the names of all files and sub-directories inside of C:\MyDir\MyDir2. Let's now talk about what MATCHNAME returns.

If MATCHNAME can find another file or sub-directory that matches our criteria, then MATCHNAME will set our variable (MyVar in this example) to that file or sub-directory's name. MATCHNAME will also return an empty string.

If MATCHNAME can not find another file or sub-directory that matches our criteria, then MATCHNAME will return an error message (ie, not an empty string).

Here's our example to list the names of all files and sub-directories inside of C:\MyDir\MyDir2:

/* Repeat until we finally break out of the loop ourselves. */
DO FOREVER

   /* Have MATCHNAME return the next name. */
   result = MATCHNAME(, 'MyVar', 'C:\MyDir\MyDir2\', 'DSNHCRAT')

   /* Did MATCHNAME return a name? If not, we're done. */
   IF result \= "" THEN LEAVE

   /* Now SAY that name. */
   SAY MyVar

END
A DO FOREVER loop can be difficult to break out of if you have other loops inside of it, so let's rewrite our above example to use a DO UNTIL loop as so:
/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \= ""

   /* Have MATCHNAME return the next name. */
   result = MATCHNAME(, 'MyVar', 'C:\MyDir\MyDir2\', 'DSNHCRAT')

   /* Did MATCHNAME return a name? */
   IF result = "" THEN DO

      /* One caveat: Do not change the value of 'result' in
       * this inside loop, or you may inadvertently mess up
       * the DO UNTIL.
       */

      /* Now SAY that name. */
      SAY MyVar

   END /* MATCHNAME returned a name. */

END
Note: If the files/sub-directories you wish to query are in the current directory (whatever it may be), then you can omit the third arg to MATCHNAME.


Getting additional information

In the above example, we listed all file and sub-directory names. But you may be wondering, "How do I distinguish the sub-directories from the files?". You'll discover how below.

You can ask MATCHNAME to return some additional information about each file/sub-directory. Among the information you can have returned is a file/directory's size, its last modified date, its full name (ie, MyVar would include the drive name and parent directory names on the returned name), and its attributes.

You can pick and choose which information you'd like returned. For example, you could have all of the above information returned. Or, you can ask for only one or two of the above pieces of information. The fifth arg you pass to MATCHNAME indicates which information you would like returned. For example, to return the name of a file/sub-directory, as well as its size, then pass 'NS'.

MATCHNAME returns the additional information in compound variables, where a tail is added to the variable name you passed to MATCHNAME. We passed the variable name MyVar to MATCHNAME. So, if we want the size returned, then MATCHNAME adds a tail name of 0 to MyVar and returns the size in MyVar.0. If we ask for the last modified date to be returned, then MATCHNAME adds a tail name of 1 to MyVar and returns the date in MyVar.1. Etc.

Here we display the name and size of each file and sub-directory on the C:\ drive:

/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \= ""

   /* Have MATCHNAME return the next name, and size. */
   result = MATCHNAME(, 'MyVar', 'C:\', 'DSNHCRAT', 'NS')

   /* Did MATCHNAME return a name? */
   IF result = "" THEN DO

      /* One caveat: Do not change the value of 'result' in
       * this inside loop, or you may inadvertently mess up
       * the DO UNTIL.
       */

      /* Now SAY that name. */
      SAY MyVar

      /* SAY the size. */
      SAY "Size =" MyVar.0

   END /* MATCHNAME returned a name. */

END
You may notice one thing. The directory sizes are blank. This is because, for a directory, MATCHNAME considers it not to have an inherent size, so MATCHNAME sets MyVar.0 to an empty string. So, that is how you can tell if a name returned is a file or directory:
DO UNTIL result \= ""

   result = MATCHNAME(, 'MyVar', 'C:\', 'DSNHCRAT', 'NS')

   IF result = "" THEN DO

      /* Is it a directory (ie, the size = "")? */
      IF MyVar.0 = "" THEN SAY MyVar "is a directory."

      /* Must be a file. */
      ELSE SAY MyVar "is a file, and its size =" MyVar.0

   END

END

Listing only files with a certain name pattern

Let's say that we wish to list only those files in C:\Windows whose names end with a .TXT extension. For this, we need to append a wildcard template of *.TXT to our directory name. In other words, for our third arg to MATCHNAME, we'll pass C:\Windows\*.TXT. (And since we no longer want to list directory names, we can omit the fourth arg. By omitting this arg, MATCHNAME will simply return any files whose names match our wildcard template, regardless of their attributes).

/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \= ""

   /* Have MATCHNAME return the next filename that ends with .TXT. */
   result = MATCHNAME(, 'MyVar', 'C:\Windows\*.txt')

   /* Did MATCHNAME return a name? */
   IF result = "" THEN DO

      /* One caveat: Do not change the value of 'result' in
       * this inside loop, or you may inadvertently mess up
       * the DO UNTIL.
       */

      /* Now SAY that name. */
      SAY MyVar

   END /* MATCHNAME returned a name. */

END
Note: If the files/sub-directories you wish to query are in the current directory (whatever it may be), then you can omit the directory name, and simply supply the wildcard template.
DO UNTIL result \= ""

   /* List the files that end with .TXT in the current directory. */
   result = MATCHNAME(, 'MyVar', '*.txt')

   IF result = "" THEN DO

      SAY MyVar

   END

END

Listing only files with a certain attribute

Let's say that we wish to list only those files on the C:\ drive that are system files (ie, have the system attribute set). For this, we need to specify the fourth arg, but specify only the 'S' attribute.

/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \= ""

   /* Have MATCHNAME return the next name with the
    * system attribute set.
    */
   result = MATCHNAME(, 'MyVar', 'C:\', 'S')

   /* Did MATCHNAME return a name? */
   IF result = "" THEN DO

      /* One caveat: Do not change the value of 'result' in
       * this inside loop, or you may inadvertently mess up
       * the DO UNTIL.
       */

      /* Now SAY that name. */
      SAY MyVar

   END /* MATCHNAME returned a name. */

END
You can specify multiple attributes. For example, below we ask MATCHNAME to return all files that have either (or both) the system and/or archive attributes.
/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \= ""

   /* Have MATCHNAME return the next name with the
    * system and/or archive attribute set.
    */
   result = MATCHNAME(, 'MyVar', 'C:\', 'SA')

   /* Did MATCHNAME return a name? */
   IF result = "" THEN DO

      /* One caveat: Do not change the value of 'result' in
       * this inside loop, or you may inadvertently mess up
       * the DO UNTIL.
       */

      /* Now SAY that name. */
      SAY MyVar

   END /* MATCHNAME returned a name. */

END
Note that any file that has either of those attributes set (or both attributes set) will be returned. The only names not returned are any files with neither of those attributes set, nor any directory names (because we omitted the 'D' attribute). If you need to know which specific attributes a returned file has set, then you should ask MATCHNAME to also return information about the attributes, and you can check that information yourself.

Of course we could list only the sub-directories (ie, no files) on the C:\ drive by specifying only the 'D' attribute.


Error messages

When MATCHNAME finds no more files/sub-directories that match your criteria, it returns the string DONE. In our above examples, we rely upon MATCHNAME to return something other than an empty string to end our loop.

But there may be other problems that could cause MATCHNAME to return such a string. For example, perhaps there is some sort of disk corruption that causes MATCHNAME not to be able to list any more names. In this case, the string MATCHNAME returns would not be DONE, but rather, some error message that tells you exactly what went wrong. So, when finishing the loop, it is best to always check the last return from MATCHNAME to see if it is DONE. If not, then MATCHNAME did not complete its listing of the directory due to some error, and you may wish to display this error message to indicate why.

Here is an example where we list all files in C:\Windows whose names end with a .TXT extension, and properly report any error.

/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \= ""

   /* Have MATCHNAME return the next filename that ends with .TXT. */
   result = MATCHNAME(, 'MyVar', 'C:\Windows\*.txt')

   /* Did MATCHNAME return a name? */
   IF result = "" THEN DO

      /* One caveat: Do not change the value of 'result' in
       * this inside loop, or you may inadvertently mess up
       * the DO UNTIL.
       */

      /* Now SAY that name. */
      SAY MyVar

   END /* MATCHNAME returned a name. */

END

/* Did MATCHNAME complete its listing? If not, display why. */
IF result \== "DONE" THEN SAY result


Aborting a MATCHNAME loop

If you're looping around MATCHNAME to list the contents of some directory, and you wish to abort that listing (ie, before MATCHNAME returns DONE), then you need to abort the search by calling MATCHNAME once without passing the name of your return variable. (You can simply call MATCHNAME without any args).

Here is an example where we list all files in C:\ whose names end with a .BAK extension, until finding one named 'autoexec.bat'. We then abort our listing at that point.

/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \== ""

   /* Have MATCHNAME return the next filename that ends with .BAT. */
   result = MATCHNAME(, 'Info', 'C:\*.bak')

   /* No error? If so, check whether it's "autoexec.bat". */
   IF result == "" && Info == 'autoexec.bat' THEN DO

      /* Abort the search. */
       MATCHNAME()

      /* End the loop. */
      result = 'DONE'

   END /* MATCHNAME returned "autoexec.bat". */

END

/* Did MATCHNAME complete its listing? If not, display why. */
IF result \== "DONE" THEN SAY result

Listing a directory tree (ie, Recursive listing)

In those examples where we asked MATCHNAME to list sub-directories, you'll notice that MATCHNAME doesn't list the contents of each sub-directory (ie, all of the files and additional sub-directories that may be inside of each sub-directory). In other words, MATCHNAME doesn't list an entire directory tree. If you want to list the additional contents of a sub-directory, then you need to start another MATCHNAME loop with a different search number (ie, the first arg passed to MATCHNAME). Because you don't know how many sub-directories you may encounter (and note that there may be further sub-directories inside of each sub-directory), the best technique is to use recursive instructions. All this means is that you put your MATCHNAME loop inside of a REXX function. And whenever you encounter a sub-directory, you call this function again, passing it a new template and a new search number. Your function will obviously need to be written accept those two arguments.

Here we have a function named ListAllFiles. It is passed the search number, and name of a directory to search. It displays the names of all files. And whenever ListAllFiles encounters a sub-directory, it dives into that sub-directory to display the names of any files inside of that sub-directory too. ListAllFiles does this by calling itself -- passing a new search number, and the name of the sub-directory. In other words, whenever it encounters a sub-directory, it starts up another, simultaneous MATCHNAME loop to query that new directory. (So that is why we need different search numbers). It is also important to use the PROCEDURE keyword.

Because we want MATCHNAME to report any sub-directory names, we are going to specify the "D" attribute. We need to specify whatever other attributes we wish. Let's assume that we don't care about hidden, system, or temp files, so we'll specify "DNCRA".

Because we need to distinguish sub-directory names from file names, we need the "S" option to get the size. (And of course, we want the name, so we specify the "N" option too).

We'll write this function so that it returns an empty string if it is successful. If there is a problem, it will abort all MATCHNAME loops and return that error message.

ListAllFiles: PROCEDURE

/* Create the template we'll pass to MATCHNAME. We need
 * to append a backslash to the directory name.
 */
template = ARG(2)
IF template = "" THEN template = DIRECTORY()
template = template || '\'

/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \== ""

   /* Have MATCHNAME return the next file/sub-directory name. */
   result = MATCHNAME(ARG(1), 'Info', template, 'DNCRA', 'NS')

   /* An error? If so, end the loop. */
   IF result \= "" THEN LEAVE

   /* Display the file/sub-directory name, indented per this level. */
   SAY COPIES(" ", ARG(1) - 1) || Info

   /* Is this a sub-directory? If so, we need to call ListAllFiles
    * again, passing a new search number (ie, we'll increment the
    * number we're using right now), and the name of this sub-directory.
    */
   IF Info.0 = "" THEN DO

      result = ListAllFiles(ARG(1) + 1, template || Info)

      /* An error? If so, end the loop, but first abort
       * our own MATCHNAME loop. We need to pass the search
       * number we're using here to abort the correct listing.
       */
      IF result \= "" THEN DO
           MATCHNAME(ARG(1))
          LEAVE
      END

   END /* A sub-directory. */

END /* Get the next file/sub-directory name. */

/* Did MATCHNAME complete its listing? If not, return an error
 * message. Otherwise, return an empty string.
 */
IF result == "DONE" THEN result = ""
RETURN result
And here's an example of how you may call ListAllFiles to display all the files in C:\Windows. We arbitrarily start with a search number of 1.
result = ListAllFiles(1, 'C:\Windows')

/* Did ListAllFiles complete its job? If not, display why. */
IF result \== "" THEN SAY result

Because any wildcard template we use also affects the sub-directory names returned, you'd normally have to do a separate MATCHNAME search on each sub-directory, passing the desired wildcard template (and a new, unique search number). On the other hand, if what you want to check is a particular extension on the filename, then it's easier to just do that yourself. Here we have a function named ListAllTextFiles. It is very similiar to the example above, but it displays the names of all files whose names end with a .TXT extension. It doesn't display the sub-directory names, but it does search those sub-directories.

Let's also assume that we wish the full name to be returned, so we'll specify the "F" option too.

ListAllTextFiles: PROCEDURE

/* Create the template we'll pass to MATCHNAME. We need
 * to append a backslash to the directory name.
 */
template = ARG(2)
IF template = "" THEN template = DIRECTORY()
template = template || '\'

/* Repeat until MATCHNAME tells us to stop. */
DO UNTIL result \== ""

   /* Have MATCHNAME return the next file/sub-directory name. */
   result = MATCHNAME(ARG(1), 'Info', template, 'DNCRA', 'FNS')

   /* An error? If so, end the loop. */
   IF result \= "" THEN LEAVE

   /* Is this a sub-directory? If so, we need to call ListAllTextFiles
    * again, passing a new search number (ie, we'll increment the
    * number we're using right now), and the name of this sub-directory.
    */
   IF Info.0 = "" THEN DO

      result = ListAllTextFiles(ARG(1) + 1, Info)

      /* An error? If so, end the loop, but first abort
       * our own MATCHNAME loop. We need to pass the search
       * number we're using here to abort the correct listing.
       */
      IF result \= "" THEN DO
           MATCHNAME(ARG(1))
          LEAVE
      END

   END /* A sub-directory. */

   /* It's a file name. See if its extension is .TXT. If
    * so display the full name.
    */
   ELSE IF TRANSLATE(FILESPEC('E', Info)) == ".TXT" THEN DO

      /* One caveat: Do not change the value of 'result' in
       * this inside loop, or you may inadvertently mess up
       * the DO UNTIL.
       */

      /* Here you'd do something with this name. We just display it. */
      SAY Info

   END /* A matching file. */

END /* Get the next file/sub-directory name. */

/* Did MATCHNAME complete its listing? If not, return an error
 * message. Otherwise, return an empty string.
 */
IF result == "DONE" THEN result = ""
RETURN result

Checking the existance of a file/directory

You can use MATCHNAME to check the existance of a file or directory. Of course, there are other functions which can do this, such as STATE(), or STREAM's "QUERY EXISTS" command. But these latter two functions can't distinguish between a directory or file name. For example, if you wish to check whether a file named C:\Windows\MyFile.txt exists, and there happens to be a directory by that same name, then STATE or STREAM's "QUERY EXISTS" will report that it exists.

Using MATCHNAME, you can check whether a specific file or directory exists. You pass that name as the template to MATCHNAME. MATCHNAME will return "DONE" if it doesn't exist, an empty string if it exists, or an error message if there is a problem determining the existance of the file/directory. Also, by using MATCHNAME's attributes arg, you can further restrict the type of item you want to check for, such as a file or directory. You can also ask MATCHNAME to return additional information which could save you having to make an additional function call to obtain such information.

Because you don't plan to loop around MATCHNAME until it returns "DONE" or an error message, you need to specify the "O" option.

Here are some examples of checking the existance of a file or directory.

/* Check if a sub-directory named 'MyDir' exists in
 * the current directory, and if so, return its full name.
 */
IF MATCHNAME(, 'Info', 'MyDir', 'D', 'ONF') == "" THEN
   SAY "MyDir's full name is" Info
ELSE
   SAY ""MyDir does not exist."
RETURN

/* Check if a file named 'MyFile' exists in the
 * current directory, and if so, return its full name.
 */
IF MATCHNAME(, 'Info', 'MyFile', 'NSHCAT', 'ONF') == "" THEN
   SAY "MyFile's full name is" Info
ELSE
   SAY "MyFile does not exist."
RETURN

/* Check if a hidden file named 'MyFile' exists in the
 * current directory, and if so, return its full name and size.
 */
IF MATCHNAME(, 'Info', 'MyFile', 'H', 'OSNF') == "" THEN DO
   SAY "MyFile's full name is" Info
   SAY "Size =" Info.0
END
ELSE
   SAY "MyFile does not exist."
RETURN

/* Check if a file named 'MyFile' exists in the
 * directory named C:\MyDir and get its last modified date.
 */
IF MATCHNAME(, 'Info', 'C:\MyDir\MyFile'', 'NSHCAT', 'OS') == "" THEN
   SAY "MyFile exists. Date modified:" Info.1
ELSE
   SAY "MyFile does not exist."