Reading the Contents of a Directory
Problem
You need to read the contents of a directory, most likely to do something to each file or subdirectory that's in it.
Solution
To write something portable, use the Boost Filesystem library's classes and functions. It provides a number of handy utilities for manipulating files, such as a portable path representation, directory iterators, and numerous functions for renaming, deleting, and copying files, and so on. Example 10-19 demonstrates how to use a few of these facilities.
Example 10-19. Reading a directory
#include #include #include using namespace boost::filesystem; int main(int argc, char** argv) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " [dir name] "; return(EXIT_FAILURE); } path fullPath = // Create the full, absolute path name system_complete(path(argv[1], native)); if (!exists(fullPath)) { std::cerr << "Error: the directory " << fullPath.string( ) << " does not exist. "; return(EXIT_FAILURE); } if (!is_directory(fullPath)) { std::cout << fullPath.string( ) << " is not a directory! "; return(EXIT_SUCCESS); } directory_iterator end; for (directory_iterator it(fullPath); it != end; ++it) { // Iterate through each // element in the dir, std::cout << it->leaf( ); // almost as you would if (is_directory(*it)) // an STL container std::cout << " (dir)"; std::cout << ' '; } return(EXIT_SUCCESS); }
Discussion
Like creating or deleting directories (see Recipe 10.10 and Recipe 10.11), there is no standard, portable way to read the contents of a directory. To make your C++ life easier, the Filesystem library in the Boost project provides a set of portable routines for operating on files and directories. It also provides many moresee the other recipes in this chapter or the Boost Filesystem web page at www.boost.com for more information.
Example 10-19 is a simple directory listing program (like ls on Unix or dir on MS-DOS). First, it builds an absolute pathname out of the argument passed to the program, like this:
path fullPath = complete(path(argv[1], native));
The data type of a path is called, appropriately, path. This is the type that the filesystem routines operate on, and is easily convertible to a string by calling path::string. Once the path has been assembled, the program checks its existence (with exists), then checks to see if it is a directory with another utility function, is_directory. If it is, then everything is in good shape and it can proceed to the real work of listing the directory contents.
There is a class called directory_iterator in filesystem that uses standard iterator semantics, like the standard containers, to allow you to use an iterator like you would a pointer to a directory element. Unlike standard containers, however, there is no end member function you can call on a directory that represents one-past-the-last-element (i.e., vector::end). Instead, if you create a directory_iterator with the default constructor, it represents an end marker that you can use for comparison to determine when you are done. So do this:
directory_iterator end;
and then you can create an iterator from your path, and compare it to end, like this:
for (directory_iterator it(fullPath); it != end; ++it) { // do whatever you want to *it std::cout << it->leaf( ); }
The leaf member function returns a string representing the element referred to by a path, and not the full path itself, which is what you get if you call the string member function.
If you have to write something that is portable, but for some reason you cannot use Boost, take a look at the Boost code itself. It contains #ifdefs that deal with (for the most part) Windows versus Posix OS interface environments and path particulars, such as drive letters versus device names.
See Also
Recipe 10.10 and Recipe 10.11