如何在C#中比较(目录)路径?


73

如果我有两个DirectoryInfo对象,如何比较它们的语义相等性?例如,以下路径应全部视为等于C:\temp

  • C:\temp
  • C:\temp\
  • C:\temp\.
  • C:\temp\x\..\..\temp\.

以下内容可能等于也可能不等于C:\temp

  • \temp 如果当前工作目录在驱动器上 C:\
  • temp 如果当前工作目录是 C:\
  • C:\temp.
  • C:\temp...\

如果考虑当前的工作目录很重要,我可以自己弄清楚,所以那不是很重要。尾随点在窗口中被剥离,因此这些路径确实应该相等-但它们在unix中未被剥离,因此在mono下,我期望其他结果。

区分大小写是可选的。这些路径可能存在或不存在,并且用户可能具有该路径的权限-我更喜欢一种快速健壮的方法,该方法不需要任何I / O(因此无需权限检查),但是如果有内置的东西-我也会对“足够好”的东西感到满意...




为什么System.IO.DirectoryInfo类无法实现bool Equals(DirectoryInfo other)处理呢?在我看来,这个东西应该这样的标准,现在我们甚至不应该能够乱像的比较两条路径简单的事情。
Elaskanator '19

Answers:


40

这个答案中,该方法可以处理一些边缘情况:

public static string NormalizePath(string path)
{
    return Path.GetFullPath(new Uri(path).LocalPath)
               .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
               .ToUpperInvariant();
}

原始答案中有更多详细信息。像这样称呼它:

bool pathsEqual = NormalizePath(path1) == NormalizePath(path2);

应该同时适用于文件和目录路径。


1
有点可笑的是,这实际上是我最终要做的,因为它没有I / O!
Eamon Nerbonne 2014年

5
new Uri(path).LocalPath-如果路径中有#符号,则会给出错误的路径
ili

在某些情况下,尤其是对于网络文件,这将给出错误的答案。实际上,在处理网络文件时,在某些情况下,如果不执行任何IO(即不处理文件句柄),就无法正确确定答案。请参阅下面的答案,以获取“更正确的”解决方案,该解决方案肯定会使用IO而不是问题中的特定要求。
David I. McIntosh

1
请注意,对于问题中的“ \ temp”和“ temp”之类的示例,这将引发异常。
约翰·雷诺兹

3
使用ToUpperInvariant文件系统路径?恭喜你 现在,您有了一个应用程序,有机会在带有土耳其语区域设置的操作系统上神秘地爆炸。
Ishmaeel

93

GetFullPath除了大小写差异(Path.GetFullPath("test") != Path.GetFullPath("TEST"))和斜杠外,似乎可以完成工作。因此,以下代码应该可以正常工作:

String.Compare(
    Path.GetFullPath(path1).TrimEnd('\\'),
    Path.GetFullPath(path2).TrimEnd('\\'), 
    StringComparison.InvariantCultureIgnoreCase)

或者,如果您想以DirectoryInfo

String.Compare(
    dirinfo1.FullName.TrimEnd('\\'),
    dirinfo2.FullName.TrimEnd('\\'), 
    StringComparison.InvariantCultureIgnoreCase)

2
您可以执行Path.GetFullPath(pathx).ToUpperInvariant()。TrimEnd('\\')来消除大小写敏感性。但是,在UNIX上应谨慎使用,因为UNIX将大小写不同的两个名称视为不同的文件夹,而Windows将它们视为一个相同。
安迪·谢拉姆

如果将其编辑为不区分大小写并使用DirectoryInfo(通过FullName),
则将

@Eamon,我为您添加了DirectoryInfo变体:-)。而且已经是大小写不变的-这就是StringComparison.InvariantCultureIgnoreCase所做的。
VladV

2
哦,以防万一其他用户偶然发现了这个答案;FullName确实需要路径发现安全权限,并且对当前工作目录敏感(这实际上意味着您只能比较绝对路径或CWD中评估的相对路径)。
伊蒙·纳邦

11
也许您应该使用System.IO.Path.DirectorySeparatorChar而不是'\\'。
Pato

11

.NET中路径的实现存在一些不足。对此有很多抱怨。NDepend的创建者Patrick Smacchia发布了一个开源库,该库可以处理常见和复杂的路径操作。如果您对应用程序中的路径执行很多比较操作,则此库可能对您有用。


嗯,很有趣-您使用过吗?
伊蒙·纳邦

3
我用它来确定一个目录是否包含另一个目录(例如C:\ A / B包含C:\ a \ b \ c \ d \ e \ .. \ .. \ .. \ f \ g),并且它的工作原理非常好好。
凯文·库洛姆贝

10

我意识到这是一篇过时的文章,但是所有答案最终都是基于两个名字的文字比较。试图获得两个“规范化”名称来考虑引用同一文件对象的各种可能方式几乎是不可能的。存在诸如以下问题:结点,符号链接,网络文件共享(以不同方式引用同一文件对象)等。 实际上,除了Igor Korkhov的答案之外,上述每个答案绝对会在某些情况(例如,路口,符号链接,目录链接等)

这个问题特别要求解决方案不需要任何I / O,但是如果您要处理网络路径,则绝对需要进行IO:在某些情况下,根本无法从任何本地路径字符串中进行确定操作,两个文件引用是否将引用同一物理文件。(这可以很容易地理解如下。假设一个文件服务器具有共享子树内的视窗目录连接的某个地方。在这种情况下,一个文件可以被直接引用,或通过结,但结驻留上,并解决通过,文件服务器等,因此客户端完全不可能仅通过本地信息来确定这两个引用文件名引用的是同一物理文件:该信息根本无法在客户端本地获得。因此,必须绝对执行一些最小的IO(例如,打开两个文件对象句柄),以确定引用是否引用相同的物理文件。)

以下解决方案虽然做得很少,但可以做一些IO,但可以正确确定两个文件系统引用在语义上是否相同,即引用同一文件对象。(如果两个文件规范均未引用有效的文件对象,则所有选择均关闭):

    public static bool AreFileSystemObjectsEqual(string dirName1, string dirName2)
    {
        //Optimization: if strings are equal, don't bother with the IO
        bool bRet = string.Equals(dirName1, dirName2, StringComparison.OrdinalIgnoreCase);
        if (!bRet)
        {
            //NOTE: we cannot lift the call to GetFileHandle out of this routine, because we _must_
            // have both file handles open simultaneously in order for the objectFileInfo comparison
            // to be guaranteed as valid.
            using (SafeFileHandle directoryHandle1 = GetFileHandle(dirName1), directoryHandle2 = GetFileHandle(dirName2))
            {
                BY_HANDLE_FILE_INFORMATION? objectFileInfo1 = GetFileInfo(directoryHandle1);
                BY_HANDLE_FILE_INFORMATION? objectFileInfo2 = GetFileInfo(directoryHandle2);
                bRet = objectFileInfo1 != null
                       && objectFileInfo2 != null
                       && (objectFileInfo1.Value.FileIndexHigh == objectFileInfo2.Value.FileIndexHigh)
                       && (objectFileInfo1.Value.FileIndexLow == objectFileInfo2.Value.FileIndexLow)
                       && (objectFileInfo1.Value.VolumeSerialNumber == objectFileInfo2.Value.VolumeSerialNumber);
            }
        }
        return bRet;
    }

这个想法来自沃伦·史蒂文斯(Warren Stevens)在我在SuperUser上发布的类似问题中的答复:https : //superuser.com/a/881966/241981


5

似乎P /调用GetFinalPathNameByHandle()将是最可靠的解决方案。

UPD:糟糕,我没有考虑到您不使用任何I / O的愿望


好吧,我不希望没有I / O,但是使用简单的I / O解决方案比从头开始编写要好……
Eamon Nerbonne

6
@Eamon Nerbonne:我的解决方案还有两个缺点:1)它只能在Vista和更新的OS上运行2)如果至少其中一个路径不存在,它将不起作用。但是它还有一个好处:它与符号链接一起工作,即回答您的问题“我如何比较它们的语义相等性”?虽然GetFullPath()没有。因此,由您决定是否需要真正的语义一致性。
伊戈尔·科尔霍夫

2
 System.IO.Path.GetFullPath(pathA).Equals(System.IO.Path.GetFullPath(PathB));

3
System.IO.Path.GetFullPath(@“ C:\ LOL”)。Equals(System.IO.Path.GetFullPath(@“ C:\ LOL \”))返回false
2xMax


1

“名称”属性相等。采取:

DirectoryInfo dir1 = new DirectoryInfo("C:\\Scratch");
DirectoryInfo dir2 = new DirectoryInfo("C:\\Scratch\\");
DirectoryInfo dir3 = new DirectoryInfo("C:\\Scratch\\4760");
DirectoryInfo dir4 = new DirectoryInfo("C:\\Scratch\\4760\\..\\");

dir1.Name == dir2.Name and dir2.Name == dir4.Name (在这种情况下为“ Scratch”。dir3 ==“ 4760”。)只有FullName属性不同。

给定两个DirectoryInfo类,您可能可以使用递归方法检查每个父级的Name属性,以确保完整路径是相同的。

编辑:这适合您的情况吗?创建一个控制台应用程序,并将其粘贴到整个Program.cs文件中。向AreEquals()函数提供两个DirectoryInfo对象,如果它们是同一目录,它将返回True。AreEquals()如果愿意,您可能可以将此方法调整为DirectoryInfo的扩展方法,因此您可以myDirectoryInfo.IsEquals(myOtherDirectoryInfo);

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

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(AreEqual(
                new DirectoryInfo("C:\\Scratch"),
                new DirectoryInfo("C:\\Scratch\\")));

            Console.WriteLine(AreEqual(
                new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework"),
                new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework\\v3.5\\1033\\..\\..")));

            Console.WriteLine(AreEqual(
                new DirectoryInfo("C:\\Scratch\\"),
                new DirectoryInfo("C:\\Scratch\\4760\\..\\..")));

            Console.WriteLine("Press ENTER to continue");
            Console.ReadLine();
        }

        private static bool AreEqual(DirectoryInfo dir1, DirectoryInfo dir2)
        {
            DirectoryInfo parent1 = dir1;
            DirectoryInfo parent2 = dir2;

            /* Build a list of parents */
            List<string> folder1Parents = new List<string>();
            List<string> folder2Parents = new List<string>();

            while (parent1 != null)
            {
                folder1Parents.Add(parent1.Name);
                parent1 = parent1.Parent;
            }

            while (parent2 != null)
            {
                folder2Parents.Add(parent2.Name);
                parent2 = parent2.Parent;
            }

            /* Now compare the lists */

            if (folder1Parents.Count != folder2Parents.Count)
            {
                // Cannot be the same - different number of parents
                return false;
            }

            bool equal = true;

            for (int i = 0; i < folder1Parents.Count && i < folder2Parents.Count; i++)
            {
                equal &= folder1Parents[i] == folder2Parents[i];
            }

            return equal;
        }
    }
}

1
Name属性将仅返回最深子目录的名称,因此“ c:\ foo \ bar”将返回“ bar”。将“ d:\ foo \ bar”与“ c:\ bar”进行比较将得出true,这是不好的。
史蒂文

这就是为什么您对所有父母进行递归比较!请删除下降投票,这是一个完全可以接受的解决方案。我已经用完整的代码示例修改了答案。
安迪·谢拉姆

嗯,这个工程即使是D:\temp主场迎战C:\temp。好主意;但是您没有处理区分大小写,并且可能会更短一些:while(dir1!= null && dir2!= null)if(!string.Equals(dir1.Name,dir2.Name,StringComparison.InvariantCultureIgnoreCase))返回false;否则{dir1 = dir1.Parent; dir2 = dir2.Parent;} return dir1 == dir2;
伊蒙·纳邦

1
是的,区分大小写是很困难的,因为OP希望代码在Mono和Windows上都可以工作,但是在Linux 2上,不同大小写的名称被认为是不同的,但是在Windows上,它们被认为是相同的文件,因此每个平台决策。
安迪·谢拉姆

1

您可以使用Minimatch,它是Node.js minimatch的端口。

var mm = new Minimatcher(searchPattern, new Options { AllowWindowsPaths = true });

if (mm.IsMatch(somePath))
{
    // The path matches!  Do some cool stuff!
}

var matchingPaths = mm.Filter(allPaths);


查看为什么需要AllowWindowsPaths = true选项:

在Windows风格的路径上,Minimatch的语法是为Linux风格的路径设计的(仅带有正斜杠)。特别是,它使用反斜杠作为转义字符,因此它不能简单地接受Windows样式的路径。我的C#版本保留了此行为。

为了抑制这种情况,并允许反斜杠和正斜杠作为路径分隔符(在模式或输入中),请设置以下 AllowWindowsPaths选项:

var mm = new Minimatcher(searchPattern, new Options { AllowWindowsPaths = true });

传递此选项将完全禁用转义字符。

Nuget: http ://www.nuget.org/packages/Minimatch/

GitHub: https : //github.com/SLaks/Minimatch


0
bool equals = myDirectoryInfo1.FullName == myDirectoryInfo2.FullName;


2
这是Binary Worrier解决方案的简单版本。请注意,后面的斜杠存在问题:“ c:\ temp”与“ c:\ temp \”不相等。
史蒂文

1
好的,因此,如果我将尾部的斜杠和框的大小归一化,并且接受它确实具有FileIOPermission的功能这一事实,这看起来是个不错的开始,谢谢!
伊蒙·纳邦

0
using System;
using System.Collections.Generic;
using System.Text;

namespace EventAnalysis.IComparerImplementation
{

    public sealed class FSChangeElemComparerByPath : IComparer<FSChangeElem>
    {
        public int Compare(FSChangeElem firstPath, FSChangeElem secondPath)
        {
            return firstPath.strObjectPath == null ?
                (secondPath.strObjectPath == null ? 0 : -1) :
                (secondPath.strObjectPath == null ? 1 : ComparerWrap(firstPath.strObjectPath, secondPath.strObjectPath));
        }

        private int ComparerWrap(string stringA, string stringB)
        {
            int length = 0;
            int start = 0;
            List<string> valueA = new List<string>();
            List<string> valueB = new List<string>();

            ListInit(ref valueA, stringA);
            ListInit(ref valueB, stringB);

            if (valueA.Count != valueB.Count)
            {
                length = (valueA.Count > valueB.Count)
                           ? valueA.Count : valueB.Count;

                if (valueA.Count != length)
                {
                    for (int i = 0; i < length - valueA.Count; i++)
                    {
                        valueA.Add(string.Empty);
                    }
                }
                else
                {
                    for (int i = 0; i < length - valueB.Count; i++)
                    {
                        valueB.Add(string.Empty);
                    }
                }
            }

            else
                length = valueA.Count;

            return RecursiveComparing(valueA, valueB, length, start);
        }

        private void ListInit(ref List<string> stringCollection, string stringToList)
        {
            foreach (string s in stringToList.Remove(0, 2).Split('\\'))
            {
                stringCollection.Add(s);
            }
        }

        private int RecursiveComparing(List<string> valueA, List<string> valueB, int length, int start)
        {
            int result = 0;

            if (start != length)
            {
                if (valueA[start] == valueB[start])
                {
                    result = RecursiveComparing(valueA, valueB, length, ++start);
                }
                else
                {
                    result = String.Compare(valueA[start], valueB[start]);
                }
            }
            else
                return 0;

            return result;
        }
    }
}

List <T> _list =新的List <T>(bla-bla-bla); _list.Sort(new FSChangeElemComparerByPath());
丹尼斯2010年

0
bool Equals(string path1, string path2)
{
    return new Uri(path1) == new Uri(path2);
}

Uri构造函数将路径标准化。


这基本上等同于@nawfal所建议的(当前已被接受的答案)
Eamon Nerbonne 2014年

1
尾部的斜杠问题如何处理?
山姆

0

我使用递归为自己解决了这个问题。

 public bool PathEquals(string Path1, string Path2)
 {
     FileInfo f1 = new FileInfo(Path1.Trim('\\','/','.'));
     FileInfo f2 = new FileInfo(Path2.Trim('\\', '/','.'));
     if(f1.Name.ToLower() == f2.Name.ToLower())
     {
         return DirectoryEquals(f1.Directory, f2.Directory);
     }
     else
     {
         return false;
     }
}

public bool DirectoryEquals(DirectoryInfo d1, DirectoryInfo d2)
{
    if(d1.Name.ToLower() == d2.Name.ToLower())
    {
        if((d1.Parent != null) && (d2.Parent != null))
        {
            return DirectoryEquals(d1.Parent, d2.Parent);
        }
        else
        {
            return true;//C:\Temp1\Temp2 equals \Temp1\Temp2
            //return (d1.Parent == null) && (d2.Parent == null);//C:\Temp1\Temp2 does not equal \Temp1\Temp2
        }
    }
    else
    {
        return false;
    }
}

注意:new FileInfo(path)即使路径不是文件(名称字段等于目录名称),也将返回有效的FileInfo

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.