web-dev-qa-db-ja.com

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
_

これは有効なパスですが、ユーザーに表示するときはnormalized。にすることをお勧めします。 。このような:

_c:\some\deep\application\configuration\instance\myfile.cfg
_

Boostの以前のバージョンにはnormalize()関数がありましたが、廃止され、削除されたようです(説明なし)。

_BOOST_FILESYSTEM_NO_DEPRECATED_マクロを使用しない理由はありますか? Boost Filesystemライブラリでこれを行う別の方法はありますか?または、パスを文字列として直接操作するコードを記述する必要がありますか?

37
Mike Willekes

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バージョン

他の回答で述べたように、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;
}
31
Adam Bowen

boost::filesystemのバージョン3では、canonicalを呼び出してすべてのシンボリックリンクを削除することもできます。これは既存のパスに対してのみ実行できるため、存在しないパスに対しても機能する関数には2つの手順が必要です(MacOS Lionでテストされ、@ void.pointerのコメントによりWindows用に更新されました)。

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

canonicalに関する苦情や要望は、Boost 1.60 [ 1 ]によって対処されました。

path lexically_normal(const path& p);
13

説明は 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」に最初に正規化された場合、正しく解決されませんでした。 (ウォルターランドリーから提供されたケース。)

ライブラリは、基になるファイルシステムにアクセスせずにパスを正規化できないため、操作がa)信頼できないb)予測できないc)間違っているd)上記すべて

7
just somebody

それはまだそこにあります。それを使い続けなさい。

シンボリックリンクは、折りたたまれたパスが必ずしも同等ではないことを意味するため、非推奨になっていると思います。 c:\full\pathc:\roughへのシンボリックリンクである場合、c:\full\path\..c:\であり、c:\fullではありません。

3
Jonathan Graehl

「正規」関数は存在するパスでのみ機能するため、パスをその部分に分割し、すべての部分を次の部分と比較する独自のソリューションを作成しました。これを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;