Valore restituito dalla funzione in PowerShell


188

Ho sviluppato una funzione PowerShell che esegue una serie di azioni relative al provisioning dei siti del team di SharePoint . Alla fine, voglio che la funzione restituisca l'URL del sito sottoposto a provisioning come stringa, quindi alla fine della mia funzione ho il seguente codice:

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

Il codice che chiama questa funzione è simile a:

$returnURL = MyFunction -param 1 ...

Quindi mi aspetto una stringa, tuttavia non lo è. Invece, è un oggetto di tipo System.Management.Automation.PSMethod. Perché restituisce quel tipo invece di un tipo String?


2
Possiamo vedere la dichiarazione di funzione?
zdan,

1
No, non l'invocazione della funzione, mostraci come / dove hai dichiarato "MyFunction". Cosa succede quando si esegue: Funzione Get-Item: \ MyFunction
zdan,

1
Suggerito un modo alternativo per risolvere questo problema: stackoverflow.com/a/42842865/992301
Feru

Penso che questa sia una delle domande più importanti di PowerShell sullo overflow dello stack relativamente a funzioni / metodi. Se stai pensando di imparare gli script di PowerShell, dovresti leggere l'intera pagina e comprenderla a fondo. Se non lo fai, ti odierai più tardi.
Birdman,

Risposte:


271

PowerShell ha una semantica di ritorno davvero stravagante, almeno se vista da una prospettiva di programmazione più tradizionale. Ci sono due idee principali per avvolgere la testa:

  • Tutto l'output viene acquisito e restituito
  • La parola chiave return in realtà indica solo un punto di uscita logico

Pertanto, i seguenti due blocchi di script faranno esattamente la stessa cosa:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

La variabile $ a nel secondo esempio viene lasciata come output sulla pipeline e, come detto, viene restituito tutto l'output. In effetti, nel secondo esempio potresti omettere del tutto il ritorno e otterrai lo stesso comportamento (il ritorno verrebbe implicato poiché la funzione si completa ed esce naturalmente).

Senza più della definizione della tua funzione non posso dire perché stai ottenendo un oggetto PSMethod. La mia ipotesi è che probabilmente hai qualche riga in meno che non viene catturata e viene posizionata sulla pipeline di output.

Vale anche la pena notare che probabilmente non sono necessari quei punti e virgola, a meno che non si annidino più espressioni su una sola riga.

Puoi leggere ulteriori informazioni sulla semantica di ritorno nella pagina about_Return su TechNet o invocando il help returncomando dalla stessa PowerShell.


13
quindi c'è un modo per restituire un valore di ridimensionamento da una funzione?
ChiliYago,

10
Se la tua funzione restituisce una tabella hash, allora tratterà il risultato in quanto tale, ma se "echi" qualcosa all'interno del corpo della funzione, incluso l'output di altri comandi, e all'improvviso il risultato viene mescolato.
Luke Puplett,

5
Se si desidera produrre elementi sullo schermo dall'interno di una funzione senza restituire quel testo come parte del risultato della funzione, utilizzare il comando write-debugdopo aver impostato la variabile $DebugPreference = "Continue"anziché"SilentlyContinue"
BeowulfNode42

7
Ciò ha aiutato, ma questo stackoverflow.com/questions/22663848/… ha aiutato ancora di più. Installa i risultati indesiderati di comandi / chiamate di funzione nella funzione chiamata tramite "... | Null" e poi restituisci semplicemente ciò che desideri.
Straff,

1
@LukePuplett è echostato il problema per me! Quel piccolo punto laterale è molto molto importante. Stavo restituendo un hashtable, seguendo tutto il resto, ma il valore restituito non era quello che mi aspettavo. Rimosso echoe ha funzionato come un fascino.
Ayo I,

54

Questa parte di PowerShell è probabilmente l'aspetto più stupido. Qualsiasi uscita estranea generata durante una funzione inquinerà il risultato. A volte non c'è alcun output, e quindi in alcune condizioni c'è qualche altro output non pianificato, oltre al valore di ritorno pianificato.

Quindi, rimuovo l'assegnazione dalla chiamata di funzione originale, quindi l'output finisce sullo schermo e quindi passo avanti fino a quando qualcosa che non avevo pianificato viene visualizzato nella finestra del debugger (utilizzando PowerShell ISE ).

Anche cose come riservare variabili in ambiti esterni causano output, come il [boolean]$isEnabledquale sputerà fastidiosamente un False a meno che tu non lo realizzi [boolean]$isEnabled = $false.

Un altro buono è quello $someCollection.Add("thing")che sputa il conteggio della nuova collezione.


2
Perché dovresti semplicemente riservare un nome invece di fare le migliori pratiche per inizializzare correttamente la variabile con un valore esplicitamente definito.
BeowulfNode42

2
Presumo che tu intenda che hai un blocco di dichiarazioni di variabili all'inizio di ogni ambito per ogni variabile utilizzata in tale ambito. Dovresti comunque, o già, inizializzare tutte le variabili prima di usarle in qualsiasi linguaggio incluso C #. Anche ciò che è importante fare [boolean]$ain PowerShell non dichiara effettivamente la variabile $ a. Puoi confermarlo seguendo quella riga con la riga $a.gettype()e confrontare quell'output con quando dichiari e inizializzi con la stringa $a = [boolean]$false.
BeowulfNode42

3
Probabilmente hai ragione, preferirei un design più esplicito per evitare scritture accidentali.
Luke Puplett,

4
Venendo dal punto di vista di un programmatore, sebbene io sia un PS-n00b, non sono così sicuro di trovarlo il peggiore dei molti aspetti fastidiosi della sintassi PS. Come mai qualcuno ha pensato che fosse preferibile scrivere "x -gt y" piuttosto che "x> y"?!? Sicuramente almeno gli operatori integrati avrebbero potuto usare una sintassi più rispettosa dell'uomo?
Il Dag il

5
@TheDag perché è prima una shell, secondo il linguaggio di programmazione; >e <sono già utilizzati per il reindirizzamento del flusso I / O verso / da file. >=scrive stdout in un file chiamato =.
Tessellating Heckler,

38

Con PowerShell 5 ora abbiamo la possibilità di creare classi. Cambia la tua funzione in una classe e return restituirà solo l'oggetto che la precede immediatamente. Ecco un semplice esempio.

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

Se questa fosse una funzione, l'output previsto sarebbe

Hello World
808979

ma come classe l'unica cosa restituita è il numero intero 808979. Una classe è un po 'come una garanzia che restituirà solo il tipo dichiarato o nullo.


6
Nota. Supporta anche staticmetodi. [test_class]::return_what()
Portando

1
"Se questa fosse una funzione, l'output previsto sarebbe 'Hello World \ n808979" - Non penso sia corretto? Il valore di output è sempre solo il valore dell'ultima istruzione, nel tuo caso return 808979, funzione o no.
AMN

1
@amn Grazie! Hai ragione che l'esempio restituirebbe che se avesse creato l'output all'interno della funzione. Questo è ciò che mi dà fastidio. A volte la funzione può generare output e quell'output più qualsiasi valore aggiunto nell'istruzione return viene restituito. Le classi che conosci restituiranno solo il tipo dichiarato o nullo. Se si preferiscono utilizzare le funzioni, un metodo semplice consiste nell'assegnare la funzione a una variabile e se viene restituito un valore superiore al valore restituito, var è un array e il valore restituito sarà l'ultimo elemento dell'array.
DaSmokeDog,

1
Bene, cosa ti aspetti da un comando chiamato Write-Output? Non è l'output "console" a cui si fa riferimento qui, è l'output per la funzione / cmdlet. Se vuoi scaricare cose sulla console ci sono Write-Verbose, Write-Hoste Write-Errorfunzioni, tra gli altri. Fondamentalmente, Write-Outputè più vicino alla returnsemantica, che a una procedura di output della console. Non abusarne, Write-Verboseusalo per la verbosità, ecco a cosa serve.
AMN

1
@amn Sono molto consapevole che questa non è la console. Era un modo scarsamente veloce di mostrare il modo in cui un ritorno gestisce l'output imprevisto all'interno di una funzione rispetto a una classe. in una funzione tutto l'output + il valore restituito vengono tutti restituiti. TUTTAVIA viene trasmesso solo il valore specificato nel ritorno di una classe. Prova a gestirli per te stesso.
DaSmokeDog

31

Per ovviare al problema, ho restituito l'ultimo oggetto dell'array che si ottiene dalla funzione ... Non è un'ottima soluzione, ma è meglio di niente:

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

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

Tutto sommato, un modo più univoco di scrivere la stessa cosa potrebbe essere:

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

7
$b = $b[-1]sarebbe un modo più semplice per ottenere l'ultimo elemento o l'array, ma ancora più semplice sarebbe semplicemente non produrre valori che non si desidera.
Duncan,

@Duncan E se volessi produrre l'output nella funzione, mentre allo stesso tempo voglio anche un valore di ritorno?
Utkan Gezer,

@ThoAppelsin se vuoi dire che vuoi scrivere cose sul terminale, allora usale per emetterle write-hostdirettamente.
Duncan,

7

Passo intorno a un semplice oggetto Hashtable con un singolo membro risultato per evitare la follia di ritorno poiché desidero anche eseguire l'output sulla console. Agisce attraverso il passaggio per riferimento.

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

Puoi vedere un esempio reale di utilizzo nel mio progetto GitHub unpackTunes .


4

È difficile dirlo senza guardare il codice. Assicurarsi che la funzione non restituisca più di un oggetto e che si acquisiscano i risultati ottenuti da altre chiamate. Cosa ottieni:

@($returnURL).count

Comunque, due suggerimenti:

Trasmetti l'oggetto alla stringa:

...
return [string]$rs

O semplicemente racchiuderlo tra virgolette, come sopra ma più breve da digitare:

...
return "$rs"

1
O anche solo "$rs": returnè necessario solo quando si torna presto dalla funzione. Tralasciando il ritorno è meglio il linguaggio di PowerShell.
David Clarke,

4

La descrizione della funzione di Luke in questi scenari sembra essere corretta. Vorrei solo capire la causa principale e il team del prodotto PowerShell farebbe qualcosa per il comportamento. Sembra essere abbastanza comune e mi è costato troppo tempo per il debug.

Per aggirare questo problema, ho usato variabili globali anziché restituire e utilizzare il valore dalla chiamata di funzione.

Ecco un'altra domanda sull'uso delle variabili globali: Impostazione di una variabile PowerShell globale da una funzione in cui il nome della variabile globale è una variabile passata alla funzione


3

Quanto segue restituisce semplicemente 4 come risposta. Quando si sostituiscono le espressioni add per le stringhe, viene restituita la prima stringa.

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

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

StartingEnd(StartingMain)

Questo può essere fatto anche per un array. L'esempio seguente restituirà "Testo 2"

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

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

StartingEnd(StartingMain)

Nota che devi chiamare la funzione sotto la funzione stessa. Altrimenti, la prima volta che viene eseguito restituirà un errore che non sa cosa sia "StartingMain".


2

Le risposte esistenti sono corrette, ma a volte in realtà non stai restituendo qualcosa in modo esplicito con a Write-Outputo a return, ma c'è qualche valore misterioso nei risultati della funzione. Questo potrebbe essere l'output di una funzione integrata comeNew-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

Tutto quel rumore extra della creazione della directory viene raccolto ed emesso nell'output. Il modo semplice per mitigare questo è aggiungere | Out-Nullalla fine New-Itemdell'istruzione, oppure è possibile assegnare il risultato a una variabile e semplicemente non usare quella variabile. Sembrerebbe così ...

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è probabilmente il più famoso di questi, ma altri includono tutti i StringBuilder.Append*()metodi, oltre al SqlDataAdapter.Fill()metodo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.