Get Relative Path from Comparing Two Absolute Paths

Get relative path from comparing two absolute paths

os.path.commonprefix() and os.path.relpath() are your friends:

>>> print os.path.commonprefix(['/usr/var/log', '/usr/var/security'])
'/usr/var'
>>> print os.path.commonprefix(['/tmp', '/usr/var']) # No common prefix: the root is the common prefix
'/'

You can thus test whether the common prefix is one of the paths, i.e. if one of the paths is a common ancestor:

paths = […, …, …]
common_prefix = os.path.commonprefix(list_of_paths)
if common_prefix in paths:

You can then find the relative paths:

relative_paths = [os.path.relpath(path, common_prefix) for path in paths]

You can even handle more than two paths, with this method, and test whether all the paths are all below one of them.

PS: depending on how your paths look like, you might want to perform some normalization first (this is useful in situations where one does not know whether they always end with '/' or not, or if some of the paths are relative). Relevant functions include os.path.abspath() and os.path.normpath().

PPS: as Peter Briggs mentioned in the comments, the simple approach described above can fail:

>>> os.path.commonprefix(['/usr/var', '/usr/var2/log'])
'/usr/var'

even though /usr/var is not a common prefix of the paths. Forcing all paths to end with '/' before calling commonprefix() solves this (specific) problem.

PPPS: as bluenote10 mentioned, adding a slash does not solve the general problem. Here is his followup question: How to circumvent the fallacy of Python's os.path.commonprefix?

PPPPS: starting with Python 3.4, we have pathlib, a module that provides a saner path manipulation environment. I guess that the common prefix of a set of paths can be obtained by getting all the prefixes of each path (with PurePath.parents()), taking the intersection of all these parent sets, and selecting the longest common prefix.

PPPPPS: Python 3.5 introduced a proper solution to this question: os.path.commonpath(), which returns a valid path.

Comparing relative and absolute filepath, extracting only the relative portion

You can use pathlib to manipulate paths, starting with Python 3.4:

from pathlib import WindowsPath

WindowsPath(r"C:\Work\Project1\sourcedata").relative_to(r"C:\Work\Project1")
# WindowsPath('sourcedata')

WindowsPath(r"C:\Work\Project1\outputs\1\hello").relative_to(r"C:\Work\Project1")
# WindowsPath('outputs/1/hello')

How to construct a relative path in Java from two absolute paths (or URLs)?

It's a little roundabout, but why not use URI? It has a relativize method which does all the necessary checks for you.

String path = "/var/data/stuff/xyz.dat";
String base = "/var/data";
String relative = new File(base).toURI().relativize(new File(path).toURI()).getPath();
// relative == "stuff/xyz.dat"

Please note that for file path there's java.nio.file.Path#relativize since Java 1.7, as pointed out by @Jirka Meluzin in the other answer.

Get relative path from two absolute paths

As of version 1.60.0 boost.filesystem does support this. You're looking for the member function path lexically_relative(const path& p) const.

Original, pre-1.60.0 answer below.


Boost doesn't support this; it's an open issue — #1976 (Inverse function for complete) — that nevertheless doesn't seem to be getting much traction.

Here's a vaguely naive workaround that seems to do the trick (not sure whether 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;
}

Construct a relative path in JavaScript from two absolute paths

If you're running this on the server with node.js:

http://nodejs.org/api/path.html#path_path_relative_from_to

This is their implementation:

https://github.com/joyent/node/blob/master/lib/path.js#L233

This should work in the browser without really any changes. It's been battle-tested, so it already handles edge cases. It's not a one-liner, but it works flawlessly, which I think is more important. The POSIX version isn't bad, if that's the only think you need to support.

Given two absolute paths, how can I express one of the paths relative to the other?

This now exists as the pathdiff crate, using the code from kennytm's answer

You can use it as:

extern crate pathdiff;

pathdiff::diff_paths(path, base);

where base is where the relative path should be applied to obtain path



Related Topics



Leave a reply



Submit