简体   繁体   中英

What decides if a value is returned from a PowerShell function?

I'm trying to figure out what dictates if a value is returned from a PowerShell function or not, and I've run into some oddities. The about_return docs say:

In PowerShell, the results of each statement are returned as output, even without a statement that contains the Return keyword.

But this seems to glaze over details. If I run this:

function My-Function {
    1
    [System.Console]::WriteLine("Hello")
    $null
    $true
    $false
    0
    2
}

Running this returns an array of (along with printing "Hello"):

1
True
False
0
2

Which means that $null isn't auto-returned. Then I tried incrementing since I'm doing using that in a function:

function My-Function {
    $n = 1
    $n
    $n++
    ($n++)
    -join @(1, 2, 3)
    (-join @(1, 2, 3))
}

Returns:

1
2
123
123

So, $n and $n++ were returned, but ($n++) wasn't? But then when compared to another operator ( -join ), it's the same in each case. Why does wrapping parenthesis around $n++ prevent it from being returned, and why don't other operators behave the same? This is even more confusing since the = operator appears to work in the opposite way:

function My-Function {
    ($n = 1)
    $n
    $n++
    ($n++)
}

Returns:

1
1
2

Now wrapping the assignment causes it to be returned, whereas wrapping $n++ causes it to not be returned.

In summary, I'd just like to know a straightforward way of being able to look at a line of code in a function, and determine if it will cause a value to be returned or not.

This section discusses the specific statements from your sample functions.
See the bottom section for background information.

  • $n = 1 and $n++ are assignments and therefore do not produce output.
  • $n is an expression whose value is output
  • $null - ditto, but even though it is output , it doesn't display by default
  • ($n++) - due to enclosure in (...) - turns the assignment into an expression and therefore does output the assigned value (too).
    • However, because you've used the post -increment form of the assignment, it is the old value that is output, not the now incremented value; to increment first (pre-increment) and then output, use (++$n)
  • [System.Console]::WriteLine("Hello") prints directly to the console , which bypasses PowerShell's system of output streams.
    • This means you cannot capture or redirect such output from inside PowerShell (see the next section).

PowerShell's output ("return value") behavior:

PowerShell , following the model of traditional shells, is organized around streams - see the conceptual about_Redirection help topic for an overview of all 6 streams that PowerShell supports. [1]

That is, any statement - and therefore potentially multiple ones - in a script or function can write to any of the output streams .

The primary output stream, meant to convey data , is the success output stream (whose number is 1 ), and only it is sent through the pipeline by default , and therefore by default only it is captured in a variable, suppressed, or redirected to a file.

There are two ways to write to the success output stream, ie to produce data output :

  • Explicitly , with a Write-Output call - although that is rarely needed .

  • Typically implicitly , by neither capturing, suppressing, nor redirecting output produced by a statement .

    • In other words: Output from any command (eg, Get-ChildItem *.txt ) or expression (eg, 1 + 2 ) is sent to the success output stream by default .

    • Unlike in traditional programming languages, return is not needed to produce output - in fact, its primary purpose is to exit the enclosing scope independently of any output the scope produces, though as a syntactic convenience you can combine the two aspects:

      • return <command-or-expression> is in effect the same as the following two statements, the first of which (potentially) produces output, the second of which exits the scope: <command-or-expression>; return <command-or-expression>; return

As for assignments - eg $n = 1; $n += 1; ++$n; $n-- $n = 1; $n += 1; ++$n; $n-- $n = 1; $n += 1; ++$n; $n-- :

  • By default they do not produce output .
  • However, as an opt-in you can make them pass the value(s) being assigned through via (...) , the grouping operator ; eg ($n = 1) both assigns 1 to variable $n and outputs 1 , which allows it to participate in larger expressions, such as ($n = 1) -gt 0
    • Note that the related $(...) ( subexpression operator ) and @(...) ( array-subexpression operator ) do not have that effect - they wrap one or more entire statement(s) , without affecting the enclosed statements' intrinsic output behavior; eg $($n = 1) does not produce output, because $n = 1 by itself doesn't produce output; however, $(($n = 1)) does , because ($n = 1) by itself does.

As for output enumeration behavior :

  • By default, PowerShell enumerates collections that are being output, in the spirit of streaming output: That is, it sends a collection's elements to the pipeline, one by one .

  • In the rare event that you do need to output a collection as a whole - which in general should be avoided, so as not to confound other commands participating in a pipeline, which usually do expect object-by-object input - you have two options:

    • , $collection (sic; uses an aux. one-element wrapper array)
    • More explicitly, but less efficiently: Write-Output -NoEnumerate $collection
    • See this answer for more information.

As for outputting $null :

  • $null is output to the pipeline , but by default doesn't show .

    • $null by itself produces no visible output,

    • but the following returns $true , demonstrating that the value was sent:

       $null | ForEach-Object { $null -eq $_ } # -> $true
  • Note that PowerShell also has an "array-valued $null " value that it uses to represent the lack of output from a command, which is technically represented as the [System.Management.Automation.Internal.AutomationNull]::Value singleton. In expression contexts, this values is treated the same as $null , but in the pipeline it behaves like an enumerable without elements and therefore sends nothing through the pipeline - see this answer for more information.

As for suppressing (discarding) unwanted output / redirecting to a file :

  • The best general-purpose way to suppress a statement's success output is to assign to $null ( $null = ... ); eg:

     # .Add() outputs a value, which is usually unwanted. $null = ($list = [System.Collections.ArrayList]::new()).Add('hi')
  • Note: The following discusses output suppression , via $null as the redirection target, but applies analogously to redirecting output to a file , by specifying a file name or path as the target. [2]

    • To selectively suppress a different output stream, prefix >$null with its number ; eg 3>$null suppresses warning stream output.

    • To suppress output from all streams , which in the case of external programs covers both stdout and stderr, use redirection *>$null .

As for merging output streams :

  • Only the success output stream (stream number 1 ) can be merged into .
  • You can selectively merge one or more output streams into it (eg 2>&1 and/or 3>&1 ), or merge all (others) : *>&1
  • In the resulting merged success output stream you can still identify what (non-success) stream a given object came from, by examining its type; eg, error stream objects (stream 2 ) are [System.Management.Automation.ErrorRecord] instances - see this answer for more information.

As for bypassing PowerShell's system of streams:

  • Out-Host and [Console]::WriteLine() calls bypass PowerShell's output streams and write directly to the host / console (terminal). (A host is any environment that hosts the PowerShell engine, which is typically , but not necessarily a console (terminal); examples of other hosts are the PowerShell SDK and the host used in PowerShell remoting ).

    • As such, their output cannot be captured, suppressed or redirected from inside PowerShell .
  • Write-Host used to unconditionally bypass PowerShell's output streams and still does by default , but - since PowerShell version 5 - now sends its output to the information stream (stream number 6 ), where it can be captured / redirected on demand - see this answer for more information.


[1] Note that PowerShell has no concept of an input stream as such, and therefore also does not support the stdin redirection operator < familiar from other shells. Instead, commands receive streaming input (only) via the pipeline . In order to receive data from the outside world , via the PowerShell CLI 's stdin stream, the automatic $input variable must be used - see this answer .

[2] Using > (or >> ) to redirect to a file effectively uses the Out-File cmdlet behind the scenes, and therefore its default character encoding, which is "Unicode" (UTF-16LE) in Windows PowerShell , and BOM-less UTF-8 in PowerShell (Core) 7+ . However, in PowerShell version 5.1 and above you can control this encoding via the $PSDefaultParameterValues preference variable - see this answer .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM