0

问题陈述

我当前的项目需要处理很多 unix 风格的路径,最重要的是加入路径和规范化路径。

通过规范化,我的意思是删除对当前目录 ( .) 的所有引用、对父目录 ( ..) 和冗余正斜杠 ( //) 的引用,同时保留原始路径的“含义”。

例如:路径/foo/bar/foo/./bar/foo/baz/../bar都指向同一个目录。但是,进行简单的字符串比较显然会表明它们是不同的路径。这就是为什么我试图规范化路径,以便它们都被同等对待。

执行此操作的代码实际上并不难编写。您可以在问题的底部找到我的代码。但是我仍然遇到了 1 个问题。

目前,用户可以输入如下所示的路径:

input:      /../../../foo/bar
normalized: /foo/bar

因为这是一个绝对路径,所以它正确地解析为foo/bar根 ( /) 目录。

但是,当输入是相对路径时,我不可能回溯所需的步骤数量,因为我不知道父目录的名称。

input:      ../../../foo/bar
normalized: foo/bar

想象一下完整路径是/a/b/c/d/../../../foo/bar,在这种情况下,算法产生:

input:      /a/b/c/d/../../../foo/bar
normalized: /a/foo/bar

无论出于何种原因,当路径被分成/a/b/c/d和时,就会出现问题../../../foo/bar

input:      /a/b/c/d/
input:      ../../../foo/bar
normalized: /a/b/c/d
normalized: foo/bar

joined: /a/b/c/d/foo/bar

如您所见,当归一化的输出值重新组合在一起时,路径就失去了原来的意义。如果我不删除主要的父引用,就不会发生这种情况。

所以我想我有3个选项,以防父引用未知的相对路径:

  1. 删除主要的父引用,即使结果在技术上是错误的
  2. 保持领先的父参考,即使结果在技术上没有标准化
  3. 抛出错误

我希望一些天才能想出一个更好的主意。但如果不是,你会怎么做?

代码

我还没有针对每个可能的用例测试我的代码,但它应该是相对(双关语)稳定的。

Public MustInherit Class UnixPath

    Private Sub New()
    End Sub

    ''' <summary>
    ''' Gets whether the specified path is an absolute path.
    ''' </summary>
    Public Shared Function IsAbsolute(path As String) As Boolean
        Return path.StartsWith(UnixPath.Separator, StringComparison.InvariantCulture)
    End Function

    ''' <summary>
    ''' Normalizes a string path, taking care of '..' and '.' parts.
    ''' </summary>
    Public Shared Function Normalize(path As String) As String
        If String.IsNullOrEmpty(path) Then
            Return String.Empty
        End If
        Dim oldPath = path.Split(New Char() {UnixPath.Separator}, StringSplitOptions.RemoveEmptyEntries)
        Dim newPath As New Stack(Of String)
        Dim skipCount As Integer = 0

        For i = oldPath.GetUpperBound(0) To oldPath.GetLowerBound(0) Step -1
            If String.Equals(oldPath(i), UnixPath.CurrentDirectory, StringComparison.InvariantCulture) Then
                Continue For
            ElseIf String.Equals(oldPath(i), UnixPath.ParentDirectory, StringComparison.InvariantCulture) Then
                skipCount += 1
            ElseIf skipCount > 0 Then
                skipCount -= 1
            Else
                newPath.Push(oldPath(i))
            End If
        Next

        If UnixPath.IsAbsolute(path) Then
            Return UnixPath.Join(UnixPath.Separator, UnixPath.Join(newPath.ToArray))
        Else
            For i = 1 To skipCount
                newPath.Push(UnixPath.ParentDirectory)
            Next
            Return UnixPath.Join(newPath.ToArray)
        End If
    End Function

    ''' <summary>
    ''' Combines an array of string paths.
    ''' </summary>
    Public Shared Function Join(ParamArray paths As String()) As String
        Dim builder As New StringBuilder
        Dim count = paths.GetUpperBound(0)

        For i = paths.GetLowerBound(0) To count
            If String.IsNullOrEmpty(paths(i)) Then
                Continue For
            End If
            builder.Append(paths(i).TrimEnd(UnixPath.Separator))
            If i = count Then
                Exit For
            End If

            builder.Append(UnixPath.Separator)
        Next

        Return builder.ToString
    End Function

    Public Shared ReadOnly Property CurrentDirectory As String
        Get
            Return "."
        End Get
    End Property

    Public Shared ReadOnly Property ParentDirectory As String
        Get
            Return ".."
        End Get
    End Property

    Public Shared ReadOnly Property Separator As Char
        Get
            Return "/"c
        End Get
    End Property

End Class
4

1 回答 1

0

您将不会通过删除来保留原始路径的含义..,而只能通过删除重复的.and来保留/。不过,目前尚不清楚您要做什么。可能你真的不需要以这种方式“规范化”你的路径。

无论如何,这里有一个想法:

Dim inputPath As String = "/../../../foo/bar"
Dim outputPath As String = inputPath.Replace(".", "").Replace("..", "")
Dim outputPathLength As Integer
Do
  outputPathLength = outputPath.Length
  outputPath = outputPath.Replace("//", "/")
Loop Until outputPathLength = outputPath.Length

为了更好的实现——也可以解释你的情况#2,你可能应该使用正则表达式。

编辑:这可能会被某些人认为是黑客攻击,但这是另一种选择:

Dim inputPath As String = "../../../foo/bar"
Dim outPath As String = IO.Path.GetFullPath(IO.Path.Combine("C:\", inputPath))
Dim newPath As String = outPath.Substring(outPath.IndexOf("\") + 1).Replace("\", "/")
If inputPath.StartsWith("/") Then newPath = "/" & newPath
MsgBox(newPath)

它使用文件系统来规范化路径,并转换回 unix 样式。适用于上述所有测试用例。

于 2013-02-09T21:18:34.790 回答