How can one get an absolute or normalized file path in .NET?
Asked Answered
C

4

65

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

Contemporize answered 12/8, 2009 at 14:49 Comment(0)
W
65

Path.GetFullPath perhaps?

Won answered 12/8, 2009 at 14:50 Comment(10)
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. relativelyCounterfeit
Path.GetFullPath(@"c:\aaa\bbb\..\ccc") = c:\aaa\ccc - good enough for me.Contemporize
Correct, but it doesn't check if the path is valid.Concettaconcettina
@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).Won
Doesn't work for relative file paths, since a prefix like: 'C:/' will be added.Whisenhunt
@My-Name-Is: That depend entirely on how you use it.Won
For this method there aren't any overloads. How should I use it differenty?Whisenhunt
@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()Interlaminate
Answer below takes care of case sensitivity.Starve
Note that another bit of documentation does explicitly state that Path.GetFullPath normalizes, which includes Canonicalization. The documentation for Path.GetFullPath states that "if path does exist, the caller must have permission to obtain path information for path. Note that unlike most members of the Path class, this method accesses the file system." (the uri class mentioned in @bdukes does not access the filesystem).Impersonate
R
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.

Renfrew answered 11/1, 2014 at 3:15 Comment(11)
Good and concise solution to path normalization, exactly what I was looking for. +1Superjacent
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.Forebear
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.Numismatology
It doesn't work with relative paths. This way it does: private string NormalizePath(string path) { return path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) .ToUpperInvariant(); }Molokai
This was a great solution to the problem. Thank you for posting this.Acquaint
@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.Cumbersome
Maybe it would just be best to encapsulate raw path strings, and instead provide (or, preferably, use) some class which defines comparison operations and everything one needs.Cumbersome
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!Edgell
It does not work with a drive letter without final antislash like C: (it is not a valid URI). Not sure it's a legal path, but definitively one that an user can type.Trochophore
Beware, the Uri class may encode characters such as +, spaces, and others.Sard
Casing matters moreso on Linux distros than Windows. However, Windows 10+ can have case-sensitivity enabled for logical volumes and directories.Thirzia
W
65

Path.GetFullPath perhaps?

Won answered 12/8, 2009 at 14:50 Comment(10)
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. relativelyCounterfeit
Path.GetFullPath(@"c:\aaa\bbb\..\ccc") = c:\aaa\ccc - good enough for me.Contemporize
Correct, but it doesn't check if the path is valid.Concettaconcettina
@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).Won
Doesn't work for relative file paths, since a prefix like: 'C:/' will be added.Whisenhunt
@My-Name-Is: That depend entirely on how you use it.Won
For this method there aren't any overloads. How should I use it differenty?Whisenhunt
@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()Interlaminate
Answer below takes care of case sensitivity.Starve
Note that another bit of documentation does explicitly state that Path.GetFullPath normalizes, which includes Canonicalization. The documentation for Path.GetFullPath states that "if path does exist, the caller must have permission to obtain path information for path. Note that unlike most members of the Path class, this method accesses the file system." (the uri class mentioned in @bdukes does not access the filesystem).Impersonate
P
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
Puffball answered 12/8, 2009 at 14:57 Comment(5)
So I assume this checks that the path actually exists?Cowhide
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).Puffball
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.Birdman
@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.Edgell
Beware, the URI class may encode characters such as +, spaces, and others.Sard
U
1

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.

Uninterested answered 15/2, 2021 at 21:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.