.net 如何创建相对于特定文件夹的绝对路径?

xyhw6mcr  于 2022-12-14  发布在  .NET
关注(0)|答案(5)|浏览(129)

例如,我如何才能让这个

"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt"

相对于此文件夹

"C:\RootFolder\SubFolder\"

如果期望的结果是

"MoreSubFolder\LastFolder\SomeFile.txt"
gab6jxml

gab6jxml1#

是的,您可以这样做,这很简单,将路径视为URI

Uri fullPath = new Uri(@"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt", UriKind.Absolute);
Uri relRoot = new Uri(@"C:\RootFolder\SubFolder\", UriKind.Absolute);

string relPath = relRoot.MakeRelativeUri(fullPath).ToString();
// relPath == @"MoreSubFolder\LastFolder\SomeFile.txt"
3z6pesqy

3z6pesqy2#

In your example, it's simply absPath.Substring(relativeTo.Length) .
More elaborate example would require going back a few levels from the relativeTo , as follows:

"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt"
"C:\RootFolder\SubFolder\Sibling\Child\"

The algorithm to make a relative path would look as follows:

  • Remove the longest common prefix (in this case, it is "C:\RootFolder\SubFolder\" )
  • Count the number of folders in relativeTo (in this case, it is 2: "Sibling\Child\" )
  • Insert ..\ for each remaining folder
  • Concatenate with the remainder of the absolute path after the suffix removal

The end result looks like this:

"..\..\MoreSubFolder\LastFolder\SomeFile.txt"
wxclj1h5

wxclj1h53#

在以下git仓库中搜索makeRelative后:https://github.com/tapika/syncProj/blob/8ea41ebc11f538a22ed7cfaf59a8b7e0b4c3da37/syncProj.cs#L1685
我们找到了这个解决方案,它在经过一点测试后通过文档得到了增强;)

public static partial class PathUtilities
{
    /// <summary>
    /// Rebases file with path <paramref name="fullPath"/> to folder with <paramref name="baseDir"/>.
    /// </summary>
    /// <param name="fullPath">Full file path (absolute)</param>
    /// <param name="baseDir">Full base directory path (absolute)</param>
    /// <returns>Relative path to file with respect to <paramref name="baseDir"/></returns>
    /// <remarks>Paths are resolved by calling the <seealso cref="System.IO.Path.GetFullPath(string)"/> method before calculating the difference. This will flatten relative path fragments:
    /// <code>
    /// "c:\test\..\test2" => "c:\test2"
    /// </code>
    /// These path framents are expected to be created by concatenating a root folder with a relative path such as this:
    /// <code>
    /// var baseFolder = @"c:\test\";
    /// var virtualPath = @"..\test2";
    /// var fullPath = System.IO.Path.Combine(baseFolder, virtualPath);
    /// </code>
    /// The default file path for the current executing environment will be used for the base resolution for this operation, which may not be appropriate if the input paths are fully relative or relative to different
    /// respective base paths. For this reason we should attempt to resolve absolute input paths <i>before</i> passing through as arguments to this method.
    /// </remarks>
    static public string MakeRelative(string fullPath, string baseDir)
    {
        String pathSep = "\\";
        String itemPath = Path.GetFullPath(fullPath);
        String baseDirPath = Path.GetFullPath(baseDir); // If folder contains upper folder references, they get resolved here. "c:\test\..\test2" => "c:\test2"

        String[] p1 = Regex.Split(itemPath, "[\\\\/]").Where(x => x.Length != 0).ToArray();
        String[] p2 = Regex.Split(baseDir, "[\\\\/]").Where(x => x.Length != 0).ToArray();
        int i = 0;

        for (; i < p1.Length && i < p2.Length; i++)
            if (String.Compare(p1[i], p2[i], true) != 0)    // Case insensitive match
                break;

        if (i == 0)     // Cannot make relative path, for example if resides on different drive
            return itemPath;

        String r = String.Join(pathSep, Enumerable.Repeat("..", p2.Length - i).Concat(p1.Skip(i).Take(p1.Length - i)));
        return r;
    }
}

字符串
此方法的用法:

string itemPath = @"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt";
string baseDirectory = @"C:\RootFolder\SubFolder\";

string result = PathUtilities.MakeRelative(itemPath, baseDirectory);
Console.WriteLine(result);

结果:

MoreSubFolder\LastFolder\SomeFile.txt
0pizxfdo

0pizxfdo4#

对于现代实现,请使用System.IO.Path.GetRelativePath

路径.GetRelativePath(字符串,字符串)方法

返回从一个路径到另一个路径的相对路径。

public static string GetRelativePath (string relativeTo, string path);

在.Net Core 2.0(2017年8月)和.Net Standard 2.1(2018年5月)中引入,实现与@TarmoPikaro发布的答案非常相似
此方法的用法:

string itemPath = @"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt";
string baseDirectory = @"C:\RootFolder\SubFolder\";

string result = System.IO.Path.GetRelativePath(baseDirectory, itemPath);
Console.WriteLine(result);

结果:

MoreSubFolder\LastFolder\SomeFile.txt

answer from @TarmoPikaro一样,此实现使用System.IO.Path.GetFullPath来解析在比较之前可能经过的相对路径。
这样做的目的是解析通过在 base path 后面附加 relative path 构建的路径,这是我们在调用GetRelativePath()之前经常做的事情。

"c:\test\..\test2" => "c:\test2"

这是意料之中的,但如果输入路径是完全相对的,路径将被解析为当前工作文件夹,在我的测试应用程序中,这看起来像这样:

".\test2" => "D:\Source\Repos\MakeRoot\bin\Debug\net6.0\test2"

在大多数情况下,这会导致MakeRelative产生意外的结果。因此,实际上应该先使用连接或自己调用GetFullPath来解析输入参数。

beq87vna

beq87vna5#

.NET Core和.NET在标准库中提供了System.IO.Path.GetRelativePath(string, string)方法。如果您需要在较早的.NET Framework项目中使用该方法,则可以使用以下多边形填充,它非常接近标准BCL行为:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

static class PathUtil
{
    public static string GetRelativePath(string relativeTo, string path)
    {
#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
        return Path.GetRelativePath(relativeTo, path);
#else
        return GetRelativePathPolyfill(relativeTo, path);
#endif
    }

#if !(NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER)
    static string GetRelativePathPolyfill(string relativeTo, string path)
    {
        path = Path.GetFullPath(path);
        relativeTo = Path.GetFullPath(relativeTo);

        var separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
        IReadOnlyList<string> p1 = path.Split(separators);
        IReadOnlyList<string> p2 = relativeTo.Split(separators, StringSplitOptions.RemoveEmptyEntries);

        var sc = StringComparison;

        int i;
        int n = Math.Min(p1.Count, p2.Count);
        for (i = 0; i < n; i++)
            if (!string.Equals(p1[i], p2[i], sc))
                break;

        if (i == 0)
        {
            // Cannot make a relative path, for example if the path resides on another drive.
            return path;
        }

        p1 = p1.Skip(i).Take(p1.Count - i).ToList();

        if (p1.Count == 1 && p1[0].Length == 0)
            p1 = Array.Empty<string>();

        string relativePath = string.Join(
            new string(Path.DirectorySeparatorChar, 1),
            Enumerable.Repeat("..", p2.Count - i).Concat(p1));

        if (relativePath.Length == 0)
            relativePath = ".";

        return relativePath;
    }

    static StringComparison StringComparison =>
        IsCaseSensitive ?
            StringComparison.Ordinal :
            StringComparison.OrdinalIgnoreCase;

    static bool IsCaseSensitive =>
        !(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
        RuntimeInformation.IsOSPlatform(OSPlatform.OSX));
#endif
}

为了提供与System.IO.Path.GetRelativePath(string, string)方法相同的行为,上面的代码涵盖了相当多的边缘情况。

相关问题