我有两个字符串 - dir1 和 dir2,我需要检查其中一个是否是另一个的子目录。我尝试使用Contains方法:
dir1.contains(dir2);
但是,如果目录具有类似的名称,例如 - c:\abc
和 c:\abc1
不是子目录,但返回 true。必须有更好的方法。
DirectoryInfo di1 = new DirectoryInfo(dir1);
DirectoryInfo di2 = new DirectoryInfo(dir2);
bool isParent = di2.Parent.FullName == di1.FullName;
或者在循环中允许嵌套子目录,例如C:\foo\bar\baz是C:\foo的子目录:
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;
}
\
和/
混合使用且作为文件夹分隔符..\
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);
}
}
更新
..\
,添加缺失的代码。[CanBeNull]
和[NotNull]
注释是JetBrains.Annotations
NuGet包的一部分。在此处找到它们:JetBrains.Annotations。 - STLDev自从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 Fiddle进行一些测试: https://dotnetfiddle.net/di4ze6
.
开头的文件夹名称。 - Good Night Nerd Priderel != "."
,它会在 @"c:\foo".IsSubPathOf(@"c:\foo")
失败。此外,@"c:\foo".IsSubPathOf(@"c:")
也无法按预期工作。 - montonero@"c:\foo".IsSubPathOf("c:")
返回了错误的结果。但是由于某种原因,"c:/foo".IsSubPathOf("c:")
却没有。我正在寻找解决方法,并将稍后更新答案。 - Good Night Nerd Pride尝试:
dir1.contains(dir2+"\\");
private static bool IsSubpathOf(string path, string subpath)
{
return (subpath.Equals(path, StringComparison.OrdinalIgnoreCase) ||
subpath.StartsWith(path + @"\", StringComparison.OrdinalIgnoreCase));
}
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\
基于@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));
}
}
如果性能至关重要,这并不是理想的选择。
更新 - 我最初写的是错误的(见下文):
在我看来,您实际上是坚持使用基本的字符串比较(当然要使用.ToLower()),使用.StartsWith()函数,以及计算路径分隔符的数量,但您还需要考虑到路径分隔符的数量,并且需要在处理之前对字符串使用类似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
}
我的路径可能包含不同的大小写,甚至有未修剪的段落... 这似乎是有效的:
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;
}
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);
}
rootPath = @"c:\foo"
和 subPath = @"c:\foobar"
返回 true - 这显然是一个错误的结果。 - Marcus Mangelsdorf