66

How can one with minimal effort (using some already existing facility, if possible) convert paths like c:\aaa\bbb\..\ccc to c:\aaa\ccc?

4 Answers 4

80

I would write it like this:

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

This should handle few scenarios like

  1. uri and potential escaped characters in it, like

    file:///C:/Test%20Project.exe -> C:\TEST PROJECT.EXE

  2. path segments specified by dots to denote current or parent directory

    c:\aaa\bbb\..\ccc -> C:\AAA\CCC

  3. tilde shortened (long) paths

    C:\Progra~1\ -> C:\PROGRAM FILES

  4. inconsistent directory delimiter character

    C:/Documents\abc.txt -> C:\DOCUMENTS\ABC.TXT

Other than those, it can ignore case, trailing \ directory delimiter character etc.

12
  • 1
    Good and concise solution to path normalization, exactly what I was looking for. +1
    – Syon
    Commented Jan 16, 2014 at 14:42
  • 33
    Do not use ToUpper() and friends for any code you want to be portable. There are case sensitive filesystems in the world. Also it's not so nice if you're showing these values to users, in which case you want to preserve case and use case-insensitive sorting and comparisons. Otherwise, looks good.
    – dhasenan
    Commented Sep 20, 2015 at 15:50
  • 2
    It depends on exactly what you mean by "canonical" but, since Windows treats file paths as case insensitive, I would argue that you do need a case conversion, otherwise it's possible for there to be more than one "canonical" path for the same file. I would prefer lower case though.
    – Andy
    Commented Jun 15, 2016 at 13:54
  • 6
    @Andy: On the other hand, if one uses this variant of NormalizePath to copy or move a file to somewhere, she/he most probably expects the casing to not change. As a user, I would ban any such program which changes my carefully househeld naming systems. Commented Dec 6, 2017 at 9:09
  • 2
    Because it really does matter: don't change the naming casing! It is rude from a user-visual perspective, and sometimes downright problematic. If there is a reason to do a 'canonical compare', that's the job of a string case-insensitive compare (possibly with a dictionary, or whatever): the CI access is handled post-"canonical" in the Windows Filesystem API! Commented Jan 28, 2018 at 21:03
66

Path.GetFullPath perhaps?

10
  • 4
    I do not believe this is guaranteed to return a canonical name. It only guarantees the name returned can be used to reference the file absolutely vs. relatively
    – JaredPar
    Commented Aug 12, 2009 at 14:52
  • 6
    Path.GetFullPath(@"c:\aaa\bbb\..\ccc") = c:\aaa\ccc - good enough for me.
    – mark
    Commented Aug 12, 2009 at 14:54
  • 12
    @Henk: Path utils should not actually check for a valid file, or even touch the file system (but there are a few cases it does).
    – leppie
    Commented Aug 12, 2009 at 14:58
  • 1
    @My-Name-Is: That depend entirely on how you use it.
    – leppie
    Commented Jun 20, 2014 at 12:46
  • 1
    @My-Name-Is: That's what GetFullPath should do. NB Path.GetFullPath(@"\..\aaa") returns the nonsense "C:\..\aaa" whereas Path.GetFullPath(@"..\aaa") returns an absolute path relative to your Path.CurrentDirectory() Commented Jul 11, 2014 at 12:22
29

Canonicalization is one of the main responsibilities of the Uri class in .NET.

var path = @"c:\aaa\bbb\..\ccc";
var canonicalPath = new Uri(path).LocalPath; // c:\aaa\ccc
5
  • So I assume this checks that the path actually exists?
    – ashes999
    Commented Dec 28, 2011 at 21:22
  • 6
    No, the Uri class is only responsible for generating paths. The system against which those paths are relevant is not taken into account. Once you get the path via the method in my answer, you'd still need to check that it exists via the File class (or whatever).
    – bdukes
    Commented Dec 29, 2011 at 14:47
  • 8
    Note that still doesn't normalise drive letter case (e.g. "C:\" and "c:\" both come out unaltered). So this isn't really "canonical" in the sense of being unique, at any rate. Commented Jun 16, 2015 at 11:21
  • 3
    @AlastairMaw Since the Windows FS is CI, assuming a path is 'canonocial' then any other path differing in case-only IS canonical-and-equivalent even with casing differences. The consumer should also use CI string compares as relevant as all case-different forms are the same. Commented Jan 28, 2018 at 21:10
  • 1
    Beware, the URI class may encode characters such as +, spaces, and others.
    – mihca
    Commented Aug 10, 2023 at 7:10
2

FileInfo objects can also help here. (https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo?view=net-5.0)

var x = Path.Combine(@"C:\temp", "..\\def/abc");
var y = new FileInfo(x).FullName; // "C:\\def\\abc"

FileInfo vs. DirectoryInfo can also help if you want to control the file vs. directory distinction.

But Path.GetFullPath is better if you just need the string.

Not the answer you're looking for? Browse other questions tagged or ask your own question.