如何从PowerShell函数返回一个且只有一个值?

时间:2015-04-10 08:11:42

标签: powershell

我从这个StackOverflow question中学到了PowerShell返回语义不同,让我们从C#的返回语义中说出来。引用上述问题:

  

PowerShell具有非常古怪的返回语义 - 至少从更传统的编程角度来看。有两个主要想法可以解决:所有输出都被捕获并返回。   return关键字实际上只是表示逻辑退出点。

让我们来看看这个例子:

function Calculate
{
   echo "Calculate"
   return 11
}

$result = Calculate

如果你回复$result,你会发现某些事情是不对的。你期待这个:

11

但实际上,你实际看到的是不同的:

Calculate
11

因此,您实际上不会返回预期的返回值,而是返回一个数组。

你可以摆脱echo语句并且不会污染返回值,但是如果你从你的函数中调用另一个函数,它会回复某些东西,那么你就会再次陷入困境。

我的问题是,如何编写PowerShell函数,只返回一件事。如果内置的PowerShell函数只返回一个值,为什么我不这样做呢?

我现在正在使用的解决方法,我很乐意摆脱:

function Calculate
{
   #Every function that returns has to echo something
   echo ""
   return 11
}

#The return values is the last value from the returning array
$result = (Calculate)[-1]

10 个答案:

答案 0 :(得分:17)

您也可以将所有内容分配为null,然后只返回您想要的变量:

Function TestReturn {

$Null = @(
    Write-Output "Hi there!"
    1 + 1
    $host
    $ReturnVar = 5
)
   return $ReturnVar
}

答案 1 :(得分:9)

已经给出的几个答案没有完全回答您的问题,因为他们没有考虑您可能会调用的其他 cmdlet - 正如您所指出的那样 - 可能会“污染”您的输出。来自@Laezylion的答案基本上是正确的,但我相信还有进一步的阐述。

很明显,您了解当前的解决方法 - 强制每个函数返回一个数组然后采用最后一个元素 - 并不是一个非常强大的解决方案。 (事实上​​,我认为将其称为kludge是安全的。:-)正确的方法 - 以及对问题的直接回答 - 很清楚......虽然乍一看它听起来像是重言式:

Question: How do you ensure a function returns only one thing?
Answer: By ensuring that it returns only one thing.

让我澄清一下。 PowerShell中实际上有两种函数/ cmdlet:(A)返回数据的函数和(B)那些不返回数据但可以报告进度或诊断消息的函数/ cmdlet。正如您所看到的,当您尝试将两者混合时出现问题。这可能很容易在无意中发生。因此,作为函数作者,您有责任理解函数中的每个函数/ cmdlet调用:具体而言,它是否返回任何内容,无论是真正的返回值还是仅仅是诊断输出?您期望从A类函数输出,但您需要警惕任何类型B函数。对于任何确实返回某个内容的人,您必须将其分配给变量,将其提供给管道,或者......让它消失。 (有几种方法可以消除它:将它传递给Out-Null,将其转换为void,将其重定向到$ null,或者将其分配给$ null。请参阅Jason Archer的Stack Overflow post来评估每个的性能这些口味,暗示你回避Out-Null。)

是的,这种方法需要付出更多努力,但通过这样做可以获得更强大的解决方案。有关这个问题的更多讨论,这些A / B功能等,请参阅最近在Simple-Talk.com上发布的A Plethora of PowerShell Pitfalls上的问题1。

答案 2 :(得分:6)

请勿使用echo来撰写信息,使用Write-HostWrite-VerboseWrite-Debug,具体取决于邮件。

EchoWrite-Output的别名,它将通过管道将其作为Object发送。 Write-Host/Verbose/Debug但只是控制台消息。

PS P:\> alias echo

CommandType Name                 ModuleName
----------- ----                 ----------
Alias       echo -> Write-Output               

样品:

function test {
    Write-Host calc
    return 11
}

$t = test
"Objects returned: $($t.count)"
$t

#Output
calc
Objects returned: 1
11

另外,如果在函数的最后一行返回值,则不需要使用return。单独编写Object / value就足够了。

function test {
    Write-Host calc
    11
}

不同之处在于return 11在函数中间使用后会跳过这些行。

function test {
    return 11
    12
}

test

#Output
11

答案 3 :(得分:6)

值得一提: 函数内的任何非托管命令输出都将以返回值结束。

我用管理XML内容的函数来解决这个问题:

Function Add-Node(NodeName){
    $NewXmlNode = $Xml.createElement("Item")
    $Xml.Items.appendChild($NewXmlNode) | out-null
}

如果删除Out-Null,函数的返回将是Node基本属性...(您还可以管理appendChild命令的退出代码......取决于动机):)

答案 4 :(得分:2)

您可以将write(写入输出的别名)替换为write-host:

PS>function test{ write-host "foo";"bar"} 
PS>$a=test                                    
test                                          
PS>$a                                         
bar

答案 5 :(得分:2)

echo是Write-Output的别名,它将对象发送到下一个命令。

使用Write-Host应该有帮助。

PS:> function Calculate
>> {
>>    #Every function that returns has to echo something
>>    write-host "test"
>>    return 11
>> }
>>
PS:> $A=Calculate
test
PS:> $A
11

答案 6 :(得分:2)

@Michael Sorens的答案很有用,但它表明了一种似乎相当繁重的设计策略。我不想将数据处理功能与报告进度或诊断消息的功能分开。 Reading his blogpost,对于OP的问题,我发现了一个不太棘手的解决方法。这是博客文章中的关键句子:

  

PowerShell函数返回所有未捕获的输出。

那如果我们捕获输出结果呢?

function Calculate
{
   $junkOutput = echo "Calculate"
   return 11
}

$result = Calculate

现在$result仅包含11个。

答案 7 :(得分:0)

在调用接受参数的函数时,使用括号显得至关重要。 本示例从SQL Server填充DataTable,并从第一行第四列返回[int]。 注意分配给变量[int] $ tt1

的实际函数调用的括号
    function InvokeSQL-GetOneCount { param( [string]$cn, [string]$prj )
        $sql = "SELECT * FROM [dbo].[dummytable] WHERE ProjectNumber = '$prj' ORDER BY FormID, PartID;"

        $handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] { param($sender, $event) Handle-Message -message $event.Message -fcolor "yellow" -bcolor "black"; };
        $conn = new-Object System.Data.SqlClient.SqlConnection($cn)
        $conn.add_InfoMessage($handler); 
        $conn.FireInfoMessageEventOnUserErrors = $true;

        $cmd = New-Object System.Data.SqlClient.SqlCommand;
        $cmd.Connection = $conn;
        $cmd.CommandType = [System.Data.CommandType]::Text;
        $cmd.CommandText = $sql;
        $cmd.CommandTimeout = 0;

        $sa = New-Object System.Data.SqlClient.SqlDataAdapter($cmd);
        $dt = New-Object System.Data.DataTable("AllCounts");
        $sa.Fill($dt) | Out-Null;

        [int]$rval = $dt.Rows[0][3];

        $conn.Close();
        return $rval;
    }
    clear-host;
    [int]$tt1 = (InvokeSQL-GetOneCount -cn "Server=[your server];DataBase=[your catalog];Integrated Security=SSPI" -prj "MS1904");
    write-host $tt1;

答案 8 :(得分:0)

我最终使用以下命令来确保仅返回一个值:

  • 写主机“某些控制台消息”
  • 调用一些实用程序arg1 arg2 |写主机
  • functionCall arg1 arg2 | out-null #禁止任何输出
  • 返回所需的值

答案 9 :(得分:0)

PowerShell笨拙地进行管道传输。若要解决返回值问题并保存真实数据的流水线,请通过引用将该函数传递一个值,即数组。该数组可以加载您希望返回的值。这是一个简单的示例:

# tp.ps1
#   test passed parameters
#   show that arrays are passed by reference, and can be used to provide
#   return values outside the pipeline
function doprint
{
    process { write-output "value now: $psitem" }
}

function recurse($thing,     $rtx)
{
    $thing++
    if($thing -lt 20) {
        if($thing -eq 15) { $rtx[0] = $thing }
        write-output $thing | doprint
        recurse $thing     $rtx
    }
}

j=0
$rtv=@(4)
recurse $j     $rtv
write-output $rtv[0]