【问题标题】:Function return value in PowerShellPowerShell 中的函数返回值
【发布时间】:2012-04-23 18:28:39
【问题描述】:

我开发了一个 PowerShell 函数,该函数执行许多涉及配置 SharePoint 团队网站的操作。最终,我希望该函数将配置站点的 URL 作为字符串返回,因此在我的函数末尾我有以下代码:

$rs = $url.ToString();
return $rs;

调用这个函数的代码如下:

$returnURL = MyFunction -param 1 ...

所以我期待一个字符串,但事实并非如此。相反,它是 System.Management.Automation.PSMethod 类型的对象。为什么它返回的是那个类型而不是 String 类型?

【问题讨论】:

  • 能看到函数声明吗?
  • 不,不是函数调用,向我们展示您声明“MyFunction”的方式/位置。当你这样做时会发生什么:Get-Item function:\MyFunction
  • 建议另一种方法来解决这个问题:stackoverflow.com/a/42842865/992301
  • 我认为这是与函数/方法相关的堆栈溢出最重要的 PowerShell 问题之一。如果您计划学习 PowerShell 脚本,您应该阅读整个页面并彻底理解它。如果你不这样做,你以后会恨自己的。
  • 在我看来,使用静态类函数是正确的干净解决方案,如此答案:stackoverflow.com/a/42743143/1915920

标签: powershell


【解决方案1】:

PowerShell 具有非常古怪的返回语义 - 至少从更传统的编程角度来看是这样。有两个主要想法可以帮助您:

  • 所有输出都被捕获并返回
  • return 关键字实际上只是表示一个逻辑退出点

因此,以下两个脚本块将有效地完成完全相同的事情:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

第二个示例中的 $a 变量作为输出留在管道上,如前所述,返回所有输出。事实上,在第二个示例中,您可以完全省略返回,并且会得到相同的行为(返回将隐含在函数自然完成和退出时)。

如果没有更多的函数定义,我无法说明为什么你会得到一个 PSMethod 对象。我的猜测是,您可能有几行没有被捕获并被放置在输出管道上的东西。

还值得注意的是,您可能不需要这些分号 - 除非您将多个表达式嵌套在一行中。

您可以在 TechNet 上的 about_Return 页面上阅读有关返回语义的更多信息,或者从 PowerShell 本身调用 help return 命令。

【讨论】:

  • 那么有没有办法从函数中返回一个缩放器值?
  • 如果你的函数返回一个哈希表,那么它会这样处理结果,但是如果你在函数体中“回显”任何东西,包括其他命令的输出,结果就会突然混合。
  • 如果您想从函数中将内容输出到屏幕而不将该文本作为函数结果的一部分返回,请在设置变量 $DebugPreference = "Continue" 后使用命令 write-debug 而不是 "SilentlyContinue"
  • 这有帮助,但这个stackoverflow.com/questions/22663848/… 帮助更大。通过“ ... | Out-Null”在被调用函数中管道命令/函数调用的不需要的结果,然后返回你想要的东西。
  • @LukePuplett echo 对我来说是个问题!这一点非常重要。我正在返回一个哈希表,遵循其他所有内容,但返回值仍然不是我所期望的。删除了 echo 并像魅力一样工作。
【解决方案2】:

PowerShell 的这部分可能是最愚蠢的方面。函数期间生成的任何无关输出都会污染结果。有时没有任何输出,然后在某些情况下,除了您计划的返回值之外,还有一些其他计划外的输出。

因此,我从原始函数调用中删除了分配,因此输出最终显示在屏幕上,然后逐步执行,直到调试器窗口中弹出我不打算的内容(使用 PowerShell ISE)。

即使是在外部作用域中保留变量之类的事情也会导致输出,例如 [boolean]$isEnabled,除非你将其设置为 [boolean]$isEnabled = $false,否则它会令人讨厌地吐出 False。

另一个不错的是$someCollection.Add("thing"),它会吐出新的收集计数。

【讨论】:

  • 您为什么只保留一个名称,而不是使用明确定义的值正确初始化变量的最佳实践。
  • 我认为您的意思是,对于该范围中使用的每个变量,您在每个范围的开头都有一个变量声明块。在使用任何语言(包括 C#)之前,您仍然应该或可能已经初始化所有变量。同样重要的是,在 powershell 中执行 [boolean]$a 实际上并没有声明变量 $a。您可以通过使用$a.gettype() 行来确认这一点,并将该输出与您使用字符串$a = [boolean]$false 声明和初始化时进行比较。
  • 你可能是对的,我只是更喜欢更明确的设计来防止意外写入。
  • 从程序员的角度来看,虽然我是 PS-n00b,但我不确定我是否发现这是 PS 语法的许多烦人方面中最糟糕的。到底有没有人认为写“x -gt y”而不是“x > y”更可取?!?当然,至少内置运算符可以使用更人性化的语法?
  • @TheDag 因为它首先是 shell,其次是编程语言; >< 已用于 I/O 流重定向到/来自文件。 >= 将标准输出写入名为 = 的文件。
【解决方案3】:

使用 PowerShell 5,我们现在可以创建类。将您的函数更改为一个类,并且 return 将返回它之前的对象。这是一个非常简单的例子。

class test_class {
    [int]return_what() {
        Write-Output "Hello, World!"
        return 808979
    }
}
$tc = New-Object -TypeName test_class
$tc.return_what()

如果这是一个函数,预期的输出将是

Hello World
808979

但作为一个类,唯一返回的是整数 808979。一个类有点像保证它只会返回声明的类型或 void。

【讨论】:

  • 注意。它还支持static 方法。导致语法[test_class]::return_what()
  • "如果这是一个函数,那么预期的输出将是 'Hello World\n808979" -- 我不认为这是正确的吗?输出值始终只是最后一条语句的值,在您的情况下为 return 808979,函数与否。
  • @amn 谢谢!您是非常正确的,该示例仅在函数内创建输出时才会返回该示例。这让我很恼火。有时您的函数可能会生成输出,并且该输出加上您在 return 语句中添加的任何值都会返回。如您所知,类只会返回声明的类型或 void。如果您更喜欢使用函数,那么一种简单的方法是将函数分配给一个变量,如果返回的值超过返回值,则 var 是一个数组,返回值将是数组中的最后一项。
  • 那么,您对名为Write-Output 的命令有何期望?这里提到的不是“控制台”输出,而是函数/cmdlet 的输出。如果你想在控制台上转储东西,有Write-VerboseWrite-HostWrite-Error 函数等等。基本上,Write-Output 更接近于return 语义,而不是控制台输出过程。不要滥用它,使用 Write-Verbose 表示冗长,这就是它的用途。
  • @amn 我很清楚这不是控制台。这是一种快速的方式来展示返回处理函数与类内部意外输出的方式。在函数中,所有输出 + 您返回的值都被传回。但是只有在类的返回中指定的值被传递包。尝试自己运行它们。
【解决方案4】:

作为一种解决方法,我一直在返回数组中您从函数返回的最后一个对象...这不是一个很好的解决方案,但总比没有好:

someFunction {
   $a = "hello"
   "Function is running"
   return $a
}

$b = someFunction
$b = $b[($b.count - 1)]  # Or
$b = $b[-1]              # Simpler

总而言之,写同一件事的更单一的方式可能是:

$b = (someFunction $someParameter $andAnotherOne)[-1]

【讨论】:

  • $b = $b[-1] 将是获取最后一个元素或数组的一种更简单的方法,但更简单的是不输出您不想要的值。
  • @Duncan 如果我确实想在函数中输出内容,同时我还想要一个返回值呢?
  • @ThoAppelsin 如果您的意思是要向终端写入内容,请使用write-host 直接输出。
【解决方案5】:

我传递一个带有单个结果成员的简单 Hashtable 对象以避免返回疯狂,因为我还想输出到控制台。它通过引用传递。

function sample-loop($returnObj) {
  for($i = 0; $i -lt 10; $i++) {
    Write-Host "loop counter: $i"
    $returnObj.result++
  }
}

function main-sample() {
  $countObj = @{ result = 0 }
  sample-loop -returnObj $countObj
  Write-Host "_____________"
  Write-Host "Total = " ($countObj.result)
}

main-sample

您可以在我的GitHub project unpackTunes 上查看实际使用示例。

【讨论】:

    【解决方案6】:

    现有答案是正确的,但有时您实际上并没有使用Write-Outputreturn 明确返回某些内容,但函数结果中有一些神秘价值。这可能是像New-Item这样的内置函数的输出

    PS C:\temp> function ContrivedFolderMakerFunction {
    >>    $folderName = [DateTime]::Now.ToFileTime()
    >>    $folderPath = Join-Path -Path . -ChildPath $folderName
    >>    New-Item -Path $folderPath -ItemType Directory
    >>    return $true
    >> }
    PS C:\temp> $result = ContrivedFolderMakerFunction
    PS C:\temp> $result
    
    
        Directory: C:\temp
    
    
    Mode                LastWriteTime         Length Name
    ----                -------------         ------ ----
    d-----         2/9/2020   4:32 PM                132257575335253136
    True
    

    目录创建的所有额外噪音都被收集并在输出中发出。缓解这种情况的简单方法是将| Out-Null 添加到New-Item 语句的末尾,或者您可以将结果分配给一个变量而不使用该变量。它看起来像这样......

    PS C:\temp> function ContrivedFolderMakerFunction {
    >>    $folderName = [DateTime]::Now.ToFileTime()
    >>    $folderPath = Join-Path -Path . -ChildPath $folderName
    >>    New-Item -Path $folderPath -ItemType Directory | Out-Null
    >>    # -or-
    >>    $throwaway = New-Item -Path $folderPath -ItemType Directory 
    >>    return $true
    >> }
    PS C:\temp> $result = ContrivedFolderMakerFunction
    PS C:\temp> $result
    True
    

    New-Item 可能是其中比较有名的,但其他包括所有的 StringBuilder.Append*() 方法,以及 SqlDataAdapter.Fill() 方法。

    【讨论】:

    • Out-Default,如果您确实想显示它但不希望它成为返回值。
    【解决方案7】:

    不看代码很难说。确保您的函数返回的对象不超过一个,并确保您捕获来自其他调用的任何结果。你得到什么:

    @($returnURL).count
    

    无论如何,有两个建议:

    将对象转换为字符串:

    ...
    return [string]$rs
    

    或者只是用双引号将其括起来,与上面相同,但键入更短:

    ...
    return "$rs"
    

    【讨论】:

    • 或者甚至只是"$rs" - 只有在从函数中提前返回时才需要return。省略返回是更好的 PowerShell 习惯用法。
    【解决方案8】:

    Luke's description of the function results 在这些情况下似乎是正确的。我只想了解根本原因,PowerShell 产品团队会对此行为采取一些措施。这似乎很常见,并且花费了我太多的调试时间。

    为了解决这个问题,我一直在使用全局变量,而不是返回并使用函数调用中的值。

    这里还有一个关于全局变量使用的问题: Setting a global PowerShell variable from a function where the global variable name is a variable passed to the function

    【讨论】:

      【解决方案9】:

      以下仅返回 4 作为答案。当您替换字符串的添加表达式时,它会返回第一个字符串。

      Function StartingMain {
        $a = 1 + 3
        $b = 2 + 5
        $c = 3 + 7
        Return $a
      }
      
      Function StartingEnd($b) {
        Write-Host $b
      }
      
      StartingEnd(StartingMain)
      

      这也可以用于数组。下面的示例将返回“文本 2”

      Function StartingMain {
        $a = ,@("Text 1","Text 2","Text 3")
        Return $a
      }
      
      Function StartingEnd($b) {
        Write-Host $b[1]
      }
      
      StartingEnd(StartingMain)
      

      请注意,您必须在函数本身下面调用该函数。否则,第一次运行时会返回不知道“StartingMain”是什么的错误。

      【讨论】:

        【解决方案10】:

        您需要在返回之前清除输出。尝试使用Out-Null。这就是 powershell return 的工作原理。它返回的不是您想要的变量,而是整个函数的输出。所以你的例子是:

        function Return-Url
        {
           param([string] $url)
        
           . {
               $rs = $url.ToString();
               return
           } | Out-Null
           
           return $rs
        }
        
        $result = Return-Url -url "https://stackoverflow.com/questions/10286164/function-return-value-in-powershell"
        
        Write-Host $result
        Write-Host $result.GetType()
        

        结果是:

        https://stackoverflow.com/questions/10286164/function-return-value-in-powershell
        System.String
        

        感谢https://riptutorial.com/powershell/example/27037/how-to-work-with-functions-returns

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-09-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-12
          • 1970-01-01
          相关资源
          最近更新 更多