5

I'm looking for a way to check if 2 strings are the same in terms of filesystem path (directory). For example all string from this set are the same in terms of filesystem path: {/x,\x,//x,/x/}, but this two - /x and /y are not, even if /y is symbolic link to /x. The program I'm going to write should work as for Linux as well for windows, so I'm looking for portable solution.

EDIT:

I'm using only header-only libs of boost, so solution with boost::filesystem is not ok for me. I know that there is UrlCompare in windows API, is there something like in linux?

Mihran Hovsepyan
  • 10,810
  • 14
  • 61
  • 111

4 Answers4

3
std::string path1 = "c:\\folder\\";
std::string path2 = "c:\\folder\\folder\\..\\";

boost::filesystem::equivalent(boost::filesystem::path(path1), boost::filesystem::path(path2)

The code returns true, because the folder are actually the same.

nhahtdh
  • 55,989
  • 15
  • 126
  • 162
Nomadmain
  • 41
  • 1
3

Any non-Boost solution will involve system dependent code (which is hidden in Boost if you use Boost). And you will have to define exactly what you mean by "matching": should "./MyFile.xxx" and "MyFile.xxx" compare equal? What about "aaa/.../MyFile.xxx" and "MyFile.xxx"?

The way I would handle this is to define a class with two data members, an std::string with the “prefix” (which would always be empty in Unix), and an std::vector<std::string> with all of the path elements. This class would be doted with the necessary comparison functions, and would use system dependent code to implement the constructor; the constructor itself would be in the source file, and the source file would include a machine dependent header (generally by using a separate directory for each variant, and choosing the header by means of -I or /I to specify which directory to use). The sort of things which might go into the header:

inline bool
isPathSeparator( char ch )
{
    return ch == '/';
}

std::string
getHeader( std::string const& fullPathName )
{
    return "";
}

bool
charCompare( char lhs, char rhs )
{
    return lhs < rhs;
}

bool
charMatch( char lhs, char rhs )
{
    return lhs == rhs;
}

for Unix, with:

inline bool
isPathSeparator( char ch )
{
    return ch == '/' || ch == '\\';
}

std::string
getHeader( std::string const& fullPathName )
{
    return fullPathName.size() > 2 && fullPathName[1] == ':'
        ? fullPathName.substr( 0, 2 )
        : std::string();
}

bool
charCompare( char lhs, char rhs )
{
    return tolower( (unsigned char)lhs) < tolower( (unsigned char)rhs );
}

bool
charMatch( char lhs, char rhs )
{
    return tolower( (unsigned char)lhs ) == tolower( (unsigned char)rhs );
}

for Windows.

The constructor would then use getHeader to initialize the header, and iterate over input.begin() + header.size() and input.end(), breaking the string up into elements. If you encounter an element of ".", ignore it, and for one of "..", use pop_back() to remove the top element of the path, provided the path isn't empty. Afterwards, it's just a question of defining the comparators to use charCompare and charMatch for char, and std::lexicographical_compare or std::equal (after verifying that the sizes are equal) with the comparator for std::string (and probably furthermore for your new class). Something like:

struct FileNameCompare
{
    bool operator()( char lhs, char rhs ) const
    {
        return charCompare( lhs, rhs );
    }
    bool operator()( std::string const& lhs, std::string const& rhs ) const
    {
        return std::lexicographical_compare(
            lhs.begin(), lhs.end(),
            rhs.begin(), rhs.end(),
            *this );
    }
    bool operator()( FileName const& lhs, FileName const& rhs ) const
    {
        return (*this)( lhs.prefix, rhs.prefix )
            || ( !(*this)( rhs.prefix, lhs.prefix )
                && std::lexicographical_compare(
                    lhs.elements.begin(), lhs.elements.end(),
                    rhs.elements.begin(), rhs.elements.end(),
                    *this ) );
    }
};

struct FileNameMatch
{
    bool operator()( char lhs, char rhs ) const
    {
        return charMatch( lhs, rhs );
    }
    bool operator()( std::string const& lhs, std::string const& rhs ) const
    {
        return lhs.size() == rhs.size()
            && std::equal( lhs.begin(), lhs.end(), rhs.begin(), *this );
    }
    bool operator()( FileName const& lhs, FileName const& rhs ) const
    {
        return (*this)( lhs.prefix, rhs.prefix )
            && lhs.elements.size() == rhs.elements.size()
            && std::equal( lhs.elements.begin(), lhs.elements.end(),
                           rhs.elements.begin(),
                           *this );
    }
};

should do the trick. (Just remember that the operator()( char, char ) const must be in the source file; you can't inline them in the header, which won't include the system dependent header which defines charCompare and charMatch.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
2

Use the boost::filesystem library - this has path comparison functionality.

EDIT: You could try the apr - yes it's not C++, however it will be portable.

Nim
  • 33,299
  • 2
  • 62
  • 101
1

In terms of portability the Boost filesystem library could be useful (documentation link). To be more specific: path.hpp is most useful, where you could use path comparison.

EDIT

This discussion on gmane.org regards a minimal header-only version of boost::filesystem. In summary: This does not exist. So you'll end up in building your own abstraction library and give it cross-platform functionality.

Under Linux there is dirent.h which is a POSIX C library. For Windows you'll have to use the Win32 API. However, there is also a so called "Directory-Stream library", which seems to be cross-platform, available here (website is in german).

Sebastian
  • 8,046
  • 2
  • 34
  • 58