【问题标题】:Looping through a hash, or using an array in PowerShell循环遍历哈希,或在 PowerShell 中使用数组
【发布时间】:2012-02-19 08:53:32
【问题描述】:

我正在使用这段(简化的)代码从带有BCP 的 SQL Server 中提取一组表。

$OutputDirectory = "c:\junk\"
$ServerOption =   "-SServerName"
$TargetDatabase = "Content.dbo."

$ExtractTables = @(
    "Page"
    , "ChecklistItemCategory"
    , "ChecklistItem"
)

for ($i=0; $i -le $ExtractTables.Length – 1; $i++)  {
    $InputFullTableName = "$TargetDatabase$($ExtractTables[$i])"
    $OutputFullFileName = "$OutputDirectory$($ExtractTables[$i])"
    bcp $InputFullTableName out $OutputFullFileName -T -c $ServerOption
}

效果很好,但现在有些表需要通过视图提取,有些则不需要。所以我需要一个类似这样的数据结构:

"Page"                      "vExtractPage"
, "ChecklistItemCategory"   "ChecklistItemCategory"
, "ChecklistItem"           "vExtractChecklistItem"

我正在查看哈希,但我没有找到任何关于如何循环遍历哈希的信息。在这里做什么是正确的?也许只使用一个数组,但两个值都用空格分隔?

还是我遗漏了一些明显的东西?

【问题讨论】:

    标签: powershell dictionary hashtable


    【解决方案1】:

    脚本不推荐使用简写;它的可读性较差。 %{} 运算符被认为是简写。为了可读性和可重用性,在脚本中应该这样做:

    变量设置

    PS> $hash = @{
        a = 1
        b = 2
        c = 3
    }
    PS> $hash
    
    Name                           Value
    ----                           -----
    c                              3
    b                              2
    a                              1
    

    选项 1:GetEnumerator()

    注意:个人喜好;语法更容易阅读

    GetEnumerator() 方法将如下所示完成:

    foreach ($h in $hash.GetEnumerator()) {
        Write-Host "$($h.Name): $($h.Value)"
    }
    

    输出:

    c: 3
    b: 2
    a: 1
    

    选项 2:键

    Keys 方法将如下所示完成:

    foreach ($h in $hash.Keys) {
        Write-Host "${h}: $($hash.$h)"
    }
    

    输出:

    c: 3
    b: 2
    a: 1
    

    附加信息

    小心排序你的哈希表...

    Sort-Object 可能会将其更改为数组:

    PS> $hash.GetType()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     Hashtable                                System.Object
    
    
    PS> $hash = $hash.GetEnumerator() | Sort-Object Name
    PS> $hash.GetType()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     Object[]                                 System.Array
    

    This and other PowerShell looping are available on my blog.

    【讨论】:

    • 为了可读性而更喜欢这个,尤其是对于像我这样不专注于 powershell 的开发人员。
    • 我同意这种方法更容易阅读,因此可能更适合编写脚本。
    • 抛开可读性不谈,VertigoRay 的解决方案和 Andy 的解决方案有一个区别。 %{} 是ForEach-Object 的别名,不同于这里的foreach 语句。 ForEach-Object 使用管道,如果您已经在使用管道,它可以更快。 foreach 声明没有;这是一个简单的循环。
    • @JamesQMurphy 根据我的测试,我没有看到任何支持您声称管道更快的说法:gist.github.com/VertigoRay/3bb0166d6a877839b420
    • @JamesQMurphy 我从上面复制并粘贴了@andy-arismendi 提供的答案;这是这个问题的流行管道解决方案。您的陈述引起了我的极大兴趣,即ForEach-Object(又名:%{})比foreach 快; 我证明这并不快。我想证明你的观点是否有效;因为我和大多数人一样,喜欢我的代码尽可能快地运行。现在,您的论点正在更改为并行运行作业(可能使用多台计算机/服务器)......这当然比从单个线程串行运行作业要快。干杯!
    【解决方案2】:

    也可以使用子表达式运算符 $( ) 进行短遍历,它返回一个或多个语句的结果。

    $hash = @{ a = 1; b = 2; c = 3}
    
    forEach($y in $hash.Keys){
        Write-Host "$y -> $($hash[$y])"
    }
    

    结果:

    a -> 1
    b -> 2
    c -> 3
    

    【讨论】:

      【解决方案3】:

      我更喜欢带有管道的枚举器方法的这种变体,因为您不必在 foreach 中引用哈希表(在 PowerShell 5 中测试):

      $hash = @{
          'a' = 3
          'b' = 2
          'c' = 1
      }
      $hash.getEnumerator() | foreach {
          Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
      }
      

      输出:

      Key = c and Value = 1
      Key = b and Value = 2
      Key = a and Value = 3
      

      现在,这并没有故意按值排序,枚举器只是以相反的顺序返回对象。

      但由于这是一个管道,我现在可以对从枚举器接收到的对象进行排序:

      $hash.getEnumerator() | sort-object -Property value -Desc | foreach {
        Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
      }
      

      输出:

      Key = a and Value = 3
      Key = b and Value = 2
      Key = c and Value = 1
      

      【讨论】:

      【解决方案4】:

      关于遍历哈希:

      $Q = @{"ONE"="1";"TWO"="2";"THREE"="3"}
      $Q.GETENUMERATOR() | % { $_.VALUE }
      1
      3
      2
      
      $Q.GETENUMERATOR() | % { $_.key }
      ONE
      THREE
      TWO
      

      【讨论】:

        【解决方案5】:

        这是另一种快速的方法,只需使用键作为哈希表的索引来获取值:

        $hash = @{
            'a' = 1;
            'b' = 2;
            'c' = 3
        };
        
        foreach($key in $hash.keys) {
            Write-Host ("Key = " + $key + " and Value = " + $hash[$key]);
        }
        

        【讨论】:

          【解决方案6】:

          你也可以在没有变量的情况下这样做

          @{
            'foo' = 222
            'bar' = 333
            'baz' = 444
            'qux' = 555
          } | % getEnumerator | % {
            $_.key
            $_.value
          }
          

          【讨论】:

          • 这给我一个“ ForEach-Object : 无法绑定参数 'Process'。无法将类型“System.String”的“getEnumerator”值转换为类型“System.Management.Automation.ScriptBlock”
          【解决方案7】:

          如果您使用的是 PowerShell v3,则可以使用 JSON 代替哈希表,并将其转换为带有 Convert-FromJson 的对象:

          @'
          [
              {
                  FileName = "Page";
                  ObjectName = "vExtractPage";
              },
              {
                  ObjectName = "ChecklistItemCategory";
              },
              {
                  ObjectName = "ChecklistItem";
              },
          ]
          '@ | 
              Convert-FromJson |
              ForEach-Object {
                  $InputFullTableName = '{0}{1}' -f $TargetDatabase,$_.ObjectName
          
                  # In strict mode, you can't reference a property that doesn't exist, 
                  #so check if it has an explicit filename firest.
                  $outputFileName = $_.ObjectName
                  if( $_ | Get-Member FileName )
                  {
                      $outputFileName = $_.FileName
                  }
                  $OutputFullFileName = Join-Path $OutputDirectory $outputFileName
          
                  bcp $InputFullTableName out $OutputFullFileName -T -c $ServerOption
              }
          

          【讨论】:

          • n.b.命令是 ConvertFrom-Json(以及相反的 ConvertTo-Json)——只需交换破折号的位置。
          【解决方案8】:

          Christian 的回答效果很好,并展示了如何使用 GetEnumerator 方法遍历每个哈希表项。您还可以使用keys 属性进行循环。下面是一个例子:

          $hash = @{
              a = 1
              b = 2
              c = 3
          }
          $hash.Keys | % { "key = $_ , value = " + $hash.Item($_) }
          

          输出:

          key = c , value = 3
          key = a , value = 1
          key = b , value = 2
          

          【讨论】:

          • 如何以其他顺序枚举哈希?例如。当我想按升序打印其内容时(此处为 a ... b ... c)。有可能吗?
          • 为什么是$hash.Item($_) 而不是$hash[$_]
          • @LPrc 使用 Sort-Object 方法来做到这一点。这篇文章对它做了很好的解释:technet.microsoft.com/en-us/library/ee692803.aspx
          • 你甚至可以只使用$hash.$_
          • 如果 hashtable 包含 key = 'Keys',则此方法不起作用。
          猜你喜欢
          • 2013-02-09
          • 1970-01-01
          • 2016-08-20
          • 1970-01-01
          • 1970-01-01
          • 2016-01-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多