boost::filesystem get relative path

23,423

Solution 1

Taken from a link found by following the ticket Nicol linked to:

template < >
    path& path::append< typename path::iterator >( typename path::iterator begin, typename path::iterator end, const codecvt_type& cvt)
    { 
        for( ; begin != end ; ++begin )
            *this /= *begin;
        return *this;
    }
    // Return path when appended to a_From will resolve to same as a_To
    boost::filesystem::path make_relative( boost::filesystem::path a_From, boost::filesystem::path a_To )
    {
        a_From = boost::filesystem::absolute( a_From ); a_To = boost::filesystem::absolute( a_To );
        boost::filesystem::path ret;
        boost::filesystem::path::const_iterator itrFrom( a_From.begin() ), itrTo( a_To.begin() );
        // Find common base
        for( boost::filesystem::path::const_iterator toEnd( a_To.end() ), fromEnd( a_From.end() ) ; itrFrom != fromEnd && itrTo != toEnd && *itrFrom == *itrTo; ++itrFrom, ++itrTo );
        // Navigate backwards in directory to reach previously found base
        for( boost::filesystem::path::const_iterator fromEnd( a_From.end() ); itrFrom != fromEnd; ++itrFrom )
        {
            if( (*itrFrom) != "." )
                ret /= "..";
        }
        // Now navigate down the directory branch
        ret.append( itrTo, a_To.end() );
        return ret;
    }

Stick that in a header file and it should do what you want.

Sample call:

boost::filesystem::path a("foo/bar"), b("foo/test/korv.txt");
std::cout << make_relative( a, b ).string() << std::endl;

Solution 2

In new versions of boost (starting in 1.60), you can use boost::filesystem::relative. (See the documentation here.)

#include <boost/filesystem.hpp>
#include <iostream>
namespace fs = boost::filesystem;

int main()
{
    fs::path parentPath("/home/user1/");
    fs::path childPath("/home/user1/Downloads/Books");
    fs::path relativePath = fs::relative(childPath, parentPath);
    std::cout << relativePath << std::endl;
}

Solution 3

The code in the provided answers is quite long on each line. Assuming that you wrote namespace fs = boost::filesystem; then this code gets you most of the way and looks easier to read to me:

    auto relativeTo( const fs::path& from, const fs::path& to )
    {
       // Start at the root path and while they are the same then do nothing then when they first
       // diverge take the entire from path, swap it with '..' segments, and then append the remainder of the to path.
       auto fromIter = from.begin();
       auto toIter = to.begin();

       // Loop through both while they are the same to find nearest common directory
       while( fromIter != from.end() && toIter != to.end() && *toIter == *fromIter )
       {
          ++toIter;
          ++fromIter;
       }

       // Replace from path segments with '..' (from => nearest common directory)
       auto finalPath = fs::path{};
       while( fromIter != from.end() )
       {
          finalPath /= "..";
          ++fromIter;
       }

       // Append the remainder of the to path (nearest common directory => to)
       while( toIter != to.end() )
       {
          finalPath /= *toIter;
          ++toIter;
       }

       return finalPath;
    }

Solution 4

Sadly, such a function does not exist in Boost.Filesystem. It has been requested, but they don't seem to care.

You basically have to do it manually.

Boost.Filesystem 1.60 added the relative function that can be used to handle this.

Solution 5

From C++17 onwards, the solution to this problem is to use std::filesystem::relative for paths that exist, and std::filesystem::path::lexically_relative for paths that might not exist.

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

fs::path path("/home/user1/Downloads/Books");
fs::path base("/home/user1/");
std::cout << fs::relative(path, base) << '\n';
std::cout << path.lexically_relative(base) << '\n';

This prints

"Downloads/Books"
"Downloads/Books"
Share:
23,423
itun
Author by

itun

Updated on December 02, 2021

Comments

  • itun
    itun over 2 years

    What methods of the boost::filesystem library can help me to get a path relative to another path?

    I have a path /home/user1/Downloads/Books and a path /home/user1/. Now I want to get a path Downloads/Books.