【问题标题】:How to track path/key during recursion through hash of hashes in PowerShell?如何通过PowerShell中的哈希值在递归期间跟踪路径/键?
【发布时间】:2018-02-14 00:23:39
【问题描述】:

我一直在尝试将两个哈希值相互比较。不幸的是,即使得到了这里的大力帮助,我也很难把我想做的事情集中起来。

所以,我再次求助于互联网搜索,我很幸运找到了 Edxi 的代码(完全归功于他和 Dbroeglin,他似乎是最初编写的)

https://gist.github.com/edxi/87cb8a550b43ec90e4a89d2e69324806

他的 compare-hash 函数在比较方面完全符合我的需要。但是,我想在最终输出中报告哈希的完整路径。所以我试图更新代码以允许这样做。我的想法是我应该能够在代码自行循环时获取密钥(又名路径),但我显然是以错误的方式处理它。

我是否采用正确的方法来解决这个问题,如果没有,有人会建议另一种方法吗?到目前为止我发现的最大问题是,如果我更改哈希,我的代码更改将不起作用,例如添加'More' = 'stuff' 所以它变成$sailings.Arrivals.PDH083.More 打破了我设置的路径。

我的代码版本:

$Sailings = @{
    'Arrivals'   = @{
        'PDH083' = @{
            'GoingTo'   = 'Port1'
            'Scheduled' = '09:05'
            'Expected'  = '10:11'
            'Status'    = 'Delayed'
        }
    }
    'Departures' = @{
        'PDH083' = @{
            'ArrivingFrom' = 'Port1'
            'Scheduled'    = '09:05'
            'Expected'     = '09:05'
            'Status'       = 'OnTime'
            'Other'        = @{'Data' = 'Test'}
        }
    }
}

$Flights = @{
    'Arrivals'   = @{
        'PDH083' = @{
            'GoingTo'   = 'Port'
            'Scheduled' = '09:05'
            'Expected'  = '10:20'
            'Status'    = 'Delayed'

        }
    }
    'Departures' = @{
        'PDH083' = @{
            'ArrivingFrom' = 'Port11'
            'Scheduled'    = '09:05'
            'Expected'     = '09:05'
            'Status'       = 'NotOnTime'
            'Other'        = @{'Data' = 'Test_Diff'}
        }
    }
}


function Compare-Hashtable {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Hashtable]$Left,

        [Parameter(Mandatory = $true)]
        [Hashtable]$Right,
        [string] $path,
        [boolean] $trackpath = $True
    )
    write-host "PAth received as: $path"
    function New-Result($Key, $LValue, $Side, $RValue) {
        New-Object -Type PSObject -Property @{
            key    = $Key
            lvalue = $LValue
            rvalue = $RValue
            side   = $Side
        }
    }




    [Object[]]$Results = $Left.Keys | ForEach-Object {

        if ($trackpath ) {
            write-host "Working on Path: " $path + $_
        }

        if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
            write-host "Right key not matched. Report path as: $path"
            New-Result $path $Left[$_] "<=" $Null
        }
        else {
            if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
                if ($path -ne $null -or $path -ne "/") {
                    $path = $path + "/" + $_
                    write-host "Sending Path to function as: $path"
                }
                Compare-Hashtable $Left[$_] $Right[$_] $path
            }
            else {
                $LValue, $RValue = $Left[$_], $Right[$_]
                if ($LValue -ne $RValue) {
                    $path = $path + "/" + $_
                    write-host "Not a hash so must be a value at path:$path"
                    New-Result $path $LValue "!=" $RValue
                }
                else {
                    Write-Host "Before changing path: $path "
                    if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
                        $path = $path.Substring(0, $path.lastIndexOf('/'))
                        Write-Host "After changing path: $path "
                    }
                }
            }

        }
       # if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) { 
       # Tried to use this to stop error on substring being less than zero
       # but clearly doesnt work when you add more levels to the hash 
                   $path = $path.Substring(0, $path.lastIndexOf('/'))
       # } else { $path = $path.Substring(0, $path.lastIndexOf('/')) }

    }



    $Results += $Right.Keys | ForEach-Object {
        if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
            New-Result $_ $Null "=>" $Right[$_]

        }
    }


    if ($Results -ne $null) { $Results }
}
cls

Compare-Hashtable $Sailings $Flights

outputs
key                             lvalue side rvalue
---                             ------ ---- ------
/Arrivals/PDH083/Expected       10:11  !=   10:20
/Arrivals/GoingTo               Port1  !=   Port
/Departures/PDH083/ArrivingFrom Port1  !=   Port11
/Departures/PDH083/Status       OnTime !=   NotOnTime
/Departures/PDH083/Other/Data   Test   !=   Test_Diff

【问题讨论】:

  • 您还可以看到我的代码在 Arrivals/GoingTo 输出上丢失了 PDH083 密钥。所以,显然我以错误的方式解决了这个问题。
  • 感谢@halfer 编辑问题。抱歉,我将在以后重新发布不必要的评论。至于我的英语水平,嗯,我尽力了,但我想这和我的编码能力一样好。
  • 你的英语对我来说似乎很棒! :-)是的,我们喜欢这里简洁的东西。

标签: powershell loops recursion hash


【解决方案1】:

我不会对$Path 进行太多的字符串操作,而是威胁$Path 作为一个数组,并在您将其作为属性(key = $Path -Join "/")分配给对象时将其加入字符串:

function Compare-Hashtable {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Hashtable]$Left,

        [Parameter(Mandatory = $true)]
        [Hashtable]$Right,
        [string[]] $path = @(),
        [boolean] $trackpath = $True
    )
    write-host "Path received as: $path"
    function New-Result($Path, $LValue, $Side, $RValue) {
        New-Object -Type PSObject -Property @{
            key    = $Path -Join "/"
            lvalue = $LValue
            rvalue = $RValue
            side   = $Side
        }
    }

    $Left.Keys | ForEach-Object {
        $NewPath = $Path + $_
        if ($trackpath ) {
            write-host "Working on Path: " $NewPath
        }

        if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
            write-host "Right key not matched. Report path as: $NewPath"
            New-Result $NewPath $Left[$_] "<=" $Null
        }
        else {
            if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
                 Compare-Hashtable $Left[$_] $Right[$_] $NewPath
            }
            else {
                $LValue, $RValue = $Left[$_], $Right[$_]
                if ($LValue -ne $RValue) {
                    New-Result $NewPath $LValue "!=" $RValue
                }
             }

        }
    }
    $Right.Keys | ForEach-Object {
        $NewPath = $Path + $_
        if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
            New-Result $NewPath $Null "=>" $Right[$_]

        }
    }
}
cls

Compare-Hashtable $Sailings $Flights

旁注: 将单个记录写入管道 (SC03),请参阅:Strongly Encouraged Development Guidelines。换句话说,不要执行$Results += ...,而是在中间将任何完成的结果放入管道中,以便获取下一个 cmdlet(此外,它不必要地编码)...

【讨论】:

  • 非常感谢!你已经成功地做到了这一点。不能再远离我的思绪了。也感谢您的旁注。我实际上会在另一段代码中使用它作为调用。如果不将其用作 cmdlet 的一部分,您的建议是否仍然适用?我以前没有遇到过 Cmdlet.WriteObject 方法,所以我必须花一些时间阅读它。你知道有什么例子可以证明它的用途吗?
  • 对不起@iRon,刚刚想到另一个问题。有没有办法捕获发送到函数的实际哈希的名称,例如$Flights 将命名为航班?我需要这个的想法是差异可能在左或右哈希上。所以我真的应该记录调用哈希的名称,还是更容易更改数据结构以拥有一个新的父级别,这样它的 $hashone.Sailings 或 $hashtwo.flights 总是清晰的?
  • 设法重新阅读将单个记录写入管道(SC03)的信息,现在更好地理解它。如果我理解正确,它不是额外的方法/命令,而是让对象返回的情况。而不是排队,然后将它们大量返回。希望我的理解现在是正确的。
  • 你的理解是正确的:假设你的输入表来自一个慢的提供者,而你的输出去到另一个慢的提供者,PowerShell可以开始向输出提供者释放数据,同时它仍在从输入提供者...
  • 我很确定您无法检索到 $Sailings$Flights 的原始名称。
猜你喜欢
  • 2023-01-10
  • 1970-01-01
  • 1970-01-01
  • 2021-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-02
  • 2016-07-11
相关资源
最近更新 更多