Valor de retorno da função no PowerShell

188

Eu desenvolvi uma função do PowerShell que executa várias ações que envolvem o provisionamento de sites da Equipe do SharePoint . Por fim, quero que a função retorne a URL do site provisionado como uma String, portanto, no final da minha função, tenho o seguinte código:

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

O código que chama essa função se parece com:

$returnURL = MyFunction -param 1 ...

Então, eu estou esperando uma String, mas não é. Em vez disso, é um objeto do tipo System.Management.Automation.PSMethod. Por que ele está retornando esse tipo em vez de um tipo String?

ChiliYago
fonte
2
Podemos ver a declaração da função?
Zdan
1
Não, não a chamada de função, mostre-nos como / onde você declarou "MyFunction". O que acontece quando você faz: Get-Item função: \ MyFunction
Zdan
1
Sugeriu uma maneira alternativa de resolver isso: stackoverflow.com/a/42842865/992301
Feru
Acho que essa é uma das perguntas mais importantes do PowerShell sobre estouro de pilha relacionado a funções / métodos. Se você planeja aprender scripts do PowerShell, leia esta página inteira e entenda-a completamente. Você se odiará mais tarde, se não o fizer.
Birdman

Respostas:

271

O PowerShell tem semântica de retorno realmente maluca - pelo menos quando vista de uma perspectiva de programação mais tradicional. Há duas idéias principais para você entender:

  • Toda a saída é capturada e retornada
  • A palavra-chave return realmente indica apenas um ponto de saída lógico

Portanto, os dois blocos de script a seguir farão exatamente a mesma coisa:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

A variável $ a no segundo exemplo é deixada como saída no pipeline e, como mencionado, toda a saída é retornada. De fato, no segundo exemplo, você poderia omitir completamente o retorno e obteria o mesmo comportamento (o retorno seria implícito, pois a função naturalmente termina e sai).

Sem mais definições de função, não sei dizer por que você está recebendo um objeto PSMethod. Meu palpite é que você provavelmente tem algo que algumas linhas acima não estão sendo capturadas e estão sendo colocadas no pipeline de saída.

Também é importante notar que você provavelmente não precisa desses pontos e vírgulas - a menos que esteja aninhando várias expressões em uma única linha.

Você pode ler mais sobre a semântica de retorno na página about_Return no TechNet ou chamando o help returncomando do próprio PowerShell.

Goyuix
fonte
13
existe alguma maneira de retornar um valor de scaler de uma função?
precisa saber é o seguinte
10
Se sua função retornar uma hashtable, ele tratará o resultado como tal, mas se você "repetir" qualquer coisa dentro do corpo da função, incluindo a saída de outros comandos, e de repente o resultado será misturado.
Luke Puplett
5
Se você quiser coisas saída para a tela de dentro de uma função sem retornar esse texto como parte do resultado da função, use o comando write-debugdepois de definir a variável $DebugPreference = "Continue"em vez de"SilentlyContinue"
BeowulfNode42
7
Isso ajudou, mas esse stackoverflow.com/questions/22663848/… ajudou ainda mais. Canalize resultados indesejados de comandos / chamadas de função na função chamada via "... | Out-Null" e, em seguida, retorne o que deseja.
Straff 21/03/2015
1
@LukePuplett echofoi o problema para mim! Esse pequeno ponto lateral é muito, muito importante. Eu estava retornando uma hashtable, seguindo todo o resto, mas ainda assim o valor de retorno não era o que eu esperava. Removido echoe funcionou como um encanto.
Ayo I
54

Essa parte do PowerShell é provavelmente o aspecto mais estúpido. Qualquer saída estranha gerada durante uma função poluirá o resultado. Às vezes, não há saída e, em algumas condições, há outra saída não planejada, além do valor de retorno planejado.

Portanto, removo a atribuição da chamada de função original, para que a saída acabe na tela e depois avance até que algo que eu não planejei saia na janela do depurador (usando o PowerShell ISE ).

Mesmo coisas como reservar variáveis ​​em escopos externos causam saída, como a [boolean]$isEnabledque irrita irritantemente um False, a menos que você o faça [boolean]$isEnabled = $false.

Outra boa é $someCollection.Add("thing")que cospe a nova contagem de coleção.

Luke Puplett
fonte
2
Por que você apenas reservou um nome em vez de fazer a melhor prática de inicializar corretamente a variável com um valor explicitamente definido.
BeowulfNode42
2
Suponho que você queira dizer que possui um bloco de declarações de variáveis ​​no início de cada escopo para cada variável usada nesse escopo. Você ainda deve ou já pode estar inicializando todas as variáveis ​​antes de usá-las em qualquer idioma, incluindo C #. Também importante [boolean]$ano PowerShell não declara a variável $ a. Você pode confirmar isso seguindo essa linha com a linha $a.gettype()e comparar essa saída com quando você declara e inicializa com a sequência $a = [boolean]$false.
precisa saber é o seguinte
3
Você provavelmente está certo, eu prefiro um design mais explícito para evitar gravações acidentais.
Luke Puplett
4
Vindo da perspectiva de um programador, embora eu seja um PS-n00b, não tenho tanta certeza de achar esse o pior dos muitos aspectos irritantes da sintaxe do PS. Como diabos alguém achou preferível escrever "x-gt y" em vez de "x> y"?!? Certamente, pelo menos, os operadores internos poderiam ter usado uma sintaxe mais amigável ao ser humano?
The Dag
5
@ TheDag porque é um shell primeiro, linguagem de programação segundo; >e <já são usados ​​para redirecionamento de fluxo de E / S para / de arquivos. >=escreve stdout em um arquivo chamado =.
TessellatingHeckler
38

Com o PowerShell 5, agora temos a capacidade de criar classes. Mude sua função para uma classe, e return retornará apenas o objeto imediatamente anterior a ela. Aqui está um exemplo muito simples.

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

Se isso fosse uma função, a saída esperada seria

Hello World
808979

mas como uma classe, a única coisa retornada é o número inteiro 808979. Uma classe é como uma garantia de que ela retornará apenas o tipo declarado ou nulo.

DaSmokeDog
fonte
6
Nota. Ele também suporta staticmétodos. Levando à sintaxe[test_class]::return_what()
Sancarn
1
"Se isso fosse uma função, a saída esperada seria 'Hello World \ n808979" - não acho que esteja correto? O valor de saída é sempre apenas o valor da última instrução, no seu caso return 808979, função ou não.
amn
1
@amn Obrigado! Você está muito correto, o exemplo retornaria apenas se ele criava saída dentro da função. O que é o que me incomoda. Às vezes, sua função pode gerar saída e essa saída mais qualquer valor adicionado na instrução de retorno é retornado. As classes que você conhece retornarão apenas o tipo declarado ou nulo. Se você preferir usar funções, um método fácil é atribuir a função a uma variável e, se mais do que o valor de retorno for retornado, var é uma matriz e o valor de retorno será o último item da matriz.
DaSmokeDog 21/02/19
1
Bem, o que você espera de um comando chamado Write-Output? Não é a saída do "console" mencionada aqui, é a saída da função / cmdlet. Se você quiser jogar o material no console existem Write-Verbose, Write-Hoste Write-Errorfunções, entre outros. Basicamente, Write-Outputestá mais próximo da returnsemântica, do que de um procedimento de saída do console. Não abuse, use Write-Verbosepara verbosidade, é para isso que serve.
quer
1
@ amn Estou muito ciente de que este não é o console. Foi uma maneira rápida de mostrar como um retorno lida com resultados inesperados dentro de uma função versus uma classe. em uma função, toda a saída + o valor retornado são todos repassados. No entanto, apenas o valor especificado no retorno de uma classe é passado no pacote. Tente executá-los por si mesmo.
DaSmokeDog 24/02/19
31

Como solução alternativa, eu retornei o último objeto da matriz que você retorna da função ... Não é uma ótima solução, mas é melhor que nada:

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

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

Em suma, uma maneira mais simples de escrever a mesma coisa poderia ser:

$b = (someFunction $someParameter $andAnotherOne)[-1]
Jonesy
fonte
7
$b = $b[-1]seria uma maneira mais simples de obter o último elemento ou a matriz, mas ainda mais simples seria simplesmente não gerar valores que você não deseja.
Duncan
@ Duncan E se eu quiser exibir as coisas na função, enquanto ao mesmo tempo também quero um valor de retorno?
Utkan Gezer
@ThoAppelsin, se você quer escrever coisas no terminal, use-as write-hostpara produzi-las diretamente.
Duncan
7

Eu passo em torno de um objeto Hashtable simples com um único membro de resultado para evitar a loucura de retorno, pois também quero enviar para o console. Atua através de passagem por referência.

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

Você pode ver exemplos reais de uso no meu projeto GitHub, unpackTunes .

O que seria legal
fonte
4

É difícil dizer sem olhar para o código. Verifique se sua função não retorna mais de um objeto e se captura todos os resultados obtidos com outras chamadas. O que você ganha por:

@($returnURL).count

Enfim, duas sugestões:

Transmitir o objeto para string:

...
return [string]$rs

Ou apenas coloque-o entre aspas duplas, igual ao anterior, mas mais curto para digitar:

...
return "$rs"
Shay Levy
fonte
1
Ou apenas "$rs"- o returnnecessário apenas ao retornar cedo da função. Deixar de lado o retorno é o melhor idioma do PowerShell.
David Clarke
4

A descrição de Lucas da função resulta nesses cenários parece correta. Eu só quero entender a causa raiz e a equipe do produto PowerShell faria algo sobre o comportamento. Parece ser bastante comum e me custou muito tempo de depuração.

Para contornar esse problema, eu tenho usado variáveis ​​globais em vez de retornar e usar o valor da chamada de função.

Aqui está outra pergunta sobre o uso de variáveis ​​globais: Definindo uma variável global do PowerShell a partir de uma função em que o nome da variável global é uma variável passada para a função

Ted B.
fonte
3

O seguinte simplesmente retorna 4 como resposta. Quando você substitui as expressões add para strings, ele retorna a primeira string.

Function StartingMain {
  $a = 1 + 3
  $b = 2 + 5
  $c = 3 + 7
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b
}

StartingEnd(StartingMain)

Isso também pode ser feito para uma matriz. O exemplo abaixo retornará "Texto 2"

Function StartingMain {
  $a = ,@("Text 1","Text 2","Text 3")
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b[1]
}

StartingEnd(StartingMain)

Observe que você precisa chamar a função abaixo da própria função. Caso contrário, na primeira execução, ele retornará um erro que não sabe o que é "StartingMain".

Edwin
fonte
2

As respostas existentes estão corretas, mas às vezes você não está retornando algo explicitamente com a Write-Outputou a return, mas há algum valor misterioso nos resultados da função. Pode ser a saída de uma função embutida comoNew-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

Todo esse ruído extra da criação do diretório está sendo coletado e emitido na saída. A maneira mais fácil de mitigar isso é adicionar | Out-Nullao final da New-Iteminstrução, ou você pode atribuir o resultado a uma variável e simplesmente não usá-la. Ficaria assim ...

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é provavelmente o mais famoso deles, mas outros incluem todos os StringBuilder.Append*()métodos, bem como o SqlDataAdapter.Fill()método.

StingyJack
fonte