Как "нормализовать" путь, используя 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;
Другие вопросы по тегам