Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compare (directory) paths in c++?

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?

like image 870
Mihran Hovsepyan Avatar asked Dec 14 '11 12:12

Mihran Hovsepyan


3 Answers

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.)

like image 142
James Kanze Avatar answered Sep 30 '22 13:09

James Kanze


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.

like image 25
Nomadmain Avatar answered Sep 30 '22 14:09

Nomadmain


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.

like image 40
Nim Avatar answered Sep 30 '22 14:09

Nim