jamiet
jamiet

Reputation: 12334

Can I refer to the iterated $_ value in an outer loop from the inner loop?

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

Answers (2)

user2555451
user2555451

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

briantist
briantist

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

Related Questions