正确排序包含版本号的列表

Sort a list that contains version numbers properly

所以,我有一个看起来像这样的版本列表:

v1.1.0
v1.2.0
v1.3.0
v1.4.0
v1.5.0
v1.7.0
v1.8.0
v1.9.0
v2.0.0
v2.1.0
v2.10.0
v2.11.0
v2.12.0
v2.2.0
v2.3.0
v2.4.0
v2.5.0
v2.6.0
v2.7.0
v2.8.0
v2.9.0

问题是,它们的顺序不正确。 我是 Powershell 的新手,所以我在尝试对它们进行排序时遇到了一些问题。 我尝试这样做:

$tags = git tag
$versions = $tags | %{ new-object System.Version ($_) } | sort

但是我得到这个错误:

new-object : Exception calling ".ctor" with "1" argument(s): "Version string portion was too short or too long." At line:1 char:24 + $versions = $tags | %{ new-object System.Version ($_) } | sort + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

有人能帮忙吗?


更新

我使用了如下所示的解决方案之一:

$location = Get-Location
$path = $location.tostring() + "\CHANGELOG.md"
$tags = git tag
$i = 0

Clear-Content $path
Add-Content $path "Change Log"
Add-Content $path "=========="
Add-Content $path " "

$ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }
$tags | Sort-Object $ToNatural

foreach($tag in $tags) 
{
    if (-NOT ($tag -match "v(\d+\.)(\d+\.)(\*|\d+)")) { continue }
    $i = $i + 1
    if ($i -eq 0) { continue }
    $tag
    If ($i -gt 0) {
        $previous = $tags[$i - 1]

        Add-Content $path " "
    }
}

这种方法可行,但所有标签似乎都记录在控制台中,并且显示如下:

1.6.0
changeDeliveryFieldAccess
orders/autoComplete
returns/autoComplete
save-lines-dates
services/serviceDetails
tile-colours
users/confirmation
v0.1
v1.1.0
v1.2.0
v1.3.0
v1.4.0
v1.5.0
v1.7.0
v1.8.0
v1.9.0
v2.0.0
v2.1.0
v2.2.0
v2.3.0
v2.4.0
v2.5.0
v2.6.0
v2.7.0
v2.8.0
v2.9.0
v2.10.0
v2.11.0
v2.12.0
v.2.7.1

如您所见,其中有一些是我不想要的。具体来说:

1.6.0
changeDeliveryFieldAccess
orders/autoComplete
returns/autoComplete
save-lines-dates
services/serviceDetails
tile-colours
users/confirmation
v.2.7.1

一旦从我的列表中清除了这些,那么顺序就会正确:)


更新 2

所以我尝试了另一种希望更好的解决方案:

$location = 获取位置 $path = $location.tostring() + "\CHANGELOG.md" $tags = git 标签 $i = 0

Clear-Content $path
Add-Content $path "#Change Log"
Add-Content $path "=========="
Add-Content $path " "

$tags |
  Where-Object { $_.Substring(1) -as [version] } |
   Sort-Object { [version] $_.Substring(1) }

foreach($tag in $tags) {
    write-host "$($tag) is ok"
}

我不确定我这样做是否正确,但这是上面代码的输出:

1.6.0 is ok
changeDeliveryFieldAccess is ok
orders/autoComplete is ok
returns/autoComplete is ok
save-lines-dates is ok
services/serviceDetails is ok
tile-colours is ok
users/confirmation is ok
v.2.7.1 is ok
v0.1 is ok
v1.1.0 is ok
v1.2.0 is ok
v1.3.0 is ok
v1.4.0 is ok
v1.5.0 is ok
v1.7.0 is ok
v1.8.0 is ok
v1.9.0 is ok
v2.0.0 is ok
v2.1.0 is ok
v2.10.0 is ok
v2.11.0 is ok
v2.12.0 is ok
v2.2.0 is ok
v2.3.0 is ok
v2.4.0 is ok
v2.5.0 is ok
v2.6.0 is ok
v2.7.0 is ok
v2.8.0 is ok
v2.9.0 is ok

我认为如果您想毫无问题地对它们进行排序,您需要将这些字符串正确地解析为版本对象。


$tags = @(
    'v1.1.0'
    'v1.2.0'
    'v1.3.0'
    'v1.4.0'
    'v1.5.0'
    'v1.7.0'
    'v1.8.0'
    'v1.9.0'
    'v2.0.0'
    'v2.1.0'
    'v2.10.0'
    'v2.11.0'
    'v2.12.0'
    'v2.2.0'
    'v2.3.0'
    'v2.4.0'
    'v2.5.0'
    'v2.6.0'
    'v2.7.0'
    'v2.8.0'
    'v2.9.0'
)


 #----------------------------------------------------------------------------# 
 #     Parse Will Fail As 'v' In String is Not Valid Semantic Versioning      # 
 #----------------------------------------------------------------------------# 
$tags | % { 
    $tag = $_
    $version = [version]::new()
    if ([version]::TryParse($tag, [ref]$version))
    {
        $version
    }
    else 
    {"ParseFailed--$($tag)"}

} | Sort-Object


 #----------------------------------------------------------------------------# 
 #                        Parsing String Successfully                         # 
 #----------------------------------------------------------------------------# 
$tags | % { 
    $tag = $_ -replace '[a-zA-Z]'
    $version = [version]::new()
    if ([version]::TryParse($tag, [ref]$version))
    {
        $version
    }
    else 
    {"ParseFailed--$($tag)"}

} | Sort-Object


如果您还想将返回的对象用作 2.1.0 类型的字符串,则可以对返回的对象添加 ToString()

tl;dr:

您后来指出您的 $tags 数组还包含 其他非版本 字符串,因此必须过滤掉这些字符串:

$sortedVersionTags = $tags |
  Where-Object { $_.Substring(1) -as [version] } |
    Sort-Object { [version] $_.Substring(1) }
  • Where-Object { $_.Substring(1) -as [version] } 仅传递那些 可以转换为 [version] (System.Version) 对象的字符串 - -as [version] - 删除开头的 v 之后(.Substring(1);忽略删除 v 是你最初的问题); -as 运算符 returns 成功转换的值或 $null.

  • Sort-Object 然后将过滤后的标签 排序为版本号 ,这会产生正确的顺序 - 请参阅下一节的解释。

  • $sortedVersionTags 然后仅接收版本标签,以其原始形式(作为 v 前缀的字符串)正确排序。


版本号中的 v 前缀阻止它们转换为 [System.Version] 实例;简单地先删除它(不是从输入本身;只是暂时的,为了创建版本信息对象,例如 v1.1.0 -> 1.1.0)。

此外,您的命令可以简化:

# $tags is an array of lines, as output by `git tag`
$tags | Sort-Object { [version] $_.Substring(1) }
  • [version] 是 PowerShell 中内置的类型加速器(简称),指的是 [System.Version].[1]

  • 你可以简单地cast一个字符串到[version],这比使用New-Object更简洁也更快。

  • Sort-Object 通过脚本块 ({ ... }) 接受 表达式 ,代替固定的 属性 排序依据;在脚本块中,$_ 指的是给定的输入对象; $_.Substring(1) 只是删除第一个字符(v)。

    • 注意该表达式仅暂时使用,目的是排序Sort-Object 仍然输出 原始字符串 - 已排序。

使用您的样本输入,上述产量(请注意 v2.10.0 如何在 v2.9.0 之后正确排序,而 词法 排序则不是这种情况) :

v1.1.0
v1.2.0
v1.3.0
v1.4.0
v1.5.0
v1.7.0
v1.8.0
v1.9.0
v2.0.0
v2.1.0
v2.2.0
v2.3.0
v2.4.0
v2.5.0
v2.6.0
v2.7.0
v2.8.0
v2.9.0
v2.10.0
v2.11.0
v2.12.0

如果你宁愿输出System.Version个实例而不是输入字符串,命令变得更简单(PSv3+语法):

[version[]] $tags.Substring(1) | Sort-Object

如果$tags中包含的所有个字符串有可能不能这样转换(由于没有v<version> 格式),使用以下(PSv4+ 语法):

# Reports non-convertible lines as non-terminating errors, but processes all others.
$tags.ForEach({ [version] $_.Substring(1) }) | Sort-Object

这种方法确保遇到无法转换的字符串不会破坏整个命令:

  • 可以转换的,输出

  • 那些不能转换的将导致打印到控制台的错误,并且也会反映在之后的自动$Error收集中。您可以使用 2>$null.

    抑制控制台输出

[1] 通常,PowerShell 允许您在类型名称中省略 System. 前缀。

问题是版本字符串中不允许 'v'。如果你先删除它,你可以正确排序:

$tags | Sort-Object {[Version]($_ -replace "[a-zA-Z]","")}

请注意,这并没有真正删除 'v'(它创建了一个没有它的副本)并且它仍然会出现在输出中:

v1.1.0
v1.2.0
v1.3.0
v1.4.0
v1.5.0
...

对字符串中包含的任意长度的数字进行排序的更通用的方法是 Roman Kuzmin's $ToNatural

如果您将此存储在 script/profile 中:

$ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }

您可以简单地使用:

$tags | Sort-Object $ToNatural

v1.1.0
v1.2.0
v1.3.0
...
v2.7.0
v2.8.0
v2.9.0
v2.10.0
v2.11.0
v2.12.0