循环遍历哈希,或在 PowerShell 中使用数组
Posted
技术标签:
【中文标题】循环遍历哈希,或在 PowerShell 中使用数组【英文标题】:Looping through a hash, or using an array in 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"
我正在查看哈希,但我没有找到任何关于如何循环遍历哈希的信息。在这里做什么是正确的?也许只使用一个数组,但两个值都用空格分隔?
还是我遗漏了一些明显的东西?
【问题讨论】:
【参考方案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
【讨论】:
枚举器可以按 "any" 顺序返回值,因为它只是迭代底层实现。在这种情况下,它恰好出现是相反的顺序。举个反例:$x = @a = 1; z = 2; $x.q = 3
在此处被“排序”为 q,a,z。
"Ordered dictionaries differ from hash tables in that the keys always appear in the order in which you list them. The order of keys in a hash table is not determined - [ordered]
属性触发选择不同的实现。【参考方案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 = '01' -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',则此方法不起作用。以上是关于循环遍历哈希,或在 PowerShell 中使用数组的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法在 Powershell 中创建一个数组并在 Azure DevOps YAML 中循环遍历它?