从两个绝对路径获取相对路径

Lig*_*ica 31 c++ boost boost-filesystem

我有两个绝对文件系统路径(A和B),我想生成第三个文件系统路径,表示"B的相对".

使用案例:

  • 管理播放列表的媒体播放器.
  • 用户将文件添加到播放列表.
  • 相对于播放列表路径添加到播放列表的新文件路径.
  • 将来,整个音乐目录(包括播放列表)都移动到其他地方.
  • 所有路径仍然有效,因为它们与播放列表相关.

boost::filesystem似乎必须complete解决relative ~ relative => absolute,但在reverse(absolute ~ absolute => relative)中没有做任何事情.

我想用Boost路径来做.

Lig*_*ica 19

从版本1.60.0开始, boost.filesystem支持这一点.您正在寻找会员功能path lexically_relative(const path& p) const.

原价,1.60.0以下的答案.


Boost不支持这个; 这是一个悬而未决的问题 - #1976(完全反向函数) - 但似乎没有太大的牵引力.

这是一个模糊的天真的解决方法,似乎可以解决这个问题(不确定它是否可以改进):

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <stdexcept>

/**
 * https://svn.boost.org/trac/boost/ticket/1976#comment:2
 * 
 * "The idea: uncomplete(/foo/new, /foo/bar) => ../new
 *  The use case for this is any time you get a full path (from an open dialog, perhaps)
 *  and want to store a relative path so that the group of files can be moved to a different
 *  directory without breaking the paths. An IDE would be a simple example, so that the
 *  project file could be safely checked out of subversion."
 * 
 * ALGORITHM:
 *  iterate path and base
 * compare all elements so far of path and base
 * whilst they are the same, no write to output
 * when they change, or one runs out:
 *   write to output, ../ times the number of remaining elements in base
 *   write to output, the remaining elements in path
 */
boost::filesystem::path
naive_uncomplete(boost::filesystem::path const p, boost::filesystem::path const base) {

    using boost::filesystem::path;
    using boost::filesystem::dot;
    using boost::filesystem::slash;

    if (p == base)
        return "./";
        /*!! this breaks stuff if path is a filename rather than a directory,
             which it most likely is... but then base shouldn't be a filename so... */

    boost::filesystem::path from_path, from_base, output;

    boost::filesystem::path::iterator path_it = p.begin(),    path_end = p.end();
    boost::filesystem::path::iterator base_it = base.begin(), base_end = base.end();

    // check for emptiness
    if ((path_it == path_end) || (base_it == base_end))
        throw std::runtime_error("path or base was empty; couldn't generate relative path");

#ifdef WIN32
    // drive letters are different; don't generate a relative path
    if (*path_it != *base_it)
        return p;

    // now advance past drive letters; relative paths should only go up
    // to the root of the drive and not past it
    ++path_it, ++base_it;
#endif

    // Cache system-dependent dot, double-dot and slash strings
    const std::string _dot  = std::string(1, dot<path>::value);
    const std::string _dots = std::string(2, dot<path>::value);
    const std::string _sep = std::string(1, slash<path>::value);

    // iterate over path and base
    while (true) {

        // compare all elements so far of path and base to find greatest common root;
        // when elements of path and base differ, or run out:
        if ((path_it == path_end) || (base_it == base_end) || (*path_it != *base_it)) {

            // write to output, ../ times the number of remaining elements in base;
            // this is how far we've had to come down the tree from base to get to the common root
            for (; base_it != base_end; ++base_it) {
                if (*base_it == _dot)
                    continue;
                else if (*base_it == _sep)
                    continue;

                output /= "../";
            }

            // write to output, the remaining elements in path;
            // this is the path relative from the common root
            boost::filesystem::path::iterator path_it_start = path_it;
            for (; path_it != path_end; ++path_it) {

                if (path_it != path_it_start)
                    output /= "/";

                if (*path_it == _dot)
                    continue;
                if (*path_it == _sep)
                    continue;

                output /= *path_it;
            }

            break;
        }

        // add directory level to both paths and continue iteration
        from_path /= path(*path_it);
        from_base /= path(*base_it);

        ++path_it, ++base_it;
    }

    return output;
}
Run Code Online (Sandbox Code Playgroud)


Pau*_*oux 7

我只是编写了可以将绝对路径转换为相对路径的代码.它适用于我的所有用例,但我不能保证它是完美的.

为了便于阅读,我将boost :: filesystem改为'fs'.在函数定义中,您可以使用fs :: path :: current_path()作为'relative_to'的默认值.

fs::path relativePath( const fs::path &path, const fs::path &relative_to )
{
    // create absolute paths
    fs::path p = fs::absolute(path);
    fs::path r = fs::absolute(relative_to);

    // if root paths are different, return absolute path
    if( p.root_path() != r.root_path() )
        return p;

    // initialize relative path
    fs::path result;

    // find out where the two paths diverge
    fs::path::const_iterator itr_path = p.begin();
    fs::path::const_iterator itr_relative_to = r.begin();
    while( itr_path != p.end() && itr_relative_to != r.end() && *itr_path == *itr_relative_to ) {
        ++itr_path;
        ++itr_relative_to;
    }

    // add "../" for each remaining token in relative_to
    if( itr_relative_to != r.end() ) {
        ++itr_relative_to;
        while( itr_relative_to != r.end() ) {
            result /= "..";
            ++itr_relative_to;
        }
    }

    // add remaining path
    while( itr_path != p.end() ) {
        result /= *itr_path;
        ++itr_path;
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)


vit*_*kot 6

我只是考虑使用boost::filesystem相同的任务,但是 - 由于我的应用程序同时使用Qt和Boost库,我决定使用Qt,它使用一个简单的方法执行此任务:QString QDir :: relativeFilePath(const QString&fileName):

QDir dir("/home/bob");
QString s;

s = dir.relativeFilePath("images/file.jpg");     // s is "images/file.jpg"
s = dir.relativeFilePath("/home/mary/file.txt"); // s is "../mary/file.txt"
Run Code Online (Sandbox Code Playgroud)

它就像一个魅力,并为我节省了几个小时的生命.


Roi*_*ton 6

使用C ++ 17及其std::filesystem::relative从boost演变而来的,这是显而易见的:

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main()
{
    const fs::path base("/is/the/speed/of/light/absolute");
    const fs::path p("/is/the/speed/of/light/absolute/or/is/it/relative/to/the/observer");
    const fs::path p2("/little/light/races/in/orbit/of/a/rogue/planet");
    std::cout << "Base is base: " << fs::relative(p, base).generic_string() << '\n'
              << "Base is deeper: " << fs::relative(base, p).generic_string() << '\n'
              << "Base is orthogonal: " << fs::relative(p2, base).generic_string();
    // Omitting exception handling/error code usage for simplicity.
}
Run Code Online (Sandbox Code Playgroud)

输出(第二个参数为基数)

Base is base: or/is/it/relative/to/the/observer
Base is deeper: ../../../../../../..
Base is orthogonal: ../../../../../../little/light/races/in/orbit/of/a/rogue/planet
Run Code Online (Sandbox Code Playgroud)

它采用std::filesystem::path::lexically_relative了比较。与纯词法函数的不同之处在于,它std::filesystem::relative可以在比较之前使用std::filesystem::weakly_canonical(为引入了relative)解析符号链接并将两个路径归一化 。