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


96

我正在使用此(简化的)代码块从带有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"

我当时在看哈希,但在如何遍历哈希中却找不到任何东西。在这里做什么正确的事?也许只使用一个数组,但两个值都用空格隔开?

还是我缺少明显的东西?

Answers:


108

克里斯蒂安(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)。可能吗?
LPrc

5
为什么要$hash.Item($_)代替$hash[$_]
alvarez

1
@LPrc使用Sort-Object方法执行此操作。本文对此做了很好的解释:technet.microsoft.com/zh-cn/library/ee692803.aspx
chazbot7

4
您甚至可以使用just $hash.$_
TNT

1
如果哈希表包含key ='Keys',则此方法无效。
鲍里斯·尼基汀

195

对于脚本,速记不是首选。它的可读性较差。%{}运算符被视为简写。为了提高可读性和可重用性,应在脚本中执行以下操作:

变量设置

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.Item($h))"
}

输出:

c: 3
b: 2
a: 1

附加信息

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

排序对象可以将其更改为数组:

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循环。


3
出于可读性考虑,尤其是像我这样不热衷于Powershell开发人员的用户,更喜欢这样。
an IBMer 2014年

1
我同意此方法更易于阅读,因此可能更适合脚本编写。
leinad13 2014年

2
除了可读性,VertigoRay的解决方案与Andy的解决方案之间存在一个区别。%{}是的别名ForEach-Object,与foreach此处的声明不同。 ForEach-Object利用管道,如果您已经在使用管道,则可以更快。该foreach声明没有;这是一个简单的循环。
JamesQMurphy

4
@JamesQMurphy我从上面复制并粘贴了@ andy-arismendi提供的答案;这是针对此问题的流行管道解决方案。您的陈述引起了我的极大兴趣,是ForEach-Object(aka:)%{}foreach; 更快。我证明那不是更快。我想证明你的观点是否正确;因为我像大多数人一样,希望我的代码尽可能快地运行。现在,您的观点正在更改为并行运行作业(可能使用多台计算机/服务器)……这比从单个线程串行运行作业要快。干杯!
VertigoRay

1
@JamesQMurphy我也认为powershell遵循了shell在向多个进程之间传递数据流时为您提供的传统优势:您可以从每个管道阶段都是一个独立进程这一简单事实中获得自然的并行性(当然,缓冲区可能会耗尽,并且最后,整个管道的运行速度与其最慢的过程一样慢)。这与让管道流程自行决定产生多个线程的流程不同,后者完全取决于流程本身的实现细节。
JohanBoulé18年

8

您也可以不使用变量

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

这使我得到一个“ ForEach对象:无法绑定参数'Process'。无法将类型“ System.String”的“ getEnumerator”值转换为类型“ System.Management.Automation.ScriptBlock”
luis.espinal,2016年

6

关于遍历哈希:

$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]);
}

5

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


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
    }

nb命令是ConvertFrom-Json(和相反的是ConvertTo-Json)-只需交换破折号位置即可。
atmarx
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.