如何检查一个路径是否是另一个路径的子路径?

18

如何检查一个路径是否是另一个路径的子路径?
仅仅检查子字符串并不可行,因为有可能会出现 . 和 .. 等项目。

9个回答

19

很遗憾,这不像StartsWith那样简单。

以下是一个更好的答案,改编自这个重复的问题。我将其作为扩展方法以方便使用。此外,我使用了暴力的catch,因为几乎任何访问文件系统的方法都可以基于用户权限失败。

public static bool IsSubDirectoryOf(this string candidate, string other)
{
    var isChild = false;
    try
    {
        var candidateInfo = new DirectoryInfo(candidate);
        var otherInfo = new DirectoryInfo(other);

        while (candidateInfo.Parent != null)
        {
            if (candidateInfo.Parent.FullName == otherInfo.FullName)
            {
                isChild = true;
                break;
            }
            else candidateInfo = candidateInfo.Parent;
        }
    }
    catch (Exception error)
    {
        var message = String.Format("Unable to check directories {0} and {1}: {2}", candidate, other, error);
        Trace.WriteLine(message);
    }

    return isChild;
}

2
吞掉异常(而不知道该方法的使用方式)似乎是一种不好的做法。例如,假设该方法用于防止从包含敏感数据的某个文件夹上传信息,现在当发生异常时,数据将被上传。 - Herman
2
这对于区分大小写的路径也不起作用。IsSubDirectoryOf(@"c:\a\b", @"c:\A") 返回 false。 - Gerd K
5
这个答案没有解释为什么明显可行的方法 ".StartsWith" 不起作用,而只是指出了这一点,并解释了更复杂的解决方案。(-1) - Hele
请注意,如果您尝试比较不同大小写的路径和其他一些边缘情况,这将无法正常工作。遗憾的是,DirectoryInfo无法处理Windows文件系统的所有怪癖。 - jrh
2
这种方法需要两个目录都存在,这是一个严重的限制。 - user626528
为什么不使用“System.IO.Path.GetFullPath”? - GoWiser

14
任何基于字符串的解决方案都有可能受到目录遍历攻击或类似尾部斜杠的正确性问题的影响。不幸的是,.NET Path 类没有提供这种功能,但是 Uri 类提供了Uri.IsBaseOf()函数来实现此功能。
    Uri potentialBase = new Uri(@"c:\dir1\");

    Uri regular = new Uri(@"c:\dir1\dir2");

    Uri confusing = new Uri(@"c:\temp\..\dir1\dir2");

    Uri malicious = new Uri(@"c:\dir1\..\windows\system32\");

    Console.WriteLine(potentialBase.IsBaseOf(regular));   // True
    Console.WriteLine(potentialBase.IsBaseOf(confusing)); // True
    Console.WriteLine(potentialBase.IsBaseOf(malicious)); // False

4
"IsBaseOf" 在这种情况下似乎无法起作用。给定输入 'C:\somerandomdir''C:\someotherdir',我得到了一个真结果。 - jpmc26
3
@jpmc26,这是因为您没有斜线。无法知道“somerandomdir”是目录名还是文件名。如果您想处理此情况,请在调用之前添加一个斜线。 - Oren Melzer
11
为什么这很重要?无论如何,一个文件都不能成为另一个文件的基础。为什么IsBaseOf会尝试截取它认为是文件名的内容,做出如此奇怪的猜测,而不是回答调用者所问的问题?如果有这样的警告和奇怪的细节需要担心,你的回答至少应该涉及到它们。 - jpmc26
这是最安全和最好的解决方案。 - Gene Pavlovsky
1
为什么不使用 System.IO.Path.GetFullPath?您暗示它不能使用。我很好奇为什么。 - GoWiser

2

我曾经使用过这样的扩展方法:

最初的回答
    /// <summary>
    /// Check if a directory is the base of another
    /// </summary>
    /// <param name="root">Candidate root</param>
    /// <param name="child">Child folder</param>
    public static bool IsBaseOf(this DirectoryInfo root, DirectoryInfo child)
    {
        var directoryPath = EndsWithSeparator(new Uri(child.FullName).AbsolutePath);
        var rootPath = EndsWithSeparator(new Uri(root.FullName).AbsolutePath);
        return directoryPath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase);
    }

    private static string EndsWithSeparator(string absolutePath)
    {
        return absolutePath?.TrimEnd('/','\\') + "/";
    }

为什么不使用 System.IO.Path.GetFullPath - GoWiser
引用文档中的话:“如果路径存在,则调用者必须具有获取路径信息的权限。请注意,与Path类的大多数成员不同,此方法访问文件系统。” 鉴于我的扩展方法接收DirectoryInfo实例,这已经是一个确定的事情,FullName返回的也差不多。虽然可以为字符串创建一个IsBase扩展参数,但我太懒了。 - Siderite Zackwehdex
在你回答问题后,我创造了下面的代码。它可以处理不存在的路径。谢谢你的回复。 - GoWiser

0
这是我的解决方案:
// length parentDirectory <= length subDirectory
static private bool isParentDirectory(string parentDirectory, string subDirectory)
{
    if (!subDirectory.Contains(parentDirectory)) return false;

    string[] folders1 = subDirectory.Split('\\');
    string[] folders2 = parentDirectory.Split('\\');

    for (int i = 0; i < folders2.Count(); i++)
    {
        if (folders2[i] != folders1[i])
            return false;
    }
    return true;
}

欢迎来到StackOverflow。请提供一些解释,而不仅仅是代码。 - Peter Csala
这有一个问题,它假设文件分隔符是\,但在运行于Linux的.Net Core上,文件分隔符可能是/。而且似乎将其拆分,然后比较每个段是毫无意义的。这不就相当于StartsWith吗? - ErikE

0

IsSubPath函数会在childparent的子目录时返回true。

public static bool IsSubPath(string parent, string child)
{
    try
    {
        parent = Path.GetFullPath(parent);
        if (!parent.EndsWith(Path.DirectorySeparatorChar.ToString()))
            parent = parent + Path.DirectorySeparatorChar;
        child = Path.GetFullPath(child);
        if (child.Length <= parent.Length)
            return false;
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            return child.StartsWith(parent, StringComparison.OrdinalIgnoreCase);
        else
            return child.StartsWith(parent);
    }
    catch
    {
        return false;
    }
}

该函数也适用于不存在的路径。

0

我发现这适用于Windows:

if (pathA.Equals(pathB, StringComparison.OrdinalIgnoreCase) ||
    pathA.StartsWith(pathB + "\\", StringComparison.OrdinalIgnoreCase))

如果您的路径可能带有尾随字符,您可以先像这样将它们规范化:
pathA = Path.GetFullPath(pathA);
pathB = Path.GetFullPath(pathB);

-1
在C#中,您可以这样做:
string cp = Path.GetFullPath(childPath);
string pp = Path.GetFullPath(parentPath);

if(pp.StartsWith(cp))
    return true;
else
    return false;

3
我建议使用 "StartsWith" 的重载版本来忽略大小写。 - Chad
@Chad GetFullPath会返回相同的大小写,因此在这里忽略大小写是没有用的。 - techExplorer
@techExplorer 我刚测试了一下GetFullPath函数返回值是否与原路径大小写相同,结果发现并不相同。还有一个问题是如何比较父目录 C:\asd1 和子目录 C:\asd11 - GoWiser

-2

我曾经遇到过同样的问题。如果你有路径字符串,可以使用StartWith()方法。

if (pathA.StartsWith (pathB + "\\")) {

虽然我不确定它是否跨平台,但它可以在PC上运行


-3

这是一种方法,您有路径A和B,请使用Path.GetFullPath()函数将它们转换为完整路径。接下来检查一个完整路径是否是另一个的起始子字符串。

所以就是这样。

if (Path.GetFullPath(A).StartsWith(Path.GetFullPath(B)) ||
    Path.GetFullPath(B).StartsWith(Path.GetFullPath(A)))
   { /* ... do your magic ... */ }

15
如果 AC:\my\dirBC:\my\dir2,这段代码是否能正确运行呢?这应该是 false,但我想 Path.GetFullPath(B).StartsWith(Path.GetFullPath(A)) 会返回 true - jpmc26
2
你可以在实际目录名称后面添加结束斜杠。如果不区分大小写,则“starts with”将起作用。 - pinkfloydx33
@jrh 只有提问者才能取消接受答案。 - jpmc26
2
@jrh 我相信 @pinkfloydx33 的建议可以解决这个答案中的问题。如果能编辑答案并加入该建议将会很好,但是 .StartsWith 似乎是其他回答中不错、更简单的替代方案。更重要的是,其他回答没有提供任何确切的理由说明为什么 .StartsWith 不起作用。@Charlie 给出的第二高票答案没有解释为什么 .StartsWith 不起作用,只是宣称了一下。 - Hele
1
@jrh如你所发现,GetFullPath处理"..",而尾部斜杠问题的解决方法就是这样做$"{input.TrimEnd(Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar)}"。我理解为什么你觉得DirectoryInfo是正确的方法,但它会更慢,而且通常是不必要的。此外,.StartsWith似乎是直观的解决方案,我不认为这个答案应该得到负分。 - Hele
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接