33

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.

tshepang
  • 12,111
  • 21
  • 91
  • 136
itun
  • 3,439
  • 12
  • 51
  • 75

6 Answers6

41

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;
}
milaniez
  • 1,051
  • 1
  • 9
  • 21
  • 2
    One disadvantage of using boost::filesystem::relative is that it attempts to access the paths supplied on the local file system. So if you want to just work with virtual paths that are not related to local files (e.g. URLs) then the relativeTo function in https://stackoverflow.com/a/29221546/323058 is more useful. – mpipe3 Dec 09 '20 at 13:55
22

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;
    }
metal
  • 6,202
  • 1
  • 34
  • 49
Robert Massaioli
  • 13,379
  • 7
  • 57
  • 73
  • 2
    This looks so much better than the other answers. – rr- Dec 26 '15 at 18:52
  • 2
    The continued relevance of this answer over the new versions of Boost, which have `relative()` functions built-in, is that this version does not normalize, resolve symlinks, or even check that the paths exist. IOW, it can be used in the abstract without a physical file system. – metal Jan 09 '20 at 20:56
  • 1
    @metal C++ now has a version of `relative` that does not touch the filesystem - see my answer. – PBS Dec 16 '21 at 03:31
15

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;
Mahmoud Al-Qudsi
  • 28,357
  • 12
  • 85
  • 125
  • 1
    No need to qualify `make_relative` in that sample call. It might be wrong (the given code doesn't look like it puts `make_relative` in `boost::filesystem`) and if it's right, it's unnecessary due to ADL. – MSalters Aug 26 '13 at 11:22
  • The code works well. The parameter cvt is unused and can be removed. – Yefu Jun 20 '18 at 20:48
6

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
3

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"
PBS
  • 1,389
  • 11
  • 20
1

The accepted answer's code doesn't work. It should be

namespace boost { namespace filesystem {

template <> path& path::append<path::iterator>(path::iterator begin, 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;
}

} } // namespace boost::filesystem
Terry Shi
  • 968
  • 1
  • 10
  • 14