Get relative path from two absolute paths

I have two absolute file system paths (A and B), and I want to create a third file system path that represents "Relative to B".

Use Case:

  • Media player managing a playlist.
  • The user adds the file to the playlist.
  • A new file path has been added to the playlist relative to the path to the playlist.
  • In the future, the entire music catalog (including the playlist) has moved to another location.
  • All paths are still valid as they relate to the playlist.

boost::filesystem seems to have complete to allow relative ~ relative => absolute , but do nothing in reverse order ( absolute ~ absolute => relative ).

I want to do this using Boost paths.

+30
c ++ boost boost-filesystem
Apr 24 '11 at 20:17
source share
7 answers

Starting with version 1.60.0, boost.filesystem supports this. You are looking for path lexically_relative(const path& p) const to the member function path lexically_relative(const path& p) const .

Original, up to 1.60.0 answer below.




Boost does not support this; this is an open issue - # 1976 (the inverse function to complete), which, however, does not seem to receive much traction.

Here's a vaguely naive workaround that seems to be doing its job (not sure if it can be improved):

 #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; } 
+19
Apr 24 '11 at 20:19
source share
β€” -

I just wrote code that can translate an absolute path into a relative path. It works in all my use cases, but I cannot guarantee that it is flawless.

I shortened boost :: filesystem to 'fs' for readability. In the function definition, you can use fs :: path :: current_path () as the default value for 'lative_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; } 
+7
Apr 02 2018-12-12T00:
source share

I was just thinking about using boost::filesystem for the same task, but - since my application uses the Qt and Boost libraries, I decided to use Qt, which performs this task with one simple QString method QDir :: lativeFilePath (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" 

It works like a charm and saved me a few hours of my life.

+6
Nov 27 '11 at 19:25
source share

With C ++ 17 and its std::filesystem::relative , which evolved from boost, this is easy:

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

Output (the second parameter is basic)

 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 

For comparison, it uses std::filesystem::path::lexically_relative . The difference from a purely lexical function is that std::filesystem::relative resolves symbolic links and normalizes both paths using std::filesystem::weakly_canonical (which was introduced for relative ) before comparing.

+3
Jul 20 '18 at 6:06
source share

Here's how I do it in the library that I create on top of the boost file system:

Step 1: Define the "deepest common root." In principle, this is the most common common denominator for the two paths. For example, if you have 2 paths: "C: \ a \ b \ c \ d" and "C: \ a \ b \ c \ l.txt", then the common root they share is "C: \ a \ B \ s \ ".

To get this, convert both paths to the canonical form of absolute NOT (you'll want to do this for speculative paths and symbolic links).

Step 2: To go from A to B, you suffix A with enough copies of "../" to move the directory tree to a common root, then add the line for B to go down the tree to it. In windows, you can have 2 paths without a common root, so the transition from any A to any B is not always possible.

 namespace fs = boost::filesystem; bool GetCommonRoot(const fs::path& path1, const fs::path& path2, fs::path& routeFrom1To2, std::vector<fs::path>& commonDirsInOrder) { fs::path pathA( fs::absolute( path1)); fs::path pathB( fs::absolute( path2)); // Parse both paths into vectors of tokens. I call them "dir" because they'll // be the common directories unless both paths are the exact same file. // I also Remove the "." and ".." paths as part of the loops fs::path::iterator iter; std::vector<fs::path> dirsA; std::vector<fs::path> dirsB; for(iter = pathA.begin(); iter != pathA.end(); ++iter) { std::string token = (*iter).string(); if(token.compare("..") == 0) { // Go up 1 level => Pop vector dirsA.pop_back(); } else if(token.compare(".") != 0) { // "." means "this dir" => ignore it dirsA.push_back( *iter); } } for(iter = pathB.begin(); iter != pathB.end(); ++iter) { std::string token = (*iter).string(); if(token.compare("..") == 0) { // Go up 1 level => Pop vector dirsB.pop_back(); } else if(token.compare(".") != 0) { // "." means "this dir" => ignore it dirsB.push_back( *iter); } } // Determine how far to check in each directory set size_t commonDepth = std::min<int>( dirsA.size(), dirsB.size()); if(!commonDepth) { // They don't even share a common root- no way from A to B return false; } // Match entries in the 2 vectors until we see a divergence commonDirsInOrder.clear(); for(size_t i=0; i<commonDepth; ++i) { if(dirsA[i].string().compare( dirsB[i].string()) != 0) { // Diverged break; } commonDirsInOrder.push_back( dirsA[i]); // I could use dirsB too. } // Now determine route: start with A routeFrom1To2.clear(); for(size_t i=0; i<commonDepth; ++i) { routeFrom1To2 /= dirsA[i]; } size_t backupSteps = dirsA.size() - commonDepth; // # of "up dir" moves we need for(size_t i=0; i<backupSteps; ++i) { routeFrom1To2 /= "../"; } // Append B path to go down to it from the common root for(size_t i=commonDepth; i<dirsB.size(); ++i) { routeFrom1To2 /= dirsB[i]; // ensures absolutely correct subdirs } return true; 

}

This will do what you want - you go up from A until you click on the shared folder, and B both descendants, and then go to B. You probably don't need the return of "commonDirsInOrder", which there is me, but the return of "routeFrom1To2" is the one you are requesting.

If you plan to actually change the working directory to "B", you can directly use "routeFrom1To2". Keep in mind that this function will create an absolute path, despite all the parts "..", but this should not be a problem.

+2
Jan 14 '14 at 4:37
source share

I wrote down one simple solution for this trick. There is no use for boost libraries, only STL std::string , std::vector .

The Win32 platform has been tested.

Just calling:

 strAlgExeFile = helper.GetRelativePath(PathA, PathB); 

And that would return the relative path from PathA to PathB .

Example:

 strAlgExeFile = helper.GetRelativePath((helper.GetCurrentDir()).c_str(), strAlgExeFile.c_str()); #ifdef _WIN32 #define STR_TOKEN "\\" #define LAST_FOLDER "..\\" #define FOLDER_SEP "\\" #define LINE_BREAK "\r\n" #else #define STR_TOKEN "/" #define LAST_FOLDER "../" #define FOLDER_SEP "/" #define LINE_BREAK "\n" #endif // _WIN32 void CHelper::SplitStr2Vec(const char* pszPath, vector<string>& vecString) { char * pch; pch = strtok (const_cast < char*> (pszPath), STR_TOKEN ); while (pch != NULL) { vecString.push_back( pch ); pch = strtok (NULL, STR_TOKEN ); } } string& CHelper::GetRelativePath(const char* pszPath1,const char* pszPath2) { vector<string> vecPath1, vecPath2; vecPath1.clear(); vecPath2.clear(); SplitStr2Vec(pszPath1, vecPath1); SplitStr2Vec(pszPath2, vecPath2); size_t iSize = ( vecPath1.size() < vecPath2.size() )? vecPath1.size(): vecPath2.size(); unsigned int iSameSize(0); for (unsigned int i=0; i<iSize; ++i) { if ( vecPath1[i] != vecPath2[i]) { iSameSize = i; break; } } m_strRelativePath = ""; for (unsigned int i=0 ; i< (vecPath1.size()-iSameSize) ; ++i) m_strRelativePath += const_cast<char *> (LAST_FOLDER); for (unsigned int i=iSameSize ; i<vecPath2.size() ; ++i) { m_strRelativePath += vecPath2[i]; if( i < (vecPath2.size()-1) ) m_strRelativePath += const_cast<char *> (FOLDER_SEP); } return m_strRelativePath; } 
+1
Aug 16 '13 at 7:52
source share

I needed to do this without Boost, and another std-based solution did not do this for me, so I re-implemented it. When I worked on this, I realized that I had done this before ...

In any case, it is not as complete as some of the others, but it can be useful to people. It depends on Windows; changes to force POSIX to include directory separator and case sensitivity in string comparisons.

Shortly after I got this and worked, I had to port the surrounding functions to Python, so it all came down to os.path.relpath(to, from) .

 static inline bool StringsEqual_i(const std::string& lhs, const std::string& rhs) { return _stricmp(lhs.c_str(), rhs.c_str()) == 0; } static void SplitPath(const std::string& in_path, std::vector<std::string>& split_path) { size_t start = 0; size_t dirsep; do { dirsep = in_path.find_first_of("\\/", start); if (dirsep == std::string::npos) split_path.push_back(std::string(&in_path[start])); else split_path.push_back(std::string(&in_path[start], &in_path[dirsep])); start = dirsep + 1; } while (dirsep != std::string::npos); } /** * Get the relative path from a base location to a target location. * * \param to The target location. * \param from The base location. Must be a directory. * \returns The resulting relative path. */ static std::string GetRelativePath(const std::string& to, const std::string& from) { std::vector<std::string> to_dirs; std::vector<std::string> from_dirs; SplitPath(to, to_dirs); SplitPath(from, from_dirs); std::string output; output.reserve(to.size()); std::vector<std::string>::const_iterator to_it = to_dirs.begin(), to_end = to_dirs.end(), from_it = from_dirs.begin(), from_end = from_dirs.end(); while ((to_it != to_end) && (from_it != from_end) && StringsEqual_i(*to_it, *from_it)) { ++to_it; ++from_it; } while (from_it != from_end) { output += "..\\"; ++from_it; } while (to_it != to_end) { output += *to_it; ++to_it; if (to_it != to_end) output += "\\"; } return output; } 
+1
Apr 30 '14 at 18:29
source share



All Articles