We ❤️ Open Source
A community education resource
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
- 5 FreeDOS editors I love
- How to write your first FreeDOS program
- How to navigate the command line
- Explore the five steps of the FreeDOS boot sequence
- A throwback experiment with Linux and Unix
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.