Как "нормализовать" путь с помощью 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? Или я должен написать код для непосредственного управления контуром в виде строки?
Ответы
Ответ 1
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#canonical
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;
}
Ответ 2
В версии 3 boost::filesystem
вы также можете попытаться удалить все символические ссылки, вызвав canonical
. Это можно сделать только для существующих путей, поэтому для функции, которая также работает для несуществующих, потребуется два шага (протестировано на MacOS Lion и обновлено для Windows благодаря комментарию @void.pointer):
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;
}
}
// Make sure the dir separators are correct even on Windows
return result.make_prefered();
}
Ответ 3
Ваши жалобы и/или пожелания о canonical
были рассмотрены Boost 1.60 [1] с
path lexically_normal(const path& p);
Ответ 4
объяснение находится на http://www.boost.org/doc/libs/1_40_0/libs/filesystem/doc/design.htm:
Работайте над реалиями, описанными ниже.
Обоснование: это не исследовательский проект. Потребность в том, что работает на сегодняшних платформах, включая некоторые встроенные операционные системы с ограниченными файловыми системами. Из-за акцента на переносимость такая библиотека была бы гораздо более полезной, если бы она была стандартизирована. Это означает возможность работать с гораздо более широким диапазоном платформ, которые просто Unix или Windows и их клоны.
где "реальность", применимая к удалению normalize
, такова:
Символьные ссылки вызывают каноническую и нормальную форму некоторых путей для представления разных файлов или каталогов. Например, с учетом иерархии каталогов /a/b/c с символической ссылкой в /по имени x, указывающей на b/c, тогда в соответствии с правилами разрешения пути POSIX путь "/a/x/.." должен быть разрешен "/A/B". Если "/a/x/.." были сначала нормированы на "/a", это будет неправильно исправлено. (Дело предоставлено Уолтером Лэндри.)
библиотека не может реально нормализовать путь без доступа к базовым файловым системам, что делает операцию a) ненадежной b) непредсказуемой c) неправильной d) все вышеперечисленное
Ответ 5
Он все еще там. Продолжайте использовать его.
Я предполагаю, что они устарели, потому что символические ссылки означают, что свернутый путь не обязательно эквивалентен. Если c:\full\path
была символической ссылкой на c:\rough
, то c:\full\path\..
будет c:\
, а не c:\full
.
Ответ 6
Поскольку "каноническая" функция работает только с существующими путями, я создал собственное решение, которое разбивает путь на его части и сравнивает каждую часть со следующей. Я использую это с 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;