We ❤️ Open Source

A community education resource

6 min read

How to use the findfirst-findnext functions to expand wildcards into filenames

Learn how Globbing works in FreeDOS programs.

There’s a concept in Unix systems called globbing where wildcards on the command line get expanded into filenames. The Linux shell does “globbing” for you, so if you wanted to view all text files in the current directory, you just type less *.txt and the shell expands the * so it’s as though you executed a longer command like:

$ less README.txt about.txt bugs.txt contrib.txt

DOS is a much simpler operating system from an era when computers measured memory in kilobytes. The DOS shell (COMMAND.COM) did not perform this “globbing” for you, so each program had to interpret wildcards on its own. However, the DOS kernel provides a system call in Interrupt 21 at Function 0x4E known as the “find first” feature, paired with another call at Function 0x4F for “find next.” With these two functions, your next FreeDOS program can expand wildcards into filenames.

Read more: 5 FreeDOS editors I love

Find first

Every C compiler for DOS provided an interface to the DOS “find first” and “find next” functions. For example, the OpenWatcom C compiler uses the _dos_findfirst and _dos_findnext functions. Let’s look at the “find first” function to see how to get the first matching name.

The _dos_findfirst function uses this syntax:

unsigned
_dos_findfirst( const char *path, unsigned attribs, struct find_t *buf );

The “path” provides the name you want to match, including the ? and * wildcards. The “attribs” argument gives a list of DOS file attributes to match against, including _A_NORMAL for regular files and _A_SUBDIR for directories. You can provide additional values to indicate hidden files, system files, and disk volume IDs, but for this example we’ll just look for regular files.

If the _dos_findfirst function can find at least one entry that matches the path and attributes, it returns zero to indicate success, and details are stored in the structure pointed to by buf. The find_t structure contains fields including name with the expanded filename, attrib which has the full attributes, and size for the size of the file in bytes.

Let’s look at an example that finds only the first matching file and prints its name and size:

#include <stdio.h>
#include <dos.h> /* _dos_findfirst */

int main()
{
  struct find_t fileinfo;

  if (_dos_findfirst("GLOB.*", _A_NORMAL, &fileinfo) == 0) {
    printf("%s: %ld bytes\n", fileinfo.name, fileinfo.size);
  }
  else {
    puts("not found");
  }

  return 0;
}

Save this file as glob.c and compile it using the OpenWatcom C compiler:

D:\SRC\glob>wcl -q glob.c

When we run the new program, the _dos_findfirst function looks for any regular files that match GLOB.* for the filename. Unlike Linux, DOS filenames are case-insensitive, so GLOB.C is the same as glob.c. If there is at least one matching file, we will see the first entry printed with its file size in bytes.

D:\SRC\glob>dir /b glob.*
GLOB.OBJ
GLOB.EXE
GLOB.C

D:\SRC\glob>glob
GLOB.OBJ: 474 bytes

Read more: How to write your first FreeDOS program

Find next

Having found at least one file, we can use the _dos_findnext function to find more matching files. The _dos_findnext function is much simpler, since it’s just continuing from a previous call to _dos_findfirst, and has this usage:

unsigned
_dos_findfirst( struct find_t *buf );

Let’s update the glob.c program to find all files that match the GLOB.* pattern:

#include <stdio.h>
#include <dos.h> /* _dos_findfirst, _dos_findnext */

int main()
{
  struct find_t fileinfo;

  if (_dos_findfirst("GLOB.*", _A_NORMAL, &fileinfo) == 0) {
    printf("%s: %ld bytes\n", fileinfo.name, fileinfo.size);

    while (_dos_findnext(&fileinfo) == 0) {
      printf("%s: %ld bytes\n", fileinfo.name, fileinfo.size);
    }
  }
  else {
    puts("not found");
  }

  return 0;
}

Compile and run the new program to see all files that match the GLOB.* pattern, including the C source file, the compiler object file, and the executable program:

D:\SRC\glob>dir /b glob.*
GLOB.C
GLOB.OBJ
GLOB.EXE

D:\SRC\glob>glob
GLOB.C: 424 bytes
GLOB.OBJ: 504 bytes
GLOB.EXE: 8958 bytes

Read more: How to navigate the command line

Listing files and directories

With the “find first” and “find next” functions, you can expand wildcards to match whatever you need to find. You can also use the attributes in the returned data to display the results in different ways.

One way to demonstrate this is by writing a version of the ls command from Linux. In this simplified example, the new ls command will display all files and subdirectories that match a pattern provided on the command line.

To do that, we can write a function called list_all that accepts a path, then uses _dos_findfirst and _dos_findnext to find all matching entries. To print each entry, we’ll write a list_file function that uses the bit field for the file attribute to print the name in two different ways: If it’s a file, we’ll just list the name. For subdirectories, we’ll print the name in brackets, which is the typical way to display a file listing in DOS.

#include <stdio.h>
#include <dos.h>

void list_file(const char *name, char attrib)
{
 if (attrib & _A_SUBDIR) {
   printf("[%s]\n", name);
 }
 else {
   puts(name);
 }
}

void list_all(const char *path)
{
 struct find_t fileinfo;

 if (_dos_findfirst(path, _A_NORMAL | _A_SUBDIR, &fileinfo) == 0) {
   list_file(fileinfo.name, fileinfo.attrib);

   while (_dos_findnext(&fileinfo) == 0) {
     list_file(fileinfo.name, fileinfo.attrib);
   }
 }
}

int main(int argc, char **argv)
{
 int i;

 for (i = 1; i < argc; i++) {
   list_all(argv[i]);
 }

 return 0;
}

Save this as ls.c and compile it. If you run the program with different command line arguments, you can explore the different files. For example, list all GLOB.* and LS.* files:

D:\SRC\glob>ls glob.*
GLOB.C
GLOB.OBJ
GLOB.EXE

D:\SRC\glob>ls ls.*
LS.C
LS.OBJ
LS.EXE

To see all C source files, use this:

D:\SRC\glob>ls *.c
GLOB.C
LS.C

If _dos_findnext doesn’t find more than one file, we’ll only see the first entry. For example, to list both the GLOB.C and LS.C source files:

D:\SRC\glob>ls glob.c ls.c
GLOB.C
LS.C

The current directory and parent directory are represented by the special names . and .., respectively:

D:\SRC\glob>ls *
[.]
[..]

More from We Love Open Source

About the Author

Jim Hall is an open source software advocate and developer, best known for usability testing in GNOME and as the founder + project coordinator of FreeDOS. At work, Jim is CEO of Hallmentum, an IT executive consulting company that provides hands-on IT Leadership training, workshops, and coaching.

Read Jim's Full Bio

The opinions expressed on this website are those of each author, not of the author's employer or All Things Open/We Love Open Source.

Want to contribute your open source content?

Contribute to We ❤️ Open Source

Help educate our community by contributing a blog post, tutorial, or how-to.

This year we're hosting two world-class events!

Join us for AllThingsOpen.ai, March 17-18, and All Things Open 2025, October 12-14.

Open Source Meetups

We host some of the most active open source meetups in the U.S. Get more info and RSVP to an upcoming event.