web-dev-qa-db-ja.com

フルパスを指定して、パスが他のパスのサブディレクトリであるかどうかを確認します。

Dir1とdir2の2つの文字列があり、一方が他方のサブディレクトリであるかどうかを確認する必要があります。私はContainsメソッドを使用しようとしました:

dir1.contains(dir2);

ただし、ディレクトリの名前が似ている場合、たとえば--c:\abcおよびc:\abc1はサブディレクトリではなく、betはtrueを返します。より良い方法があるに違いありません。

29
andree
DirectoryInfo di1 = new DirectoryInfo(dir1);
DirectoryInfo di2 = new DirectoryInfo(dir2);
bool isParent = di2.Parent.FullName == di1.FullName;

または、ネストされたサブディレクトリを許可するループ内、つまりC:\ foo\bar\bazC:\ 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;
}
27
BrokenGlass
  • 大文字小文字を区別しません
  • \フォルダー区切り文字と/フォルダー区切り文字の組み合わせを許容します
  • パス内の..\を許容します
  • 部分的なフォルダ名の一致を回避します(c:\foobarc:\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:シンボリックリンクにメモを追加します。
24
angularsen

試してください:

dir1.contains(dir2+"\\");
3
Andrew Cooper

更新-私が最初に書いたこれは間違っています(以下を参照):

パス区切り文字を数えるとともに、.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」という1つのディレクトリパスと「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
Steve Greene

私のパスには、異なるケーシングが含まれている可能性があり、トリミングされていないセグメントが含まれている可能性もあります...これは機能しているようです:

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
AlexeiOst

@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
Johan Larsson

私の場合、パスと可能なサブパスには「..」が含まれず、「\」で終わることはありません。

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