Reputation: 3112
I was testing a function and was playing around with what I could do with return
and stumbled accross a strange issue, in PowerShell 5.1 and PwSh 7.1, the return
cmdlet seemd to not work in a group:
PS C:\Users\Neko> return "test"
test
PS C:\Users\Neko> (return "test")
return : The term 'return' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct
and try again.
At line:1 char:2
+ (return "test")
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (return:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
If I put return
in a group, it seemed to become unrecognized by PowerShell.
I understand that return prints to the console and does not have "output" per say like Write-Host
,
PS C:\Users\Neko> $test = return "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko> $test = Write-Host "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko>
but I can't fathom why having return
in a grouping expression is causing such an issue.
Why is using return
in groups causing a strange error and how can I remedy that?
This doesn't occur in subexpressions,
PS C:\Users\Neko> $(return "test")
test
Upvotes: 2
Views: 231
Reputation: 438153
return
is a statement, whereas only commands and expressions are allowed inside ()
, the grouping operator.
exit
, throw
, ...) and loops and conditionals (if
, while
, switch
, ...) - see about_Language_Keywords for the list of all keywords in PowerShell.In order to group (embed in another statement) a statement (potentially even multiple ones, separated with ;
, as usual) - you need $()
, the subexpression operator.
Note that the operator's name is somewhat unfortunate; "sub-statement operator" would have been more descriptive.
The primary use of $(...)
is inside expandable strings ("..."
), and it is typically not needed elsewhere; instead, (...)
should be used when possible, so as to avoid potential side effects (and prevent visual clutter) - see this answer.
That said, in this specific case, as the about_Return help topic states, return
's purpose is to exit the enclosing scope (function, script, or script block), so using it as the RHS of a variable assignment never makes sense.
The reason for the can't-use-a-statement-as-an-expression restriction is rooted in the fundamentals of PowerShell's grammar, as summarized by a member of the PowerShell team in this GitHub issue (emphasis added):
PowerShell has pipelines contained by statements, not statements contained by pipelines. That's always been the case, and the way to put a statement within a pipeline is to use the
$(...)
syntax — that's its only purpose to exist outside of strings in the language.
The two most important consequences to end users are:
You cannot use a statement in a pipeline:
foreach ($i in 1..3) { $i } | % { $_ + 1 }
does not work.[1]$(...)
as a workaround($(foreach ($i in 1..3) { $i }) | % { $_ + 1 }
, doing so forgoes the streaming benefits of the pipeline; that is, the output from the foreach
loop is collected in memory first, in full before the results are sent through the pipeline.{ ... }
) and invoke it with &
, the call operator[2]:
& { foreach ($i in 1..3) { $i } } | % { $_ + 1 }
1..3 | % { $_ + 1 }
)You cannot use the flow-control statements return
, exit
, and throw
with &&
and ||
, the pipeline-chain operators:
ls nosuchfile || exit 1
does not work.$(...)
: ls nosuchfile || $(exit 1)
Unfortunately, these restrictions cannot be lifted if backward compatibility must be maintained; quoting again from the previously linked GitHub issue:
[Lifting these restrictions amounts to] a huge change in PowerShell's grammar (effectively creating a fork in the language)
While PowerShell to date (as of v7.0) has had an ironclad commitment to backward compatibility which prevented serious breaking changes, a debate about whether to permit such changed and how to (carefully) manage them has now begun: see this GitHub issue.
If such changes are eventually permitted, many longstanding annoyances could finally be tackled - see this GitHub issue.
[1] Note that foreach
is also a built-in alias for the ForEach-Object
cmdlet, but the command shown uses the foreach
loop statement - see about_ForEach.
[2] If the statement has side effects that you want the caller to see, use .
, the dot-sourcing operator instead of &
.
Upvotes: 2
Reputation: 438153
Note: This answer complements my (already lengthy) other one in order to address some of your analysis in the question:
I understand that
return
prints to the console and does not have "output" per say likeWrite-Host
It is Write-Host
that has no output in the data sense: that is, it does not write to the success output stream (stream number 1
- see about_Redirection
)
Instead, it writes to the host (the display; in PSv5+, it does so via stream 6
, the information stream), bypassing the success output stream, preventing capturing or redirection of the output - see this answer for background information, including when use of Write-Host
(rather than Write-Output
/ implicit output) is appropriate.
By contrast, return
does send the output from its (optional) operand[1] to the success output stream.
As for the surprising behavior of:
PS> $test = return "test"
test
You've hit an edge case (discussed in this since-closed GitHub issue): something that is syntactically allowed, but doesn't make sense to do: the return
statement effectively short-circuits the assignment: that is, "test"
is sent directly to the success output stream (1
), and the assignment is ignored, because the flow-control statement return
exits the enclosing scope - not just the statement! - before the assignment occurs.
You can verify that return
indeed still targets the success output stream as follows:
PS> $testOuter = & { $test = return "test" }; "[$testOuter]"
[test] # proof that $testOuter was able to capture the `return` value.
However, it's easy to avoid this problem: any expression or command (pipeline) can directly be used as the RHS.
# Do NOT use `return` in the RHS of an assignment.
PS> $test = "test"; "[$test]"
[test]
[1] While using return
with an expression (such as return $foo
) is probably most common, note that it supports an entire pipeline as its operand (e.g., return Get-Date | % Year
).
Upvotes: 1
Reputation: 16096
@Neko Musume -- as per your request.
Because as per the MS Docs on the topic ...
... it can only be used as defined. Meaning 'Exit the current scope, which can be a function, script, or script block.', thus using parens, as you are trying to do, does not meet that criteria.
Upvotes: 2