web-dev-qa-db-ja.com

現在の作業ディレクトリからの相対パスを取得していますか?

コマンドラインで指定されたファイルを処理するためのコンソールユーティリティを書いていますが、Google/Stack Overflowで解決できない問題に遭遇しました。ドライブ文字を含む完全なパスが指定されている場合、現在の作業ディレクトリに相対するようにそのパスを再フォーマットするにはどうすればよいですか?

VirtualPathUtility.MakeRelative関数に似たものがなければなりませんが、もしあれば、それは私を避けます。

72
Amy

切り替わるスラッシュを気にしない場合は、Uriを[ab]使用できます。

Uri file = new Uri(@"c:\foo\bar\blop\blap.txt");
// Must end in a slash to indicate folder
Uri folder = new Uri(@"c:\foo\bar\");
string relativePath = 
Uri.UnescapeDataString(
    folder.MakeRelativeUri(file)
        .ToString()
        .Replace('/', Path.DirectorySeparatorChar)
    );

関数/メソッドとして:

string GetRelativePath(string filespec, string folder)
{
    Uri pathUri = new Uri(filespec);
    // Folders must end in a slash
    if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString()))
    {
        folder += Path.DirectorySeparatorChar;
    }
    Uri folderUri = new Uri(folder);
    return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}
128
Marc Gravell

Environment.CurrentDirectoryを使用して現在のディレクトリを取得し、FileSystemInfo.FullPathを使用して任意の場所へのフルパスを取得できます。したがって、現在のディレクトリと問題のファイルの両方を完全に修飾し、完全なファイル名がディレクトリ名で始まるかどうかを確認します-ある場合は、ディレクトリ名の長さに基づいて適切なサブストリングを取得します。

サンプルコードを次に示します。

using System;
using System.IO;

class Program
{
    public static void Main(string[] args)
    {
        string currentDir = Environment.CurrentDirectory;
        DirectoryInfo directory = new DirectoryInfo(currentDir);
        FileInfo file = new FileInfo(args[0]);

        string fullDirectory = directory.FullName;
        string fullFile = file.FullName;

        if (!fullFile.StartsWith(fullDirectory))
        {
            Console.WriteLine("Unable to make relative path");
        }
        else
        {
            // The +1 is to avoid the directory separator
            Console.WriteLine("Relative path: {0}",
                              fullFile.Substring(fullDirectory.Length+1));
        }
    }
}

私はそれが世界で最も堅牢なものだとは言っていません(シンボリックリンクはおそらくそれを混乱させる可能性があります)が、これがたまに使用するツールにすぎない場合はおそらく大丈夫です。

37
Jon Skeet
public string MakeRelativePath(string workingDirectory, string fullPath)
{
    string result = string.Empty;
    int offset;

    // this is the easy case.  The file is inside of the working directory.
    if( fullPath.StartsWith(workingDirectory) )
    {
        return fullPath.Substring(workingDirectory.Length + 1);
    }

    // the hard case has to back out of the working directory
    string[] baseDirs = workingDirectory.Split(new char[] { ':', '\\', '/' });
    string[] fileDirs = fullPath.Split(new char[] { ':', '\\', '/' });

    // if we failed to split (empty strings?) or the drive letter does not match
    if( baseDirs.Length <= 0 || fileDirs.Length <= 0 || baseDirs[0] != fileDirs[0] )
    {
        // can't create a relative path between separate harddrives/partitions.
        return fullPath;
    }

    // skip all leading directories that match
    for (offset = 1; offset < baseDirs.Length; offset++)
    {
        if (baseDirs[offset] != fileDirs[offset])
            break;
    }

    // back out of the working directory
    for (int i = 0; i < (baseDirs.Length - offset); i++)
    {
        result += "..\\";
    }

    // step into the file path
    for (int i = offset; i < fileDirs.Length-1; i++)
    {
        result += fileDirs[i] + "\\";
    }

    // append the file
    result += fileDirs[fileDirs.Length - 1];

    return result;
}

このコードはおそらく防弾ではありませんが、これが私が思いついたものです。少し堅牢です。 2つのパスを取り、パスAに対してパスBを返します。

例:

MakeRelativePath("c:\\dev\\foo\\bar", "c:\\dev\\junk\\readme.txt")
//returns: "..\\..\\junk\\readme.txt"

MakeRelativePath("c:\\dev\\foo\\bar", "c:\\dev\\foo\\bar\\docs\\readme.txt")
//returns: "docs\\readme.txt"
8
Ben

ここの他の回答のおかげで、いくつかの実験の後、非常に便利な拡張メソッドを作成しました:

public static string GetRelativePathFrom(this FileSystemInfo to, FileSystemInfo from)
{
    return from.GetRelativePathTo(to);
}

public static string GetRelativePathTo(this FileSystemInfo from, FileSystemInfo to)
{
    Func<FileSystemInfo, string> getPath = fsi =>
    {
        var d = fsi as DirectoryInfo;
        return d == null ? fsi.FullName : d.FullName.TrimEnd('\\') + "\\";
    };

    var fromPath = getPath(from);
    var toPath = getPath(to);

    var fromUri = new Uri(fromPath);
    var toUri = new Uri(toPath);

    var relativeUri = fromUri.MakeRelativeUri(toUri);
    var relativePath = Uri.UnescapeDataString(relativeUri.ToString());

    return relativePath.Replace('/', Path.DirectorySeparatorChar);
}

重要なポイント:

  • メソッドパラメータとしてFileInfoおよびDirectoryInfoを使用して、処理対象について曖昧さがないようにします。 Uri.MakeRelativeUriは、ディレクトリが末尾のスラッシュで終わることを想定しています。
  • DirectoryInfo.FullNameは、末尾のスラッシュを正規化しません。コンストラクターで使用されたパスを出力します。この拡張メソッドはあなたのためにそれの世話をします。
4
Ronnie Overby

いくつかの制限を設けてこれを行う方法 もあります。これは記事のコードです:

public string RelativePath(string absPath, string relTo)
    {
        string[] absDirs = absPath.Split('\\');
        string[] relDirs = relTo.Split('\\');
        // Get the shortest of the two paths 
        int len = absDirs.Length < relDirs.Length ? absDirs.Length : relDirs.Length;
        // Use to determine where in the loop we exited 
        int lastCommonRoot = -1; int index;
        // Find common root 
        for (index = 0; index < len; index++)
        {
            if (absDirs[index] == relDirs[index])
                lastCommonRoot = index;
            else break;
        }
        // If we didn't find a common prefix then throw 
        if (lastCommonRoot == -1)
        {
            throw new ArgumentException("Paths do not have a common base");
        }
        // Build up the relative path 
        StringBuilder relativePath = new StringBuilder();
        // Add on the .. 
        for (index = lastCommonRoot + 1; index < absDirs.Length; index++)
        {
            if (absDirs[index].Length > 0) relativePath.Append("..\\");
        }
        // Add on the folders 
        for (index = lastCommonRoot + 1; index < relDirs.Length - 1; index++)
        {
            relativePath.Append(relDirs[index] + "\\");
        }
        relativePath.Append(relDirs[relDirs.Length - 1]);
        return relativePath.ToString();
    }

このコードを実行するとき:

string path1 = @"C:\Inetpub\wwwroot\Project1\Master\Dev\SubDir1"; 
string path2 = @"C:\Inetpub\wwwroot\Project1\Master\Dev\SubDir2\SubDirIWant";

System.Console.WriteLine (RelativePath(path1, path2));
System.Console.WriteLine (RelativePath(path2, path1));

それは印刷されます:

..\SubDir2\SubDirIWant
..\..\SubDir1
2
Dmitry Pavlov