【问题标题】:boost::filesystem get relative pathboost::filesystem 获取相对路径
【发布时间】:2012-04-27 09:14:16
【问题描述】:

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

我有一个路径/home/user1/Downloads/Books 和一个路径/home/user1/。现在我想得到一个路径Downloads/Books

【问题讨论】:

  • 所有其他方法都失败,将两者都转换为绝对字符串和子字符串。
  • 新版本的 boost 对此有一个非常简单的答案,前提是down below

标签: c++ boost filesystems boost-filesystem


【解决方案1】:

boost 的新版本(从1.60 开始),您可以使用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;
}

【讨论】:

  • 使用 boost::filesystem::relative 的一个缺点是它试图访问本地文件系统上提供的路径。因此,如果您只想使用与本地文件(例如 URL)无关的虚拟路径,那么 stackoverflow.com/a/29221546/323058 中的 relativeTo 函数会更有用。
【解决方案2】:

所提供答案中的代码每行都很长。假设你写了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;
    }

【讨论】:

  • 这看起来比其他答案好多了。
  • 这个答案与内置 relative() 函数的新版本 Boost 的持续相关性在于,这个版本不规范化、解析符号链接,甚至不检查路径是否存在。 IOW,它可以在没有物理文件系统的情况下抽象地使用。
  • @metal C++ 现在有一个不涉及文件系统的relative 版本 - 请参阅我的答案。
【解决方案3】:

取自通过 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;

【讨论】:

  • 无需在该示例调用中限定make_relative。这可能是错误的(给定的代码看起来不像将 make_relative 放在 boost::filesystem 中),如果它是正确的,由于 ADL,它是不必要的。
  • 代码运行良好。参数 cvt 未使用,可以去掉。
【解决方案4】:

很遗憾,Boost.Filesystem 中不存在这样的功能。 It has been requested,但他们似乎并不在意。

你基本上必须手动完成。

Boost.Filesystem 1.60 添加了the relative function 可用于处理此问题。

【讨论】:

  • 其实补丁被拒绝是有原因的(append的使用)。
  • @MahmoudAl-Qudsi:我链接到一个功能请求。该请求的评论中链接了一个补丁,但它不是请求的部分
  • 你是对的。也许还有更多的 cmets 会有所帮助? :)
【解决方案5】:

从 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"

【讨论】:

    【解决方案6】:

    接受的答案的代码不起作用。应该是

    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
    

    【讨论】:

      猜你喜欢
      • 2012-09-20
      • 2011-04-25
      • 2011-02-15
      • 1970-01-01
      • 2020-11-12
      • 1970-01-01
      • 1970-01-01
      • 2010-09-28
      • 2016-01-18
      相关资源
      最近更新 更多