I'm trying to join a Windows path with a relative path using Path.Combine.
However, Path.Combine(@"C:\blah",@"..\bling") returns C:\blah\..\bling instead of C:\bling\.
Does anyone know how to accomplish this without writing my own relative path resolver (which shouldn't be too hard)?
48 Answers
What Works:
string relativePath = "..\\bling.txt"; string baseDirectory = "C:\\blah\\"; string absolutePath = Path.GetFullPath(baseDirectory + relativePath); (result: absolutePath="C:\bling.txt")
What doesn't work
string relativePath = "..\\bling.txt"; Uri baseAbsoluteUri = new Uri("C:\\blah\\"); string absolutePath = new Uri(baseAbsoluteUri, relativePath).AbsolutePath; (result: absolutePath="C:/blah/bling.txt")
4Call Path.GetFullPath on the combined path
> Path.GetFullPath(Path.Combine(@"C:\blah\",@"..\bling")) C:\bling (I agree Path.Combine ought to do this by itself)
1 Path.GetFullPath(@"c:\windows\temp\..\system32")? 1For windows universal apps Path.GetFullPath() is not available, you can use the System.Uri class instead:
Uri uri = new Uri(Path.Combine(@"C:\blah\",@"..\bling")); Console.WriteLine(uri.LocalPath); Path.GetFullPath() does not work with relative paths.
Here's the solution that works with both relative + absolute paths. It works on both Linux + Windows and it keeps the .. as expected in the beginning of the text (at rest they will be normalized). The solution still relies on Path.GetFullPath to do the fix with a small workaround.
It's an extension method so use it like text.Canonicalize()
/// <summary> /// Fixes "../.." etc /// </summary> public static string Canonicalize(this string path) { if (path.IsAbsolutePath()) return Path.GetFullPath(path); var fakeRoot = Environment.CurrentDirectory; // Gives us a cross platform full path var combined = Path.Combine(fakeRoot, path); combined = Path.GetFullPath(combined); return combined.RelativeTo(fakeRoot); } private static bool IsAbsolutePath(this string path) { if (path == null) throw new ArgumentNullException(nameof(path)); return Path.IsPathRooted(path) && !Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && !Path.GetPathRoot(path).Equals(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal); } private static string RelativeTo(this string filespec, string folder) { var pathUri = new Uri(filespec); // Folders must end in a slash if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString())) folder += Path.DirectorySeparatorChar; var folderUri = new Uri(folder); return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString() .Replace('/', Path.DirectorySeparatorChar)); } This will give you exactly what you need (path does NOT have to exist for this to work)
DirectoryInfo di = new DirectoryInfo(@"C:\blah\..\bling"); string cleanPath = di.FullName; 1Be careful with Backslashes, don't forget them (neither use twice:)
string relativePath = "..\\bling.txt"; string baseDirectory = "C:\\blah\\"; //OR: //string relativePath = "\\..\\bling.txt"; //string baseDirectory = "C:\\blah"; //THEN string absolutePath = Path.GetFullPath(baseDirectory + relativePath); 1To handle any absolute, relative, or URI base paths, it doesn't seem like there's one canned solution that fits all... so, I wrote one:
public static String CombinePaths(String basepath, String relpath) { Stack<String> vs = new Stack<String>(); int i; var s = basepath.Split('\\'); for (i = 0; i < s.Length; i++) { if (s[i] != "..") { if (s[i] != ".") vs.Push(s[i]); } else { vs.Pop(); } } var r = relpath.Split('\\'); for (i = 0; i < r.Length; i++) { if (r[i] != "..") { if (r[i] != ".") vs.Push(r[i]); } else { vs.Pop(); } } String ret = ""; var a = vs.ToArray(); i = a.Count() - 1; while (i > 0) { ret += a[i].ToString(); ret += "\\"; i--; } ret += a[0].ToString(); return ret; } Feel free to improve performance or apply whatever shortcuts apply to my answer (I don't usually work in C#; I usually write C++).