【问题标题】:PowerShell create array failed in a loopPowerShell 创建数组在循环中失败
【发布时间】:2021-03-03 03:17:08
【问题描述】:

认为我在这里和其他地方已经阅读了足够多的示例。我仍然无法在 Power Shell 中创建数组。

我希望通过该代码从数组中创建对值的切片。

    $values = @('hello','world','bonjour','moon','ola','mars')

function slice_array {
    param (
        [String[]]$Items
    )
    [int16] $size = 2
    $pair = [string[]]::new($size)     # size is 2
    $returns = [System.Collections.ArrayList]@()
    
    [int16] $_i = 0
    foreach($item in $Items){
        $pair[$_i] = $Item
        $_i++;
        if($_i -gt $size - 1){
            $_i = 0
            [void]$returns.Add($pair)
        }
    }
    return $returns
}

slice_array($values)

输出是

ola
mars
ola
mars
ola
mars

希望

'hello','world'
'bonjour','moon'
'ola','mars'

是否可以将该数组切片为长度为 2 的数组?

任何解释为什么它不能按预期工作? 代码应该怎么改?

感谢您提供正确理解 PowerShell 中的数组的任何提示!

【问题讨论】:

  • 顺便说一句:必须调用 PowerShell 函数、cmdlet、脚本和外部程序方法 - foo('arg1', 'arg2')。如果您使用, 分隔参数,您将构造一个命令将其视为单个参数数组。为防止意外使用方法语法,请使用Set-StrictMode -Version 2 或更高版本,但请注意其其他影响。请参阅this answer 了解更多信息。

标签: powershell


【解决方案1】:

这是一个 PowerShell 惯用的解决方案(代码所需的修复在底部):

  • 函数被命名为 Get-Slices 以遵守 PowerShell 的动词-名词命名约定(有关详细信息,请参阅 the docs)。

    • 注意:经常使用名词的单数形式,例如Get-Item 而不是 Get-Items,因为您可能会根据情况获得 一个 或多个输出值;然而,由于这里的明确目的是将一个 单个 对象分割成 多个部分,所以我选择了复数。
  • 切片大小(每个切片的元素数)作为参数传递。

  • 该函数使用..range operator,从数组中提取单个切片。

  • 它使用 PowerShell 的隐式输出行为(无需return,无需显式构建返回值列表;有关更多信息,请参阅this answer)。

  • 它展示了如何从一个函数作为一个整体输出一个数组,这需要使用, 的一元形式array constructor operator 将其包装在一个辅助单元素数组中。如果没有这个辅助数组,数组的元素将单独输出到管道(也用于函数/脚本输出;有关更多信息,请参阅this answer

# Note: For brevity, argument validation, pipeline support, error handling, ...
#       have been omitted.
function Get-Slices {
  
  param (
    [String[]] $Items
    ,
    [int] $Size   # The slice size (element count)
  )

  $sliceCount = [Math]::Ceiling($Items.Count / $Size)
  if ($sliceCount -le 1) {
    # array is empty or as large as or smaller than a slice? -> 
    # wrap it *twice* to ensure that the output is *always* an 
    # *array of arrays*, in this case containing just *one* element
    # containing the original array.
    ,, $Items
  }
  else {
    foreach ($offset in 0..($sliceCount-1)) {
      , $Items[($offset * $Size)..(($offset+1) * $Size - 1)] # output this slice
    }
  }

}

将数组分割成对并将输出收集到数组数组中(锯齿状数组):

$arrayOfPairs = 
  Get-Slices -Items 'hello','world','bonjour','moon','ola','mars' -Size 2

注意:

  • 在 PowerShell 中调用函数(通常是命令)时需要类似

    Shell 的语法:参数是 空格分隔的 并且 不是 括起来的在(...)(更多信息请参见this answer

  • 由于函数声明的参数默认是位置,因此命名我上面所做的参数(-Item ...-Size ...)并不是绝对必要的,但有助于提高可读性。

两个示例调用:

"`n-- Get pairs (slice count 2):"

Get-Slices -Items 'hello','world','bonjour','moon','ola','mars' -Size 2 |
  ForEach-Object { $_ -join ', ' }


"`n-- Get slices of 3:"

Get-Slices -Items 'hello','world','bonjour','moon','ola','mars' -Size 3 |
  ForEach-Object { $_ -join ', ' }

以上产出:

-- Get pairs (slice count 2):
hello, world
bonjour, moon
ola, mars

-- Get slices of 3:
hello, world, bonjour
moon, ola, mars

至于你尝试了什么

您的代码的唯一问题是您一直重复使用相同的辅助数组来收集一对元素,因此后续迭代替换了之前的元素因此,最后,您的数组列表包含对 相同对数组的多个引用,仅反映 最后一对。 p>

出现这种情况是因为数组是引用类型而不是值类型的实例 - 有关背景信息,请参阅this answer

最简单的解决方案$pair 数组的(浅)克隆添加到列表中,这样可以确保每个列表entry 是一个不同的数组:

[void]$returns.Add($pair.Clone())

【讨论】:

    【解决方案2】:

    为什么你得到 3 个相等的对而不是不同的对:

    .Net(基于它的powershell)是面向对象的语言,它具有reference typesvalue types 的概念。几乎所有类型都是reference types

    你的代码会发生什么:

    1. 您创建了$pair = [string[]] 对象。 $pair 变量实际上存储了(引用[string[]] 对象的内存地址,因为数组是引用类型
    2. 你用值填充$pair 数组
    3. 您将 (!) $pair 添加到 $returns。请记住,$pair对内存块的引用。当您将其添加到$returns 时,它会添加您写入值的[string[]] 的内存地址。
    4. 你重复第2步:你用不同的值填充$pair数组,但是这个数组在内存中的地址保持不变。这样做实际上是用同一 $pair 对象中的新值替换了 step2 中的值。
    5. = // = step3
    6. = // = step4
    7. = // = step3

    因此:在$returns 中有三个相同的内存地址:[[reference to $pair], [reference to $pair], [reference to $pair]]。并且$pair 值被最后一对值的代码覆盖。

    在输出时它是这样工作的:

    1. Powershell 查看$results 这是一个数组。
    2. Powershell 查找 $results[0] 其中reference to $pair
    3. Powershell 输出reference to $pair[0]
    4. Powershell 输出reference to $pair[1]
    5. Powershell 查找 $results[1] 其中reference to $pair
    6. Powershell 输出reference to $pair[0]
    7. Powershell 输出reference to $pair[1]
    8. Powershell 查找 $results[1] 其中reference to $pair
    9. Powershell 输出reference to $pair[0]
    10. Powershell 输出reference to $pair[1]

    所以你看,你从同一个内存地址三倍输出对象。您在 slice_array 中将其覆盖了 3 次,现在它只存储最后一对值。

    要在您的代码中修复它,您应该在内存中创建一个新的$pair:在$returns.Add($pair) 之后添加$pair = [string[]]::new($size)

    【讨论】:

    • 感谢您在这里提供帮助!
    • 您的代码最后一条评论在字符串上的作用就像魅力一样,但我如何在 $_content = [System.IO.File]::ReadLines($file_path) 所创建的列表上做同样的事情
    猜你喜欢
    • 2018-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多