web-dev-qa-db-fra.com

Comment "normaliser" un chemin d'accès avec boost :: système de fichiers?

Nous utilisons boost :: filesystem dans notre application. J'ai un chemin "complet" qui est construit en concaténant plusieurs chemins ensemble:

#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();
}

Le chemin final est imprimé comme:

c:\some\deep\application\folder\..\configuration\instance\..\instance\myfile.cfg

C'est un chemin valide, mais quand je l'affiche à l'utilisateur, je préférerais qu'il soit normalisé. (Note: Je ne suis même pas sûr si "normalisé" est le mot correct pour cela). Comme ça:

c:\some\deep\application\configuration\instance\myfile.cfg

Les versions précédentes de Boost avaient une fonction normalize() - mais il semble avoir été déconseillé et supprimé (sans aucune explication).

Y a-t-il une raison pour laquelle je ne devrais pas utiliser la macro BOOST_FILESYSTEM_NO_DEPRECATED? Existe-t-il un autre moyen de faire cela avec la bibliothèque Boost Filesystem? Ou devrais-je écrire du code pour manipuler directement le chemin en tant que chaîne?

36
Mike Willekes

Boost v1.48 et supérieur

Vous pouvez utiliser 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

les versions 1.48 et supérieures fournissent également la fonction boost::filesystem::read_symlink pour la résolution des liens symboliques.

Boost versions antérieures à v1.48

Comme mentionné dans d'autres réponses, vous ne pouvez pas normaliser parce que boost :: système de fichiers ne peut pas suivre les liens symboliques. Cependant, vous pouvez écrire une fonction qui normalise "autant que possible" (en supposant que "." Et ".." sont traités normalement) car boost permet de déterminer si un fichier est un lien symbolique.

Autrement dit, si le parent du ".." est un lien symbolique, vous devez le conserver, sinon vous pouvez le supprimer sans danger et il est probablement toujours sûr de supprimer ".".

Cela ressemble à la manipulation de la chaîne réelle, mais légèrement plus élégant.

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;
}
30
Adam Bowen

Avec la version 3 de boost::filesystem, vous pouvez également essayer de supprimer tous les liens symboliques avec un appel à canonical. Cela ne peut être fait que pour les chemins existants. Ainsi, une fonction qui fonctionne aussi pour les chemins non existants nécessiterait deux étapes (testée sur 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;
}
18
jarzec

Vos plaintes et/ou souhaits concernant canonical ont été traités par Boost 1.60 [ 1 ] avec

path lexically_normal(const path& p);
10
Alexander Shukaev

l'explication est à http://www.boost.org/doc/libs/1_40_0/libs/filesystem/doc/design.htm :

Travailler dans les réalités décrites ci-dessous.

Justification: Ce n'est pas un projet de recherche. Il faut que quelque chose fonctionne sur les plates-formes actuelles, y compris certains des systèmes d'exploitation intégrés avec des systèmes de fichiers limités. En raison de l’importance accordée à la portabilité, une telle bibliothèque serait beaucoup plus utile si elle était normalisée. Cela signifie pouvoir travailler avec une gamme beaucoup plus large de plates-formes uniquement Unix ou Windows et leurs clones.

où la "réalité" applicable à la suppression de normalize est:

Les liens symboliques amènent la forme canonique et normale de certains chemins à représenter différents fichiers ou répertoires. Par exemple, étant donné la hiérarchie des répertoires/a/b/c, avec un lien symbolique dans/un x nommé pointant vers b/c, puis sous les règles de résolution de chemin POSIX, le chemin "/ a/x/.." "/un B". Si "/ a/x/.." était d'abord normalisé à "/ a", la résolution serait incorrecte. (Affaire fournie par Walter Landry.)

la bibliothèque ne peut pas réellement normaliser un chemin sans accès aux systèmes de fichiers sous-jacents, ce qui rend l'opération a) peu fiable b) imprévisible c) erronée d) toutes ces réponses

8
just somebody

C'est toujours là. Continuez à l'utiliser.

J'imagine qu'ils l'ont déconseillé parce que les liens symboliques signifient que le chemin réduit ne correspond pas nécessairement. Si c:\full\path était un lien symbolique vers c:\rough, alors c:\full\path\.. serait c:\ et non c:\full.

3
Jonathan Graehl

Comme la fonction "canonique" ne fonctionne qu'avec les chemins existants, j'ai créé ma propre solution qui divise le chemin en ses parties et compare chaque partie à la suivante. J'utilise cela avec 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;
}

Pour utiliser ceci, voici un exemple sur la façon dont vous l'appelez:

std::cout<<NormalizePath("/home/../home/thatfile/")<<std::endl;