Given full path, check if path is subdirectory of some other path, or otherwise
Solution 1
DirectoryInfo di1 = new DirectoryInfo(dir1);
DirectoryInfo di2 = new DirectoryInfo(dir2);
bool isParent = di2.Parent.FullName == di1.FullName;
Or in a loop to allow for nested sub-directories, i.e. C:\foo\bar\baz is a sub directory of 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;
}
Solution 2
- Case insensitive
- Tolerates mix of
\
and/
folder delimiters - Tolerates
..\
in path - Avoids matching on partial folder names (
c:\foobar
not a subpath ofc:\foo
)
Note: This only matches on the path string and does not work for symbolic links and other kinds of links in the filesystem.
Code:
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;
}
}
Test cases (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);
}
}
Updates
- 2015-08-18: Fix bug matching on partial folder names. Add test cases.
- 2015-09-02: Support
..\
in paths, add missing code - 2017-09-06: Add note on symbolic links.
Solution 3
Try:
dir1.contains(dir2+"\\");
Solution 4
Since netstandard2.1 there is finally an almost convenient and platform-independent way to check this: Path.GetRelativePath().
var relPath = Path.GetRelativePath(basePath, subPath);
var isSubPath = !relPath.StartsWith('.') && !Path.IsPathRooted(relPath);
Both subPath
and basePath
must be absolut paths.
Convenience extension function:
public static bool IsSubPathOf(this string subPath, string basePath) {
var rel = Path.GetRelativePath(basePath, subPath);
return !rel.StartsWith('.') && !Path.IsPathRooted(rel);
}
.NET Fiddle with some test cases: https://dotnetfiddle.net/9FIU0g
Solution 5
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
works great with paths, like: C:\test\..\forbidden\
andree
Updated on March 31, 2021Comments
-
andree about 3 years
I have 2 strings - dir1 and dir2, and I need to check if one is sub-directory for other. I tried to go with Contains method:
dir1.contains(dir2);
but that also returns true, if directories have similar names, for example -
c:\abc
andc:\abc1
are not sub-directories, bet returns true. There must be a better way. -
Darcara about 11 yearsThis works only if the directories lack the final slash. See Why isn't this DirectoryInfo comparison working?
-
milanio over 8 yearsWhat about path C:\foo\bar\..\bar2 vs C:\foo\bar2? Or C:\foo\bar\ vs C:\foo\bar\..\..\?
-
angularsen over 8 yearsGood point. I believe we should add Path.GetFullPath() to resolve those examples.
-
angularsen over 8 yearsAdded three more test cases and fixed the implementation to support your examples. Also added two missing extension methods the implementation relied on. I'm sure this can all be simplified, but it seems to work.
-
Marcus Mangelsdorf over 8 yearsThis unfortunately returns true for
rootPath = @"c:\foo"
andsubPath = @"c:\foobar"
- which is obviously a false positive. -
emery.noel almost 7 yearsFor me, Path.GetFullPath("c:") returns "c:\windows\system32"
-
angularsen almost 7 years@emery.noel I believe that is expected behavior, meaning you only specify a drive and not a path.
Path.GetFullPath(@"c:\")
returnsc:\
to me. How does this relate to the implementation ofIsSubpathOf()
? -
emery.noel almost 7 years@anjdreas it is your first test case. I did not see how it could pass. For me, I had to append slashes to directories BEFORE I call GetFullPath or I get unexpected results.
-
emery.noel almost 7 years@anjdreas ... and of course now I go back and look, I see that you are doing it too. Missed the 2nd parenthesis.
-
Kevin Shea over 6 yearsUnfortunately doesn't work when symbolic links are involved (and presumably directory junctions). Still, +1 for covering the vast majority of directory layouts.
-
angularsen over 6 years@KevinShea Yes, this only works on path strings, not on the filesystem level. I added a note to the answer now. To my knowledge, Windows API does not have a way to test for similar paths across symbolic links, network shares etc. You could do a check of created and edited timestamps, then finally a full checksum of the files, but that may not be a feasible solution in many cases.
-
STLDev over 5 yearsIt's worth noting that the
[CanBeNull]
and[NotNull]
annotations are part of theJetBrains.Annotations
nuget package. Find them here: JetBrains.Annotations. -
Josef Bláha over 4 yearsThis code ignores case (in)sensitivity of the platform. Otherwise it seems simple and working!
-
Konstantin over 4 yearsImagine 2 directories: C:\SomeDirectory and C:\SomeDirectoryBackup this will give true, even though the second directory is not a child of the first one
-
papplesharp about 3 yearsOn windows it is case-insensitive and this will fail