Reputation: 12334
For demo purposes I have a JSON document that lists continents and some countries in each continent:
$json = ConvertFrom-Json '[{"Continent" : "Europe","Countries" : ["UK","France","Germany"]},
{"Continent" : "Africa","Countries" : ["Mali","Malawi","Nigeria"]}]'
If I want to get a list of all the countries therein I can do it in one line of PowerShell by piping to the foreach
cmdlet twice:
$json | foreach {$_.Countries} | foreach {$_ | Select @{Name="Country";Expression={$_}}}
Country ------- UK France Germany Mali Malawi Nigeria
All quite normal. However, if I want to Select each Country's continent too then I need a multi-statement script block:
$json | foreach {
$Continent = $_.Continent
$_.Countries | foreach {
$_ | Select-Object @{Name="Continent";Expression={$Continent}},@{Name="Country";Expression={$_}}
}
}
Continent Country --------- ------- Europe UK Europe France Europe Germany Africa Mali Africa Malawi Africa Nigeria
I love the terseness of PowerShell so in this second example I'm slightly deflated about the fact that that I need to store the iterated continent in a variable. All I'm wondering is, can I make this better? Can I make it terser? Is there a way of referring to the iterated value in the outer loop from within the call to Select-Object
?
That's all, basically just trying to learn more about PowerShell and what it can do.
Upvotes: 2
Views: 899
Reputation:
You cannot access the value of the outer $_
directly, but you can capture its value using param
:
param($x=$_)
This will allow you to technically have everything on one line:
$json | foreach { param($x=$_) $_.Countries | foreach { $_ | Select-Object @{Name="Continent";Expression={$x.Continent}},@{Name="Country";Expression={$_}}}}
You can also reduce the size of your code by using some standard aliases (namely, %
for foreach
and select
for Select-Object
):
$json | % { param($x=$_) $_.Countries | % { $_ | select @{Name="Continent";Expression={$x.Continent}},@{Name="Country";Expression={$_}}}}
But I would only recomment doing this for quick runs in the console. Putting code that packed and unreabale into production is just plain evil. ;)
Upvotes: 3
Reputation: 47832
I don't think it's possible to get that "outer" $_
. Moreover, even if you could, I don't think it's a good idea.
You can refer to parent scopes (recursively) in Powershell using Get-Variable -Scope 1
where 1 is the immediate parent, but that doesn't seem to work in nested ForEach-Object
calls. See about_Scopes for more information on that.
In general though it's better to code for readability, in my opinion.
If your primary reason for being bothered by a multi-statement block is having it on more than one line, you can separate your statements with a ;
to keep them on the same line.
$json | foreach { $Continent = $_.Continent ; $_.Countries | foreach { $_ | Select-Object @{Name="Continent";Expression={$Continent}},@{Name="Country";Expression={$_}} }}
Personally, I prefer it broken up.
Upvotes: 1