给定完整路径,检查路径是否是其他路径的子目录,否则

问题描述 投票:0回答:11

我有 2 个字符串 - dir1 和 dir2,我需要检查其中一个是否是另一个的子目录。我尝试使用 Contains 方法:

dir1.contains(dir2);

但这也返回 true,如果目录具有相似的名称,例如 -

c:\abc
c:\abc1
不是子目录,则 bet 返回 true。一定有更好的方法。

c# .net directory
11个回答
39
投票
DirectoryInfo di1 = new DirectoryInfo(dir1);
DirectoryInfo di2 = new DirectoryInfo(dir2);
bool isParent = di2.Parent.FullName == di1.FullName;

或者在循环中允许嵌套子目录,即 C: oo ar azC: oo :

的子目录
DirectoryInfo di1 = new DirectoryInfo(dir1);
DirectoryInfo di2 = new DirectoryInfo(dir2);
bool isParent = false;
while (di2.Parent != null)
{
    if (di2.Parent.FullName == di1.FullName)
    {
        isParent = true;
        break;
    }
    else di2 = di2.Parent;
}

34
投票
  • 不区分大小写
  • 允许混合使用
    \
    /
    文件夹分隔符
  • 容忍路径中的
    ..\
  • 避免匹配部分文件夹名称(
    c:\foobar
    不是
    c:\foo
    的子路径)

注意:这仅匹配路径字符串,不适用于文件系统中的符号链接和其他类型的链接。

代码:

public static class StringExtensions
{
    /// <summary>
    /// Returns true if <paramref name="path"/> starts with the path <paramref name="baseDirPath"/>.
    /// The comparison is case-insensitive, handles / and \ slashes as folder separators and
    /// only matches if the base dir folder name is matched exactly ("c:\foobar\file.txt" is not a sub path of "c:\foo").
    /// </summary>
    public static bool IsSubPathOf(this string path, string baseDirPath)
    {
        string normalizedPath = Path.GetFullPath(path.Replace('/', '\\')
            .WithEnding("\\"));

        string normalizedBaseDirPath = Path.GetFullPath(baseDirPath.Replace('/', '\\')
            .WithEnding("\\"));

        return normalizedPath.StartsWith(normalizedBaseDirPath, StringComparison.OrdinalIgnoreCase);
    }

    /// <summary>
    /// Returns <paramref name="str"/> with the minimal concatenation of <paramref name="ending"/> (starting from end) that
    /// results in satisfying .EndsWith(ending).
    /// </summary>
    /// <example>"hel".WithEnding("llo") returns "hello", which is the result of "hel" + "lo".</example>
    public static string WithEnding([CanBeNull] this string str, string ending)
    {
        if (str == null)
            return ending;

        string result = str;

        // Right() is 1-indexed, so include these cases
        // * Append no characters
        // * Append up to N characters, where N is ending length
        for (int i = 0; i <= ending.Length; i++)
        {
            string tmp = result + ending.Right(i);
            if (tmp.EndsWith(ending))
                return tmp;
        }

        return result;
    }

    /// <summary>Gets the rightmost <paramref name="length" /> characters from a string.</summary>
    /// <param name="value">The string to retrieve the substring from.</param>
    /// <param name="length">The number of characters to retrieve.</param>
    /// <returns>The substring.</returns>
    public static string Right([NotNull] this string value, int length)
    {
        if (value == null)
        {
            throw new ArgumentNullException("value");
        }
        if (length < 0)
        {
            throw new ArgumentOutOfRangeException("length", length, "Length is less than zero");
        }

        return (length < value.Length) ? value.Substring(value.Length - length) : value;
    }
}

测试用例(NUnit):

[TestFixture]
public class StringExtensionsTest
{
    [TestCase(@"c:\foo", @"c:", Result = true)]
    [TestCase(@"c:\foo", @"c:\", Result = true)]
    [TestCase(@"c:\foo", @"c:\foo", Result = true)]
    [TestCase(@"c:\foo", @"c:\foo\", Result = true)]
    [TestCase(@"c:\foo\", @"c:\foo", Result = true)]
    [TestCase(@"c:\foo\bar\", @"c:\foo\", Result = true)]
    [TestCase(@"c:\foo\bar", @"c:\foo\", Result = true)]
    [TestCase(@"c:\foo\a.txt", @"c:\foo", Result = true)]
    [TestCase(@"c:\FOO\a.txt", @"c:\foo", Result = true)]
    [TestCase(@"c:/foo/a.txt", @"c:\foo", Result = true)]
    [TestCase(@"c:\foobar", @"c:\foo", Result = false)]
    [TestCase(@"c:\foobar\a.txt", @"c:\foo", Result = false)]
    [TestCase(@"c:\foobar\a.txt", @"c:\foo\", Result = false)]
    [TestCase(@"c:\foo\a.txt", @"c:\foobar", Result = false)]
    [TestCase(@"c:\foo\a.txt", @"c:\foobar\", Result = false)]
    [TestCase(@"c:\foo\..\bar\baz", @"c:\foo", Result = false)]
    [TestCase(@"c:\foo\..\bar\baz", @"c:\bar", Result = true)]
    [TestCase(@"c:\foo\..\bar\baz", @"c:\barr", Result = false)]
    public bool IsSubPathOfTest(string path, string baseDirPath)
    {
        return path.IsSubPathOf(baseDirPath);
    }
}

更新

  • 2015-08-18:修复部分文件夹名称匹配错误。添加测试用例。
  • 2015-09-02:在路径中支持
    ..\
    ,添加缺失代码
  • 2017-09-06:添加符号链接注释。

8
投票

自 netstandard2.1 起,终于有了一种几乎方便且与平台无关的方法来检查这一点:Path.GetRelativePath()

var relPath = Path.GetRelativePath(
    basePath.Replace('\\', '/'),
    subPath.Replace('\\', '/'));
var isSubPath =
    rel != "." && rel != ".."
    && !rel.StartsWith("../")
    && !Path.IsPathRooted(rel);

subPath
basePath
都必须是绝对路径。

便捷的扩展功能:

public static bool IsSubPathOf(this string subPath, string basePath) {
    var rel = Path.GetRelativePath(
        basePath.Replace('\\', '/'),
        subPath.Replace('\\', '/'));
    return rel != "."
        && rel != ".."
        && !rel.StartsWith("../")
        && !Path.IsPathRooted(rel);
}

.NET 摆弄一些测试用例:https://dotnetfiddle.net/di4ze6


5
投票

尝试:

dir1.contains(dir2+"\\");

1
投票

在我的例子中,路径和可能的子路径不包含“..”并且永远不会以“\”结尾:

private static bool IsSubpathOf(string path, string subpath)
{
    return (subpath.Equals(path, StringComparison.OrdinalIgnoreCase) ||
            subpath.StartsWith(path + @"\", StringComparison.OrdinalIgnoreCase));
}

1
投票
string path1 = "C:\test";
string path2 = "C:\test\abc";

var root = Path.GetFullPath(path1);
var secondDir = Path.GetFullPath(path2 + Path.AltDirectorySeparatorChar);

if (!secondDir.StartsWith(root))
{
}

Path.GetFullPath
非常适合路径,例如:
C:\test\..\forbidden\


0
投票

我的路径可能包含不同的大小写,甚至有未修剪的部分...... 这似乎有效:

public static bool IsParent(string fullPath, string base)
{
	var fullPathSegments = SegmentizePath(fullPath);
	var baseSegments = SegmentizePath(base);
	var index = 0;
	while (fullPathSegments.Count>index && baseSegments.Count>index && 
		fullPathSegments[index].Trim().ToLower() == baseSegments[index].Trim().ToLower())
		index++;
	return index==baseSegments.Count-1;
}

public static IList<string> SegmentizePath(string path)
{
	var segments = new List<string>();
	var remaining = new DirectoryInfo(path);
	while (null != remaining)
	{
		segments.Add(remaining.Name);
		remaining = remaining.Parent;
	}
	segments.Reverse();
	return segments;
}


0
投票

基于@BrokenGlass的答案,但进行了调整:

using System.IO;

internal static class DirectoryInfoExt
{
    internal static bool IsSubDirectoryOfOrSame(this DirectoryInfo directoryInfo, DirectoryInfo potentialParent)
    {
        if (DirectoryInfoComparer.Default.Equals(directoryInfo, potentialParent))
        {
            return true;
        }

        return IsStrictSubDirectoryOf(directoryInfo, potentialParent);
    }

    internal static bool IsStrictSubDirectoryOf(this DirectoryInfo directoryInfo, DirectoryInfo potentialParent)
    {
        while (directoryInfo.Parent != null)
        {
            if (DirectoryInfoComparer.Default.Equals(directoryInfo.Parent, potentialParent))
            {
                return true;
            }

            directoryInfo = directoryInfo.Parent;
        }

        return false;
    }
}

using System;
using System.Collections.Generic;
using System.IO;

public class DirectoryInfoComparer : IEqualityComparer<DirectoryInfo>
{
    private static readonly char[] TrimEnd = { '\\' };
    public static readonly DirectoryInfoComparer Default = new DirectoryInfoComparer();
    private static readonly StringComparer OrdinalIgnoreCaseComparer = StringComparer.OrdinalIgnoreCase;

    private DirectoryInfoComparer()
    {
    }

    public bool Equals(DirectoryInfo x, DirectoryInfo y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }

        if (x == null || y == null)
        {
            return false;
        }

        return OrdinalIgnoreCaseComparer.Equals(x.FullName.TrimEnd(TrimEnd), y.FullName.TrimEnd(TrimEnd));
    }

    public int GetHashCode(DirectoryInfo obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException(nameof(obj));
        }
        return OrdinalIgnoreCaseComparer.GetHashCode(obj.FullName.TrimEnd(TrimEnd));
    }
}

如果性能至关重要,则不理想。


0
投票

更新 - 我最初写的这个是错误的(见下文):

在我看来,您实际上坚持使用 .StartsWith() 函数进行基本字符串比较(当然使用 .ToLower() ),并计算路径分隔符,但是您添加了关于数字的额外考虑路径分隔符 - 并且您需要事先在字符串上使用 Path.GetFullPath() 之类的东西,以确保您处理的是一致的路径字符串格式。所以你最终会得到一些基本且简单的东西,就像这样:

string dir1a = Path.GetFullPath(dir1).ToLower();
string dir2a = Path.GetFullPath(dir2).ToLower();
if (dir1a.StartsWith(dir2a) || dir2a.StartsWith(dir1a)) {
    if (dir1a.Count(x => x = Path.PathSeparator) != dir2a.Count(x => x = Path.PathSeparator)) {
        // one path is inside the other path
    }
}

更新...

正如我在使用代码时发现的那样,这是错误的,因为它没有考虑到一个目录名称以与另一个目录的整个名称相同的字符开头的情况。我有一种情况,其中一个目录路径为“D:\prog\dat\Mirror_SourceFiles”,另一个目录路径为“D:\prog\dat\Mirror”。由于我的第一条路径确实以字母“D:\prog\dat\Mirror”“开头”,所以我的代码给了我一个错误的匹配。我完全摆脱了 .StartsWith 并将代码更改为这样(方法:将路径拆分为各个部分,并将这些部分与较小数量的部分进行比较):

// make sure "dir1" and "dir2a" are distinct from each other
// (i.e., not the same, and neither is a subdirectory of the other)
string[] arr_dir1 = Path.GetFullPath(dir1).Split(Path.DirectorySeparatorChar);
string[] arr_dir2 = Path.GetFullPath(dir2).Split(Path.DirectorySeparatorChar);
bool bSame = true;
int imax = Math.Min(arr_dir1.Length, arr_dir2.Length);
for (int i = 0; i < imax; ++i) {
  if (String.Compare(arr_dir1[i], arr_dir2[i], true) != 0) {
    bSame = false;
    break;
  }
}

if (bSame) {
  // do what you want to do if one path is the same or
  // a subdirectory of the other path
}
else {
  // do what you want to do if the paths are distinct
}

当然,请注意,在“真实程序”中,您将在 try-catch 中使用 Path.GetFullPath() 函数来处理与传递给其中的字符串相关的适当异常。


0
投票

您可以使用 Microsoft.Extensions.FileSystemGlobbing 中的 Matcher。该功能自 .NET Standard 2.0 起可用。

示例:

//path parameter is "FileSystemGlobbing\\abc"

public Task<bool> IsPathInDirectory(string path)
{
    var paths = new string[] { "C:\\Temp\\FileSystemGlobbing\\abc\\" };
     
    //You can also use this to check if a relative file path exits
    //var paths = Directory.GetFiles("YourDirectoryToSearch", "*", SearchOption.AllDirectories);

    path = $"**/{path}/**";

    Matcher callMatcher = new();

    callMatcher.AddIncludePatterns(new string[] { path });

    var matches = callMatcher.Match("C:\\", paths);
    
    //Returns true in this case
    return Task.FromResult(matches.HasMatches);
}

-1
投票
public static bool IsSubpathOf(string rootPath, string subpath)
{
    if (string.IsNullOrEmpty(rootPath))
        throw new ArgumentNullException("rootPath");
    if (string.IsNullOrEmpty(subpath))
        throw new ArgumentNulLException("subpath");
    Contract.EndContractBlock();

    return subath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase);
}
© www.soinside.com 2019 - 2024. All rights reserved.