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

本文关键字:文件夹 路径 相对于 何创建 创建 | 更新日期: 2023-09-27 18:21:47

例如,我如何制作这个

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

相对于此文件夹

"C:'RootFolder'SubFolder'"

如果预期结果是

"MoreSubFolder'LastFolder'SomeFile.txt"

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

是的,你可以做到,这很容易,把你的路径想象成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"

在您的示例中,它只是absPath.Substring(relativeTo.Length)

更详细的示例需要从relativeTo返回几个级别,如下所示:

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

生成相对路径的算法如下所示:

  • 删除最长的公共前缀(在这种情况下,它是"C:'RootFolder'SubFolder'"
  • 计算relativeTo中的文件夹数(在这种情况下,为2:"Sibling'Child'"
  • 为每个剩余文件夹插入..'
  • 删除后缀后与绝对路径的剩余部分连接

最终结果如下:

"..'..'MoreSubFolder'LastFolder'SomeFile.txt"

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

Path.GetRelativePath(String,String)方法
返回从一个路径到另一个路径的相对路径。

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

与@TarmoPikaro的答案一样,该实现使用System.IO.Path.GetFullPath来解析在比较之前通过的潜在相对路径。

这背后的意图是通过将基本路径附加到相对路径来解析构建的路径,这是我们在调用GetRelativePath()之前在代码中经常做的。它应该解决以下问题:

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

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

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

在大多数情况下,这将导致MakeRelative的意外结果。出于这个原因,实际上应该首先使用串联或自己对GetFullPath的调用来解析输入参数。

在以下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

.NET Core和.NET在标准库中提供了System.IO.Path.GetRelativePath(string, string)方法。如果你需要在旧的.NET Framework项目中使用该方法,那么你可以使用下面的polyfill,它非常模仿标准的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)方法相同的行为。