Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does PowerShell require the opening curly brace of a script block to be on the same line?

Okay, the expected "output" of this script is fairly obvious, and it works. It copies files:

Get-ChildItem -Recurse -Directory | ForEach-Object{
    if ($_.Name.ToUpper().Contains("INCLUDE_PUBLIC")){
        Get-ChildItem -Recurse -File -Include "*.h" -Path $_.FullName | ForEach-Object {Copy-Item ($_.FullName) ("e:\IncTest")}
    }
}

However, if I move the opening brace { to the next line, I get the following output:

cmdlet ForEach-Object at command pipeline position 2
Supply values for the following parameters:
Process[0]: 

Am I getting tricked by some other syntax issue, or does ForEach-Object require its brace to be on the same line?

like image 416
zzxyz Avatar asked Oct 31 '17 01:10

zzxyz


People also ask

What is the use of curly brackets in PowerShell?

Curly braces in PowerShell variable names allow for arbitrary characters in the name of the variable. If there are no "pathological" characters in the variable name, then the braces are not needed and have no effect.

What is a PowerShell script block?

In the PowerShell programming language, a script block is a collection of statements or expressions that can be used as a single unit. A script block can accept arguments and return values.

What are curly braces brackets used for in coding?

Different programming languages have various ways to delineate the start and end points of a programming structure, such as a loop, method or conditional statement. For example, Java and C++ are often referred to as curly brace languages because curly braces are used to define the start and end of a code block.


2 Answers

PowerShell has two distinct parsing modes, owing to the fact that it is both a shell and a scripting language.

  • Argument mode is line-oriented and is shell-like: It applies to a single command invoking an executable / script / cmdlet / alias / function, or a series of such commands chained with the pipe symbol (|) to form a pipeline.

    • Each individual command must be on its own line and the only way to spread it across multiple lines is to use line continuation, which requires escaping the very end of each interior line with ` (a backtick).
      However, that practice is both visually subtle and brittle (placing even just spaces or tabs after the ` breaks the command).

    • In order to spread the individual commands of a pipeline across multiple lines (without having to use line continuation), end each line but the last with |.

  • Expression mode works as it does in other programming languages in which whitespace isn't significant: as long as the construct is syntactically complete, it can straddle any number of lines.

It's important to note that a single command can involve a mix of parsing modes, such as when you pass a script block to a cmdlet, where the cmdlet parses its arguments in argument mode, but the script block itself is parsed in expression mode (see below).

The official documentation on parsing modes is in the conceptual about_Parsing help topic.


ForEach-Object is a cmdlet and therefore parsed in argument mode.

Thus, unless you use line continuation, it will not look for arguments on subsequent lines, and executing ForEach-Object on its own, without arguments, causes it to prompt for those of its arguments that are mandatory - the -Process script block - which is what you saw.

By contrast, ... | ForEach-Object { works, despite the script block continuing on subsequent lines, because the script block itself is parsed in expression mode, allowing it to be spread across multiple lines, whereas starting the script block - the opening { - on the same line as ForEach-Object is sufficient for ForEach-Object to recognize it.


An example that contrasts cmdlet ForEach-Object, parsed in argument mode, with the foreach statement, parsed in expression mode[1] :

# Argument mode 
1, 2 | ForEach-Object { # opening brace must be on same line
  "Element: $_" 
}


# Expression mode
foreach ($el in 1, 2) # expression mode - OK to place opening { on next line
{
  "Element: $el" 
}

[1] Note that, perhaps confusingly, ForEach-Object has an alias also named foreach, which blurs the distinction between the cmdlet and the foreach statement. It is the situational parsing mode that determines wether foreach is interpreted as the alias (and therefore refers to ForEach-Object) or as the statement.

like image 50
mklement0 Avatar answered Oct 21 '22 23:10

mklement0


PS C:\> Get-Help -Name 'ForEach-Object'

SYNTAX
    ForEach-Object [-MemberName] <String> [-ArgumentList <Object[]>] [-Confirm] [-InputObject <PSObject>] [-WhatIf] [<CommonParameters>]

    ForEach-Object [-Process] <ScriptBlock[]> [-Begin <ScriptBlock>] [-Confirm] [-End <ScriptBlock>] [-InputObject <PSObject>] [-RemainingScripts <ScriptBlock[]>] [-WhatIf] [<CommonParameters>]

By using a script block, you're using positionally-bound parameters of the ForEach-Object cmdlet. In this case, the -Process { } parameter set. If you escape the end of the line (in essence, escaping the newline), you can get around that, but it's generally a bad practice.

like image 21
Maximilian Burszley Avatar answered Oct 21 '22 21:10

Maximilian Burszley