¿Cómo puedo comparar las rutas (directorio) en C #?

Si tengo dos objetos DirectoryInfo , ¿cómo puedo compararlos para la igualdad semántica? Por ejemplo, las siguientes rutas deben considerarse todas iguales a C:\temp :

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

Lo siguiente puede ser o no igual a C:\temp :

  • \temp si el directorio de trabajo actual está en la unidad C:\
  • temp si el directorio de trabajo actual es C:\
  • C:\temp.
  • C:\temp...\

Si es importante considerar el directorio de trabajo actual, puedo resolverlo yo mismo, así que no es tan importante. Los puntos finales se eliminan en ventanas, por lo que esas rutas realmente deberían ser iguales, pero no se eliminan en Unix, por lo que bajo mono esperaría otros resultados.

La sensibilidad de mayúsculas y minúsculas Las rutas pueden o no existir, y el usuario puede o no tener permisos para la ruta. Preferiría un método rápido y robusto que no requiera ninguna E / S (por lo que no se permite el permiso), pero si hay algo creado -Estaría contento con cualquier cosa “lo suficientemente buena” también …

A partir de esta respuesta , este método puede manejar algunos casos extremos:

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

Más detalles en la respuesta original. Llámalo así:

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

Debería funcionar para rutas de archivos y directorios.

GetFullPath parece hacer el trabajo, excepto por la diferencia de caso ( Path.GetFullPath("test") != Path.GetFullPath("TEST") ) y la barra inclinada final. Entonces, el siguiente código debería funcionar bien:

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

O, si desea comenzar con DirectoryInfo :

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

Hay algunos problemas en la implementación de rutas en .NET. Hay muchas quejas al respecto. Patrick Smacchia , el creador de NDepend, publicó una biblioteca de código abierto que permite manejar operaciones de rutas comunes y complejas . Si realiza muchas operaciones de comparación en las rutas de acceso de su aplicación, esta biblioteca puede ser útil para usted.

Me doy cuenta de que esta es una publicación anterior, pero todas las respuestas se basan finalmente en una comparación textual de dos nombres. Tratar de obtener dos nombres “normalizados”, que tengan en cuenta la miríada de formas posibles de referenciar el mismo objeto de archivo, es casi imposible. Existen problemas tales como: uniones, enlaces simbólicos, archivos compartidos de red (haciendo referencia al mismo objeto de archivo de diferentes maneras), etc. etc. De hecho, cada respuesta anterior, con la excepción de Igor Korkhov, arrojará resultados incorrectos en ciertas circunstancias (por ejemplo, uniones, enlaces simbólicos, enlaces de directorio, etc.)

La pregunta solicitó específicamente que la solución no requiriera ninguna E / S, pero si va a tratar con rutas en red, absolutamente deberá hacer IO: hay casos en los que simplemente no es posible determinar desde cualquier cadena de ruta local manipulación, si dos referencias de archivo harán referencia al mismo archivo físico. (Esto se puede entender fácilmente de la siguiente manera: supongamos que un servidor de archivos tiene una unión de directorio de Windows en algún subárbol compartido. En este caso, se puede hacer referencia a un archivo directamente oa través del cruce. Pero el cruce reside en el servidor de archivos, por lo que es simplemente imposible para un cliente determinar, simplemente a través de información local, que los dos nombres de archivo de referencia se refieren al mismo archivo físico: la información simplemente no está disponible localmente para el cliente. Por lo tanto, uno debe hacer absolutamente una IO mínima – por ejemplo, abra dos manejadores de objetos de archivo para determinar si las referencias se refieren al mismo archivo físico).

La siguiente solución hace algo de IO, aunque es mínima, pero determina correctamente si dos referencias del sistema de archivos son semánticamente idénticas, es decir, hacen referencia al mismo objeto de archivo. (Si ninguna especificación de archivo hace referencia a un objeto de archivo válido, todas las apuestas están desactivadas):

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

La idea para esto vino de una respuesta de Warren Stevens en una pregunta similar que publiqué en SuperUser: https://superuser.com/a/881966/241981

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

Parece que P / Invocar GetFinalPathNameByHandle () sería la solución más confiable.

UPD: Vaya, no tomé en cuenta su deseo de no usar ninguna E / S

Microsoft ha implementado métodos similares, aunque no son tan útiles como las respuestas anteriores:

  • Método PathUtil.ArePathsEqual (que simplemente return string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase); )
  • Método PathUtil.Normalize
  • PathUtil.NormalizePath Method (que simplemente return PathUtil.Normalize(path); )

Las propiedades de “Nombre” son iguales. Tomar:

 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” en este caso. dir3 == “4760”.) Solo son las propiedades de FullName las que son diferentes.

Es posible que pueda hacer un método recursivo para examinar las propiedades de Nombre de cada padre dadas sus dos clases de DirectoryInfo para garantizar que la ruta completa sea la misma.

EDITAR : ¿esto funciona para tu situación? Cree una aplicación de consola y péguela en todo el archivo Program.cs. Proporcione dos objetos DirectoryInfo a la función AreEquals () y devolverá True si son del mismo directorio. Es posible que pueda modificar este método de AreEquals() para que sea un método de extensión en DirectoryInfo si lo desea, de modo que podría simplemente hacer 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 folder1Parents = new List(); List folder2Parents = new List(); 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; } } } 

Puede usar Minimatch, un puerto de minimatch de Node.js.

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

Vea por qué es necesaria la opción AllowWindowsPaths = true :

En las rutas estilo Windows, la syntax de Minimatch se diseñó para rutas estilo Linux (con barras diagonales solamente). En particular, utiliza la barra diagonal inversa como un carácter de escape, por lo que no puede aceptar simplemente las rutas estilo Windows. Mi versión de C # conserva este comportamiento.

Para suprimir esto, y permitir barras diagonales inversas y barras diagonales como separadores de ruta (en patrones o entradas), configure la opción AllowWindowsPaths :

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

Pasar esta opción desactivará por completo los caracteres de escape.

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

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

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

?

 using System; using System.Collections.Generic; using System.Text; namespace EventAnalysis.IComparerImplementation { public sealed class FSChangeElemComparerByPath : IComparer { 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 valueA = new List(); List valueB = new List(); 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 stringCollection, string stringToList) { foreach (string s in stringToList.Remove(0, 2).Split('\\')) { stringCollection.Add(s); } } private int RecursiveComparing(List valueA, List 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; } } } 
 bool Equals(string path1, string path2) { return new Uri(path1) == new Uri(path2); } 

El constructor de Uri normaliza la ruta.