Как "нормализовать" путь, используя boost::filesystem?
Мы используем boost::filesystem в нашем приложении. У меня есть "полный" путь, который создается путем объединения нескольких путей вместе:
#include <boost/filesystem/operations.hpp>
#include <iostream>
namespace bf = boost::filesystem;
int main()
{
bf::path root("c:\\some\\deep\\application\\folder");
bf::path subdir("..\\configuration\\instance");
bf::path cfgfile("..\\instance\\myfile.cfg");
bf::path final ( root / subdir / cfgfile);
cout << final.file_string();
}
Окончательный путь печатается как:
c:\some\deep\application\folder\..\configuration\instance\..\instance\myfile.cfg
Это допустимый путь, но когда я показываю его пользователю, я бы предпочел, чтобы он был нормализован. (Примечание: я даже не уверен, что "нормализовано" является правильным словом для этого). Как это:
c:\some\deep\application\configuration\instance\myfile.cfg
Более ранние версии Boost имели normalize()
функция - но, кажется, устарела и удалена (без каких-либо объяснений).
Есть ли причина, по которой я не должен использовать BOOST_FILESYSTEM_NO_DEPRECATED
макрос? Есть ли альтернативный способ сделать это с помощью библиотеки Boost Filesystem? Или я должен написать код для прямого манипулирования путем в виде строки?
6 ответов
Boost v1.48 и выше
Ты можешь использовать 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
v1.48 и выше также предоставляют boost::filesystem::read_symlink
функция для разрешения символических ссылок.
Повышение версии до v1.48
Как упоминалось в других ответах, вы не можете нормализовать, потому что boost::filesystem не может переходить по символическим ссылкам. Тем не менее, вы можете написать функцию, которая нормализует "как можно больше" (при условии, что "." И ".." обрабатываются нормально), так как boost предлагает возможность определить, является ли файл символической ссылкой.
То есть, если родительский элемент ".." является символической ссылкой, вам нужно сохранить ее, в противном случае, вероятно, ее можно отбросить и, вероятно, всегда безопасно удалить ".".
Это похоже на манипулирование реальной строкой, но немного более элегантно.
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;
}
С версией 3 boost::filesystem
Вы также можете попытаться удалить все символические ссылки с помощью вызова canonical
, Это может быть сделано только для существующих путей, поэтому для функции, которая также работает для несуществующих, потребуется два шага (протестировано на MacOS Lion):
boost::filesystem::path normalize(const boost::filesystem::path &path) {
boost::filesystem::path absPath = absolute(path);
boost::filesystem::path::iterator it = absPath.begin();
boost::filesystem::path result = *it++;
// Get canonical version of the existing part
for (; exists(result / *it) && it != absPath.end(); ++it) {
result /= *it;
}
result = canonical(result);
// For the rest remove ".." and "." in a path with no symlinks
for (; it != absPath.end(); ++it) {
// Just move back on ../
if (*it == "..") {
result = result.parent_path();
}
// Ignore "."
else if (*it != ".") {
// Just cat other path entries
result /= *it;
}
}
return result;
}
Ваши жалобы и / или пожелания по поводу canonical
были повышены 1.60 [ 1] с
path lexically_normal(const path& p);
Объяснение на http://www.boost.org/doc/libs/1_40_0/libs/filesystem/doc/design.htm:
Работайте в реалиях, описанных ниже.
Обоснование: это не исследовательский проект. Нужно что-то, что работает на современных платформах, включая некоторые из встроенных операционных систем с ограниченными файловыми системами. Из-за акцента на переносимость, такая библиотека была бы намного более полезной, если бы была стандартизирована. Это означает возможность работать с гораздо более широким спектром платформ, включая Unix или Windows и их клоны.
где "реальность" применима к удалению normalize
является:
Символические ссылки вызывают каноническую и нормальную форму некоторых путей для представления различных файлов или каталогов. Например, учитывая иерархию каталогов / a / b / c с символической ссылкой в / a с именем x, указывающей на b / c, то в соответствии с правилами разрешения имен POSIX путь "/a/x/.." должен разрешаться в "/ A / B". Если бы "/a/x/.." сначала было нормализовано до "/a", это бы разрешило неправильно. (Дело предоставлено Уолтером Лэндри.)
библиотека не может реально нормализовать путь без доступа к базовым файловым системам, что делает операцию а) ненадежной; б) непредсказуемой; в) неправильной; г) всем вышеперечисленным.
Это все еще там. Продолжайте использовать это.
Я предполагаю, что они устарели, потому что символические ссылки означают, что свернутый путь не обязательно эквивалентен. Если c:\full\path
были символической ссылкой на c:\rough
, затем c:\full\path\..
было бы c:\
не c:\full
,
Поскольку "каноническая" функция работает только с существующими путями, я создал собственное решение, которое разделяет путь на его части и сравнивает каждую часть со следующей. Я использую это с Boost 1.55.
typedef boost::filesystem::path PathType;
template <template <typename T, typename = std::allocator<T> > class Container>
Container<PathType> SplitPath(const PathType& path)
{
Container<PathType> ret;
long the_size = std::distance(path.begin(),path.end());
if(the_size == 0)
return Container<PathType>();
ret.resize(the_size);
std::copy(path.begin(),path.end(),ret.begin());
return ret;
}
PathType NormalizePath(const PathType& path)
{
PathType ret;
std::list<PathType> splitPath = SplitPath<std::list>(path);
for(std::list<PathType>::iterator it = (path.is_absolute() ? ++splitPath.begin() : splitPath.begin()); it != splitPath.end(); ++it)
{
std::list<PathType>::iterator it_next = it;
++it_next;
if(it_next == splitPath.end())
break;
if(*it_next == "..")
{
it = splitPath.erase(it);
it = splitPath.erase(it);
}
}
for(std::list<PathType>::iterator it = splitPath.begin(); it != splitPath.end(); ++it)
{
ret /= *it;
}
return ret;
}
Чтобы использовать это, вот пример того, как вы называете это:
std::cout<<NormalizePath("/home/../home/thatfile/")<<std::endl;