可重用的自定义排序

Rya*_*ger 1 powershell

我发现自己最近处理了大量 DNS 数据,并且我一直在使用自定义排序表达式按名称的每个虚线部分对 FQDN 进行反向排序,以便同一区域/子区域中的记录彼此相邻排序。例如:

# Build some fake data that normally comes from API call results
# There are generally more columns in the results and they're not always the same
$fqdns = @'
"fqdn","otherdata"
"a.example.com","foo"
"a.example.org","foo"
"example.com","foo"
"example.org","foo"
"www.example.com","foo"
"www.example.org","foo"
"www.sub.example.com","foo"
"www.sub.example.org","foo"
'@ | ConvertFrom-Csv

$fqdns | sort @{E={$a=$_.fqdn.Split('.'); [array]::Reverse($a); $a -join '.'}}
Run Code Online (Sandbox Code Playgroud)

sort 表达式效果很好,但是在 shell 中以交互方式工作时需要输入很多内容。我想弄清楚如何将它作为变量或函数添加到我的配置文件中,以便我可以通过更少的输入重新使用它。关键是我正在执行初始 Split 的 string 属性不会总是被调用fqdn。所以我需要一些我仍然可以指定该值的东西。我设想能够输入这样的东西:

$fqdns | sort (fqdnsort fqdn)
Run Code Online (Sandbox Code Playgroud)

PS 我不一定要在表达式本身中寻找效率改进,但如果您有想法,也欢迎这些想法。

Mat*_*sen 5

您影响的自定义排名行为完全包含在您传递给sort/的参数中Sort-Object- 因此您所要做的就是编写一个返回此类对象的函数:

function Get-FqdnSortKey
{
    return @{E={$a=$_.fqdn.Split('.'); [array]::Reverse($a); $a -join '.'}}
}
Run Code Online (Sandbox Code Playgroud)

然后使用像:

$fqdns |Sort-Object (Get-FqdnSortKey)
Run Code Online (Sandbox Code Playgroud)

在进行任何其他功能修改之前,为了清晰起见,让我们稍微重构和重新格式化现有代码:

function Get-FqdnSortKey
{
  param()

  $SortKeyExpression = {
    $a=$_.fqdn.Split('.')
    [array]::Reverse($a)
    $a -join '.'
  }

  return @{Expression=$SortKeyExpression}
}
Run Code Online (Sandbox Code Playgroud)

如果要在属性表达式中参数化源属性的名称,则必须使用以下命令关闭函数参数值GetNewClosure()

function Get-FqdnSortKey
{
  param(
    [string]$PropertyName = 'Fqdn'
  )

  $SortKeyExpression = {
    $a = $_.$PropertyName.Split('.')
    [array]::Reverse($a)
    $a -join '.'
  }.GetNewClosure()

  return @{Expression=$SortKeyExpression}
}
Run Code Online (Sandbox Code Playgroud)

这样,当$PropertyName稍后的时间点由结果闭包解析,它仍然具有Get-FqdnSortKey我们定义它时的值。


如果要向数据添加内在排序行为,可以通过使用 PowerShell 的class关键字定义自定义数据类型来实现- 内在排序的唯一要求是我们的类型实现System.IComparable接口:

class FqdnWithMetaData : IComparable
{
    [string]$Fqdn
    [string]$OtherData

    hidden [string] $_namespaceOrder = $null

    [int]
    CompareTo([object]$other)
    {
      if($other -isnot [FqdnWithMetaData]){
        throw [ArgumentException]::new('other')
      }

      return [string]::Compare($this.GetNamespaceOrder(), $other.GetNamespaceOrder(), [StringComparison]::InvariantCultureIgnoreCase)
    }

    hidden [string] GetNamespaceOrder()
    {
      if(-not $this._namespaceOrder){
        $labels = $this.Fqdn.Split('.')
        [array]::Reverse($labels)
        $this._namespaceOrder = $labels -join '.'
      }

      return $this._namespaceOrder
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们已经实现了IComparableSort-Object将调用$current.CompareTo($next)输入作为其比较例程的一部分:

$data = @(
  [FqdnWithMetaData]@{ Fqdn = 'yahoo.com' },
  [FqdnWithMetaData]@{ Fqdn = 'google.com' },
  [FqdnWithMetaData]@{ Fqdn = 'google.net' },
  [FqdnWithMetaData]@{ Fqdn = 'amazon.com' }
)

$data |Sort-Object    # No need to supply anything else here

# Resulting in
Fqdn       OtherData
----       ---------
amazon.com
google.com
yahoo.com
google.net
Run Code Online (Sandbox Code Playgroud)

有关在 PowerShell 中使用自定义数据类型的详细信息,请参阅about_Classes帮助主题