18

What is an easy way to check if directory 1 is a subdirectory of directory 2 and vice versa?

I checked the Path and DirectoryInfo helperclasses but found no system-ready function for this. I thought it would be in there somewhere.

Do you guys have an idea where to find this?

I tried writing a check myself, but it's more complicated than I had anticipated when I started.

Sjors Miltenburg
  • 2,540
  • 4
  • 33
  • 60
  • Possible duplicate of [Given full path, check if path is subdirectory of some other path, or otherwise](http://stackoverflow.com/questions/5617320/given-full-path-check-if-path-is-subdirectory-of-some-other-path-or-otherwise) – Marcus Mangelsdorf Feb 01 '16 at 11:19

11 Answers11

11

In response to the first part of the question: "Is dir1 a sub-directory of dir2?", this code should work:

public bool IsSubfolder(string parentPath, string childPath)
{
    var parentUri = new Uri(parentPath);
    var childUri = new DirectoryInfo(childPath).Parent;
    while (childUri != null)
    {
        if(new Uri(childUri.FullName) == parentUri)
        {
            return true;
        }
        childUri = childUri.Parent;
    }
    return false;
}

The URIs (on Windows at least, might be different on Mono/Linux) are case-insensitive. If case sensitivity is important, use the Compare method on Uri instead.

Florian K
  • 602
  • 9
  • 30
Steve Dunn
  • 21,044
  • 11
  • 62
  • 87
9

Here's a simpler way to do it using the Uri class:

var parentUri = new Uri(parentPath);
var childUri = new Uri(childPath);
if (parentUri != childUri && parentUri.IsBaseOf(childUri))
{
   //dowork
}
glopes
  • 4,038
  • 3
  • 26
  • 29
  • Uri.IsBaseOf is nifty, but for the question asked, it has issues: 1) it returns true if the paths are the same 2) it returns true if childPath is a sub-sub-dir, etc - essentially a recursive sub-directory; whereas it appears the question is for an immediate sub directory check. 3) The paths need to end with slashes otherwise the Uri will treat them as file paths. Just pointing this out as the paths in the question are @"C:\Program Files\MyApp" and @"C:\Program Files\MyApp\Images" so instead append a slash for proper use, i.e. @"C:\Program Files\MyApp\" and @"C:\Program Files\MyApp\Images\". – Tyler Laing Oct 10 '14 at 21:19
  • But, despite the above, Uri.IsBaseOf seems to do a great job of recursively checking if one directory is under another. And to get around it returning true in the case that both directories are the same, they appear to compare nicely, so simply change to: if (parentUri != childUri && parentUri.IsBaseOf(childUri)) – Tyler Laing Oct 10 '14 at 21:26
  • yes, this makes total sense, just edited the answer to check for sameness. thanks! – glopes Oct 11 '14 at 09:58
  • Note that problems with the URI class may prevent this from working with .Net Core on non windows machines. To work around this you can prepend the string "file://" to the path. – Yaur Jun 29 '16 at 18:47
4

See original answer here: https://stackoverflow.com/a/31941159/134761

  • Case insensitive
  • Tolerates mix of \ and / folder delimiters
  • Tolerates ..\ in path
  • Avoids matching on partial folder names (c:\foobar not a subpath of c:\foo)

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);
    }
}

Update 2015-08-18: Fix bug matching on partial folder names. Add test cases.

Update 2016-01-29: Link to original question https://stackoverflow.com/a/31941159/134761

Community
  • 1
  • 1
angularsen
  • 8,160
  • 1
  • 69
  • 83
  • 1
    Perfect! Providing the test cases is even better because now we can check the other answers against it (and most of them are failing :( ) - Maybe adding UNC paths to the test cases might make them even more complete. (Combining this with resolving mapped drives to UNC paths would be the ultimate sub folder check method! :) – Marcus Mangelsdorf Jan 29 '16 at 12:36
  • You are welcome to suggest an improvement to the code :-) – angularsen Jan 29 '16 at 13:30
  • I just realized this is an outdated answer. See duplicate question here for an updated answer: http://stackoverflow.com/a/31941159/134761 – angularsen Jan 29 '16 at 13:32
1

If you have two path then look at this:

Normalize directory names in C#

http://filedirectorypath.codeplex.com/ (I don't know the quality of it)

And use this:

var ancestor = new DirectoryPathAbsolute(ancestorPath);
var child = new DirectoryPathAbsolute(childPath);

var res = child.IsChildDirectoryOf(ancestor); //I don't think it actually checks for case-sensitive filesystems

Otherwise, if you want to know whether a directory exists as a subdirectory in a path take a look on:

Directory.EnumerateDirectories

Came in .Net 4.0. Example:

Does path contain a directory starting with Console:

//* is a wildcard. If you remove it, it search for directories called "Console"
var res = Directory.EnumerateDirectories(@path, "Console*", SearchOption.AllDirectories).Any();
Community
  • 1
  • 1
Lasse Espeholt
  • 17,622
  • 5
  • 63
  • 99
1

DirectoryInfo has a property Parent which is also a DirectoryInfo type. You can use that to to determine if your directory is a subdirectory of a parent directory.

MilkyWayJoe
  • 9,082
  • 2
  • 38
  • 53
1

The second directories(d2) full name will contain the full name of the first directory(d1) if it is a sub-folder of d1.

This assumes that you are using valid directories

if (d2.FullName.Contains(d1.FullName))
{
     //dowork
}

If you need to check for mapped drives you could try

    static void Main(string[] args)
    {
        if (GetUNCPath(d2.FullName).ToLower().Contains(GetUNCPath(d1.FullName).ToLower()))
        {
        }
    }

    [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern int WNetGetConnection(
        [MarshalAs(UnmanagedType.LPTStr)] string localName,
        [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, ref int length);

    private static string GetUNCPath(string originalPath)
    {

        StringBuilder sb = new StringBuilder(512);
        int size = sb.Capacity;
        // look for the {LETTER}: combination ...
        if (originalPath.Length > 2 && originalPath[1] == ':')
        {
            // don't use char.IsLetter here - as that can be misleading
            // the only valid drive letters are a-z && A-Z.
            char c = originalPath[0];
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
            {
                int error = WNetGetConnection(originalPath.Substring(0, 2), sb, ref size);
                if (error == 0)
                {
                    DirectoryInfo dir = new DirectoryInfo(originalPath);
                    string path = Path.GetFullPath(originalPath).Substring(Path.GetPathRoot(originalPath).Length);
                    return Path.Combine(sb.ToString().TrimEnd(), path);
                }
            }
        }
        return originalPath;
    }

Code for mapped drive taken from http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/6f79f2b3-d092-431f-bc28-d15d93cf5d09

Jason Quinn
  • 2,443
  • 3
  • 28
  • 36
1
public static bool IsSubfolder(DirectoryInfo parentPath, DirectoryInfo childPath)
{
return parentPath.FullName.StartsWith(childPath.FullName+Path.DirectorySeparatorChar);
}
Tobias Brohl
  • 479
  • 6
  • 25
  • 1
    Note that the additional `DirectorySeparatorChar` must only be added, if it is not present yet. Test it with `new DirectoryInfo("MyFolder")` and `new DirectoryInfo("MyFolder/")` – mihca Nov 19 '22 at 12:45
0

You can use Path.GetDirectoryName Method to get parent directory. It works for directories too.

Giorgi
  • 30,270
  • 13
  • 89
  • 125
  • 1
    this does not work if the path is specified as C:\dir1 (it results in the path c:\\) It does work if the path is specified as c:\dir1\ – Sjors Miltenburg Nov 28 '10 at 13:05
0

With help from the great test cases written in angularsen's answer, I wrote the following simpler extension method on .NET Core 3.1 for Windows:

public static bool IsSubPathOf(this string dirPath, string baseDirPath, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase)
{
    dirPath = dirPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
    if (!dirPath.EndsWith(Path.DirectorySeparatorChar))
    {
      dirPath += Path.DirectorySeparatorChar;
    }
  
    baseDirPath = baseDirPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
    if (!baseDirPath.EndsWith(Path.DirectorySeparatorChar))
    {
      baseDirPath += Path.DirectorySeparatorChar;
    }

    string dirPathUri = new Uri(dirPath).LocalPath;
    string baseDirUri = new Uri(baseDirPath).LocalPath;

    return dirPathUri.Contains(baseDirUri, comparisonType);
}
Shahin Dohan
  • 6,149
  • 3
  • 41
  • 58
-1

this is what I got, after first verifying that the two directory path strings are something and in a path format I know something about: shouldnotbechilddirpath.ToUpper().StartsWith(maybeparentdirpath.ToUpper())

Be sure to take out the ToUppers() if you are maybe working in a case sensitive file system.

stackuser83
  • 2,012
  • 1
  • 24
  • 41
  • 1
    This has the same problem as [Jason Quinn's answer](http://stackoverflow.com/a/3525950/2822719), you will get false positives for directories like "c:\dir" and "c:\dir1". – Marcus Mangelsdorf Jan 29 '16 at 12:31
  • You are right about the false positive, if the speed of impl. is the importance, an extra check that you know the maybeparentdirpath ends with the OS directory separator character would catch that. After that point, network paths and relative paths would also be problems. – stackuser83 Feb 17 '16 at 15:44
-11

You can compare directory2 to directory1's Parent property when using a DirectoryInfo in both cases.

DirectoryInfo d1 = new DirectoryInfo(@"C:\Program Files\MyApp");
DirectoryInfo d2 = new DirectoryInfo(@"C:\Program Files\MyApp\Images");

if(d2.Parent.FullName == d1.FullName)
{
    Console.WriteLine ("Sub directory");
}
fletcher
  • 13,380
  • 9
  • 52
  • 69