I'm Michael Suodenjoki - a software engineer living in Kgs. Lyngby, north of Copenhagen, Denmark. This is my personal site containing my blog, photos, articles and main interests.

Updated 2011.01.23 15:37 +0100

 

Unix findfirst()

I'm not a Unix expert at all, so when I lately found myself in trouble compiling a utility C++ class for Unix, I had to dive into the details. The source code did not compile on the Unix box and I tracked the problem to the use of the Visual C++ specific _findfirst()/_findnext()  functions.

Note: Microsoft has a fine tradition of providing platform specific code and they (luckily?) use the naming scheme of prefixing the non standard structure and functions with underscore '_' character, to indidate this. There are also the platform specific Windows SDK specific FindFirstFile/FindNextFile functions.

If you want to program your code in as a platform independent way as possible you cannot use any platform specific / non-standard structures and functions. You want to use standard C++ or other libraries which are platform independent.

A search on the internet revealed that other programmers have the same problem  (Google Search) and the common answer seems to be that one should look at Boost filesystem library and specifically to the directory-iterator class. Because the Boost filesystem library is platform independent it sounds like as a good advice. However there were very little specific examples how to really use it so I thought that I could provide a sample implementation that you may choose to take offset from in your own code.

Note: A good introduction to the Boost filesystem library can be found in chapter 9 in the excellent online book The Boost C++ Libraries by Boris Schäling.

So here goes... I chosen to make a different solution than using findfirst/findnext function style. Instead I return a vector of the found files which you can iterate through as you like, inspired by the code found here.

C++ Header - findfiles.hpp

#ifndef _FINDFILES_HPP
#define _FINDFILES_HPP

// Standard C++ includes
#include <string>
#include <vector>

typedef std::vector<std::string> FileSet;

/************************************************
*
*                FIND FILES
*
************************************************/

//
// '::FindFiles'
//
// Finds a set of files matching a filesearch specification (path combined with possible filename wildcards * or ?)-
// Returns a set (i.e. vector) of matching found files (if any).
//
// Notes:
//   * Even though you can give Windows backslash character as path separator, please prefer using normal slash instead.
//   * Each found file is always in full/complete path and path separator is always slash character.
//   * Implementation uses Boost filesystem and is therefore independent of operating system.
//
// Usage Examples:
//
//   FindFiles( "" )                             - similar to "./*.*", i.e. find all files in current directory.
//   FindFiles( "*.*")                           - similar to "./*.*", i.e. find all files in current directory.
//   FindFiles( "../s*")                         - find all files or directories beginning with s in parent directory (to current directory).
//   FindFiles( "../subdir/s*" )                 - find all files or directories beginning with s in parents subdir directory (to current directory).
//   FindFiles( "C:\\*.*" )                      - files all files in root directory of C:
//   FindFiles( "C:\\Windows\\System32\\*.exe" ) - find all exe files in Windows system directory.
//   FindFiles( "C:/Windows/System32/n*.exe"  )  - find all exe files beginning with n in Windows system directory.
//
FileSet FindFiles( const std::string & filesearch );

#endif // _FINDFILES_HPP

C++ Source - findfiles.cpp

#include "findfiles.hpp"

// Boost includes
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>

using namespace std;
using namespace boost::filesystem;

/************************************************
*
*               FIND FILES
*
************************************************/

//
// '::FindFiles'
//
// Finds a set of files matching a filesearch specification (path combined with possible filename wildcards * or ?)-
// Returns a set (i.e. vector) of matching found files (if any).
//
// Notes:
//   * Even though you can give Windows backslash character as path separator, please prefer using normal slash instead.
//   * Each found file is always in full/complete path and path separator is always slash character.
//   * Implementation uses Boost filesystem and is therefore independent of operating system.
//
// Usage Examples:
//
//   FindFiles( "" )                             - similar to "./*.*", i.e. find all files in current directory.
//   FindFiles( "*.*")                           - similar to "./*.*", i.e. find all files in current directory.
//   FindFiles( "../s*")                         - find all files or directories beginning with s in parent directory (to current directory).
//   FindFiles( "../subdir/s*" )                 - find all files or directories beginning with s in parents subdir directory (to current directory).
//   FindFiles( "C:\\*.*" )                      - files all files in root directory of C:
//   FindFiles( "C:\\Windows\\System32\\*.exe" ) - find all exe files in Windows system directory.
//   FindFiles( "C:/Windows/System32/n*.exe"  )  - find all exe files beginning with n in Windows system directory.
//
FileSet FindFiles( const string & filesearch )
{
  FileSet fileset;

  // Initialize path p (if necessary prepend with ./ so current dir is used) 
  path p(filesearch);
  if( !path(filesearch).has_parent_path() )
  {
    p = "./");
    p /= filesearch;
  }

  // Initialize regex filter - use *.* as default if nothing is given in filesearch
  string f( p.has_filename() ? p.filename() : "*.*"));
  // Make replacements:
  // 1: Replace . with \. (i.e. escape it)
  // 2: Replace any ? with .
  // 3: Replace any * with .* 
  f = boost::regex_replace( f, boost::regex("(\\.)|(\\?)|(\\*)"), "(?1\\\\.)(?2.)(?3.*)", boost::match_default | boost::format_all );

  const boost::regex filter(f);
  
  path dir(system_complete(p).parent_path());
  if( is_directory(dir) )
  {
    // Iterate through contents (files/directories) of directory...
    for( directory_iterator it(dir); it!=directory_iterator(); ++it )
    {
      // If match
      if( boost::regex_match( it->leaf(), filter ) )
        fileset.push_back(it->string());
    }
  }

  return fileset;
}