获取boost::filesystem相对路径

33

boost::filesystem库中哪些方法可以帮助我获取相对于另一个路径的路径?

我有两个路径:/home/user1/Downloads/Books/home/user1/。现在我想获取Downloads/Books路径。


如果所有方法都失败了,将两个字符串都转换为绝对字符串,然后从一个字符串中提取另一个字符串的子串。 - Andrew T Finnell
2
Boost的新版本对此有一个非常简单的答案,提供在下面链接中:https://dev59.com/r2kw5IYBdhLWcg3wDWTL#37715252 - Drew Dormann
6个回答

41

在新版本的 boost (从1.60开始),您可以使用 boost::filesystem::relative(此处查看文档。)

#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;
}

2
使用boost::filesystem::relative的一个缺点是它试图访问本地文件系统上提供的路径。因此,如果您只想使用与本地文件无关的虚拟路径(例如URL),那么https://dev59.com/r2kw5IYBdhLWcg3wDWTL#29221546中的relativeTo函数更有用。 - mpipe3

22
提供的答案中的代码每行都很长。假设您写了 namespace fs = boost::filesystem; 那么这段代码将帮助你完成大部分工作,并且看起来更易于阅读:
    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;
    }

2
这看起来比其他答案好多了。 - rr-
2
这个答案的持续相关性超越了新版本的Boost,因为该版本没有规范化、解析符号链接,甚至没有检查路径是否存在。换句话说,它可以在抽象中使用,而不需要物理文件系统。 - metal
1
@metal C++现在有一个版本的relative,它不会触及文件系统 - 请查看我的答案。 - PBS

15

以下内容取自通过Nicol提供的链接:

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;
    }

把这段代码放进头文件里,它就会按照你的要求执行。

示例调用:

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

1
在那个示例调用中,不需要限定make_relative。这可能是错误的(给定的代码似乎没有将make_relative放在boost::filesystem中),如果它是正确的,由于ADL,它是不必要的。 - MSalters
代码运行良好。参数cvt未使用,可以删除。 - Yefu

6

1
实际上,这个补丁因为使用了append而被拒绝了。 - Mahmoud Al-Qudsi
1
@MahmoudAl-Qudsi:我链接了一个功能请求。在该请求的评论中链接了一个补丁,但它并不是该请求的一部分。 - Nicol Bolas
你说得对。也许再加上一些注释会更有帮助? :) - Mahmoud Al-Qudsi

3
从C++17开始,解决这个问题的方法是对于存在的路径使用std::filesystem::relative,对于可能不存在的路径使用std::filesystem::path::lexically_relative
#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';

这将打印

"Downloads/Books"
"Downloads/Books"

1
接受的答案代码不起作用。应该是
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接