问题陈述
我当前的项目需要处理很多 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个选项,以防父引用未知的相对路径:
- 删除主要的父引用,即使结果在技术上是错误的
- 保持领先的父参考,即使结果在技术上没有标准化
- 抛出错误
我希望一些天才能想出一个更好的主意。但如果不是,你会怎么做?
代码
我还没有针对每个可能的用例测试我的代码,但它应该是相对(双关语)稳定的。
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