PathCanonicalize equivalent in C#

前端 未结 3 1556
醉酒成梦
醉酒成梦 2021-01-12 00:07

What is the equivalent to PathCanonicalize in C#?

Use: I need to take a good guess whether two file paths refer to the same file (without disk access). My typical ap

相关标签:
3条回答
  • 2021-01-12 00:50

    Path.GetFullPath() does not work with relative paths. I've been looking for a solution that works for relative paths as well.

    I tried many methods but none of them worked. @Paul's third strategy does not work with linux \\ and has a bug with relative paths as it introduces one more folder, you lose one .. in the result.

    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));
    }
    
    0 讨论(0)
  • 2021-01-12 00:54

    quick and dirty:

    In the past I have created a FileInfo object from the path string and then used the FullName property. This removes all of the ..\'s and the .\'s.

    Of course you could interop:

     [DllImport("shlwapi", EntryPoint="PathCanonicalize")]
        private static extern bool PathCanonicalize(
            StringBuilder lpszDst,
            string lpszSrc
        );
    
    0 讨论(0)
  • 2021-01-12 00:58

    3 solutions:

    Best case scenario, where you are 100% certain the calling process will have full access to the filesystem. CAVEAT: permission on a production box can be tricky

        public static string PathCombineAndCanonicalize1(string path1, string path2)
        {
            string combined = Path.Combine(path1, path2);
            combined = Path.GetFullPath(combined);
            return combined;
        }
    

    But, we're not always free. Often you need to do the string arithmetic without permission. There is a native call for this. CAVEAT: resorts to native call

        public static string PathCombineAndCanonicalize2(string path1, string path2)
        {
            string combined = Path.Combine(path1, path2);
            StringBuilder sb = new StringBuilder(Math.Max(260, 2 * combined.Length));
            PathCanonicalize(sb, combined);
            return sb.ToString();
        }
    
        [DllImport("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool PathCanonicalize([Out] StringBuilder dst, string src);
    

    A third strategy is to trick the CLR. Path.GetFullPath() works just fine on a fictitious path, so just make sure you're always giving it one. What you can do is to swap out the root with a phony UNC path, call GetFullPath(), and then swap the real one back in. CAVEAT: this may require a hard sell in code review

        public static string PathCombineAndCanonicalize3(string path1, string path2)
        {
            string originalRoot = string.Empty;
    
            if (Path.IsPathRooted(path1))
            {
                originalRoot = Path.GetPathRoot(path1);
                path1 = path1.Substring(originalRoot.Length);
            }
    
            string fakeRoot = @"\\thiscantbe\real\";
            string combined = Path.Combine(fakeRoot, path1, path2);
            combined = Path.GetFullPath(combined);
            combined = combined.Substring(fakeRoot.Length);
            combined = Path.Combine(originalRoot, combined);
            return combined;
        }
    
    0 讨论(0)
提交回复
热议问题