How do I normalize a pathname using boost::filesystem?
Boost v1.48 and above
You can use boost::filesystem::canonical
:
path canonical(const path& p, const path& base = current_path());
path canonical(const path& p, system::error_code& ec);
path canonical(const path& p, const path& base, system::error_code& ec);
http://www.boost.org/doc/libs/1_48_0/libs/filesystem/v3/doc/reference.html#canonical
v1.48 and above also provide the boost::filesystem::read_symlink
function for resolving symbolic links.
Boost versions prior to v1.48
As mentioned in other answers, you can't normalise because boost::filesystem can't follow symbolic links. However, you can write a function that normalises "as much as possible" (assuming "." and ".." are treated normally) because boost offers the ability to determine whether or not a file is a symbolic link.
That is to say, if the parent of the ".." is a symbolic link then you have to retain it, otherwise it is probably safe to drop it and it's probably always safe to remove ".".
It's similar to manipulating the actual string, but slightly more elegant.
boost::filesystem::path resolve(
const boost::filesystem::path& p,
const boost::filesystem::path& base = boost::filesystem::current_path())
{
boost::filesystem::path abs_p = boost::filesystem::absolute(p,base);
boost::filesystem::path result;
for(boost::filesystem::path::iterator it=abs_p.begin();
it!=abs_p.end();
++it)
{
if(*it == "..")
{
// /a/b/.. is not necessarily /a if b is a symbolic link
if(boost::filesystem::is_symlink(result) )
result /= *it;
// /a/b/../.. is not /a/b/.. under most circumstances
// We can end up with ..s in our result because of symbolic links
else if(result.filename() == "..")
result /= *it;
// Otherwise it should be safe to resolve the parent
else
result = result.parent_path();
}
else if(*it == ".")
{
// Ignore
}
else
{
// Just cat other path entries
result /= *it;
}
}
return result;
}
Using boost::filesystem::path as a key in an std::map
To make paths comparable you need to convert them to the canonical representation and then compare via string. A canonical path is absolute, normalized and has symbolic links removed. Boost offers canonical AFAIK. Because symbolic links need to be resolved, calling canonical requires access to the filesystem.
boost::filesystem::path changes base variable with operator /=
As @cpplearner answerd in a comment to my question, I'm quoting him and marking this as answer
Just use operator/ ? – cpplearner
How to combine two boost::filesystem::path's?
If you are looking to make a relative path absolute with respect to some directory (often the current working directory), there is a function to do this:
http://www.boost.org/doc/libs/1_64_0/libs/filesystem/doc/reference.html#absolute
see also
canonical
boost filesystem 3 path inclusion check
*path1.begin() == *path2.begin()
This will however mean that "c:/foo" shares a base with "c:/bar", which might be unintended.
for( boost::filesystem::path::iterator itrLeft( path1.begin() ), itrRight( path2.begin() ); *itrLeft == *itrRight && itrLeft != path1.end() && itrRight != path2.end(); ++itrLeft, ++itrRight )
This way you can see how many atoms are matching, I suggest using boost::filesystem::absolute first if you want to make it robust.
boost::filesystem get relative path
Taken from a link found by following the ticket Nicol linked to:
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;
}
Stick that in a header file and it should do what you want.
Sample call:
boost::filesystem::path a("foo/bar"), b("foo/test/korv.txt");
std::cout << make_relative( a, b ).string() << std::endl;
Related Topics
Fastest Way to Get the Integer Part of Sqrt(N)
What Should I Know About Structured Exceptions (Seh) in C++
Implicit VS Explicit Conversion
Changing Dpi Scaling Size of Display Make Qt Application's Font Size Get Rendered Bigger
Can Lambda Functions Be Recursive
Lambda Functions as Base Classes
How to Easily Make Std::Cout Thread-Safe
Clang C++ Cross Compiler - Generating Windows Executable from MAC Os X
Why Doesn't This Reinterpret_Cast Compile
Visual Studio 2010 & 2008 Can't Handle Source Files with Identical Names in Different Folders
Use-Cases of Pure Virtual Functions with Body
Debug Assertion Failed! Expression: _Acrt_First_Block == Header
Template Metaprogramming - Difference Between Using Enum Hack and Static Const