循环遍历哈希表或使用数组在PowerShell中

123

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

$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"

我正在查看哈希表,但是没有找到如何循环遍历哈希表的任何内容。在这种情况下应该做什么?也许只需使用一个数组,但用空格分隔两个值吗?
或者我错过了一些明显的东西?
8个回答

259

缩写不是脚本的首选;它的可读性较差。%{}操作符被认为是缩写。以下是如何在脚本中实现可读性和可重用性:

变量设置

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

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

更多关于PowerShell循环的信息,请访问我的博客。


5
我更喜欢这种风格,因为它更易读,特别是对于像我这样不是专业的PowerShell开发人员。 - anIBMer
2
我同意这种方法更易于阅读,因此对于脚本编写可能更好。 - leinad13
2
除了可读性之外,VertigoRay的解决方案和Andy的解决方案之间有一个区别。% {}是ForEach-Object的别名,它与此处的foreach语句不同。ForEach-Object使用管道,如果您已经在使用管道,则可以更快地使用它。foreach语句则不会使用管道,它只是一个简单的循环。 - JamesQMurphy
1
@JamesQMurphy 我在我的测试中没有看到任何支持管道更快的证据:https://gist.github.com/VertigoRay/3bb0166d6a877839b420 - VertigoRay
4
我复制并粘贴了@andy-arismendi上面提供的答案;这是这个问题的流行管道解决方案。你的陈述引起了我的兴趣,即“ForEach-Object”(又称:“% {}”)比“foreach”快; 我已经证明它不会更快。我想证明你的观点是否正确,因为像大多数人一样,我喜欢我的代码尽可能地运行得快。现在,你的论点正在改变为并行运行作业(可能使用多台计算机/服务器)......当然比从单个线程连续运行作业要快。干杯! - VertigoRay
显示剩余5条评论

117

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

1
我该如何以其他顺序枚举哈希表?比如说,当我想要按升序(这里是a ... b ... c)打印它的内容时。这是否可能? - SimonH
6
为什么使用$hash.Item($_)而不是$hash[$_] - alvarez
1
@LPrc 使用Sort-Object方法来实现。这篇文章对此进行了很好的解释:https://technet.microsoft.com/zh-cn/library/ee692803.aspx - chazbot7
5
你甚至可以只使用 $hash.$_ - TNT
在我看来,@TNT的评论是最佳实践答案。当$_不是字符串类型时,只有点注释不会引发ParserError。应将此编辑到接受的答案中。 - DoubleOZ
2
如果哈希表包含键=“Keys”,则此方法无效。 - Boris Nikitin

10

你也可以不使用变量来完成这个操作

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

这让我得到了一个错误信息:"ForEach-Object:无法绑定参数'Process'。无法将类型为'System.String'的“getEnumerator”值转换为类型“System.Management.Automation.ScriptBlock”。 - luis.espinal

9

我更喜欢使用管道的枚举器方法变体,因为您不必在 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

枚举器可以以“任何”顺序返回值,因为它只是迭代底层实现。在这种情况下,它恰好表现为反向顺序。举个反例:$x = @{a = 1; z = 2}; $x.q = 3 在这里的顺序是q、a、z,“有序”的。 - user2864740
2
有序字典与哈希表的不同之处在于键始终按照您列出它们的顺序出现。哈希表中键的顺序是不确定的 - [ordered] 属性会触发选择不同的实现。 - user2864740

7

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

$hash = @{
    'a' = 1;
    'b' = 2;
    'c' = 3
};

foreach($key in $hash.keys) {
    Write-Host ("Key = " + $key + " and Value = " + $hash[$key]);
}

7
关于遍历哈希表的问题:
$Q = @{"ONE"="1";"TWO"="2";"THREE"="3"}
$Q.GETENUMERATOR() | % { $_.VALUE }
1
3
2

$Q.GETENUMERATOR() | % { $_.key }
ONE
THREE
TWO

4
可以使用子表达式运算符 $( ) 来进行短跨度计算,它返回一个或多个语句的结果。
$hash = @{ a = 1; b = 2; c = 3}

forEach($y in $hash.Keys){
    Write-Host "$y -> $($hash[$y])"
}

结果:

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

0

如果您使用的是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
    }

注意,命令是ConvertFrom-Json(以及相反的ConvertTo-Json)--只需交换破折号的位置。 - atmarx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接