Come faccio a passare più parametri in una funzione in PowerShell?


438

Se ho una funzione che accetta più di un parametro stringa, il primo parametro sembra ottenere tutti i dati assegnati ad esso e i parametri rimanenti vengono passati come vuoti.

Uno script di test rapido:

Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC", "DEF")

L'output generato è

$arg1 value: ABC DEF
$arg2 value: 

L'output corretto dovrebbe essere:

$arg1 value: ABC
$arg2 value: DEF

Questo sembra essere coerente tra v1 e v2 su più macchine, quindi ovviamente sto facendo qualcosa di sbagliato. Qualcuno può indicare esattamente cosa?


3
Chiami così:Test "ABC" "DEF"
Ranadip Dutta,

Risposte:


576

I parametri nelle chiamate alle funzioni in PowerShell (tutte le versioni) sono separati da spazio, non separati da virgola . Inoltre, le parentesi sono del tutto inutili e causeranno un errore di analisi in PowerShell 2.0 (o successivo) se Set-StrictModeè attivo. Gli argomenti tra parentesi vengono utilizzati solo nei metodi .NET.

function foo($a, $b, $c) {
   "a: $a; b: $b; c: $c"
}

ps> foo 1 2 3
a: 1; b: 2; c: 3

20
La cosa più importante che alla fine mi ha aiutato a "rimanere" nella mia mente è l'ultima frase: "Gli argomenti tra parentesi sono usati solo nei metodi .NET."
Ashley,

1
Preferisco usare la parentesi e la virgola separate .. è possibile farlo in PowerShell?
sam yi

8
@samyi No. Il passaggio di a (1,2,3)a una funzione viene effettivamente trattato come un array; un solo argomento. Se si desidera utilizzare gli argomenti di stile del metodo OO, utilizzare i moduli:$m = new-module -ascustomobject { function Add($x,$y) { $x + $y } }; $m.Add(1,1)
x0n

4
Powershellè un linguaggio di shell ed è comune per i linguaggi di shell utilizzare gli spazi come separatore di token. Non direi che Powershellè l'essere diverso qui, è perfettamente in linea con altre shell di default del sistema come cmd, sh, bash, ecc
Bender il più grande

270

La risposta corretta è già stata fornita, ma questo problema sembra abbastanza diffuso da giustificare alcuni dettagli aggiuntivi per coloro che desiderano comprendere le sottigliezze.

Avrei aggiunto questo solo come commento, ma volevo includere un'illustrazione: ho strappato questo dal mio grafico di riferimento rapido sulle funzioni di PowerShell. Ciò presuppone che la firma della funzione f sia f($a, $b, $c):

Insidie ​​della sintassi di una chiamata di funzione

Pertanto, si può chiamare una funzione con parametri posizionali separati da spazio o parametri nominati indipendenti dall'ordine . Le altre insidie ​​rivelano che devi conoscere le virgole, le parentesi e lo spazio bianco.

Per ulteriori informazioni, vedere il mio articolo Down the Rabbit Hole: A Study in PowerShell Pipelines, Functions and Parameters . L'articolo contiene anche un collegamento al riferimento rapido / grafico a muro.


4
La spiegazione con la sintassi più dettagliata che chiama ogni parametro e lo assegna un valore lo ha davvero cementato. Grazie!
ConstantineK,

7
Grazie, mi stava guidando mentalmente non capendo cosa stavo facendo di sbagliato. Quando alla fine ho fatto bene avevo fame di una spiegazione di questo comportamento.
BSAFH,

1
Grazie per aver pubblicato questo come risposta. Sapere perché qualcosa di sbagliato è importante tanto quanto ciò che è sbagliato.
Gavin Ward,

4
Questa è una risposta molto migliore. Dovrebbe essere più in alto.
Mark Bertenshaw,

53

Ci sono alcune buone risposte qui, ma volevo sottolineare un paio di altre cose. I parametri di funzione sono in realtà un luogo in cui brilla PowerShell. Ad esempio, puoi avere parametri nominali o posizionali in funzioni avanzate in questo modo:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [int] $Id
    )
}

Quindi puoi chiamarlo specificando il nome del parametro oppure puoi semplicemente usare i parametri posizionali, dal momento che li hai definiti esplicitamente. Quindi uno di questi funzionerebbe:

Get-Something -Id 34 -Name "Blah"
Get-Something "Blah" 34

Il primo esempio funziona anche se Nameviene fornito secondo, perché abbiamo usato esplicitamente il nome del parametro. Il secondo esempio funziona in base alla posizione, quindi Namedovrebbe essere il primo. Quando possibile, cerco sempre di definire le posizioni in modo che entrambe le opzioni siano disponibili.

PowerShell ha anche la capacità di definire set di parametri. Lo utilizza al posto del sovraccarico del metodo ed è di nuovo abbastanza utile:

function Get-Something
{
    [CmdletBinding(DefaultParameterSetName='Name')]
    Param
    (
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Name')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Id')]
         [int] $Id
    )
}

Ora la funzione prenderà un nome o un id, ma non entrambi. Puoi usarli in posizione, o per nome. Poiché sono di tipo diverso, PowerShell lo scoprirà. Quindi tutti questi funzionerebbero:

Get-Something "some name"
Get-Something 23
Get-Something -Name "some name"
Get-Something -Id 23

È inoltre possibile assegnare parametri aggiuntivi ai vari set di parametri. (Questo è stato un esempio piuttosto semplice.) All'interno della funzione, è possibile determinare quale set di parametri è stato utilizzato con la proprietà $ PsCmdlet.ParameterSetName. Per esempio:

if($PsCmdlet.ParameterSetName -eq "Name")
{
    Write-Host "Doing something with name here"
}

Quindi, in una nota a margine correlata, c'è anche la convalida dei parametri in PowerShell. Questa è una delle mie funzionalità preferite di PowerShell e rende il codice all'interno delle tue funzioni molto pulito. Esistono numerose convalide che puoi utilizzare. Un paio di esempi sono:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidatePattern('^Some.*')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [ValidateRange(10,100)]
         [int] $Id
    )
}

Nel primo esempio, ValidatePattern accetta un'espressione regolare che assicura che il parametro fornito corrisponda a quello che ti aspetti. In caso contrario, viene generata un'eccezione intuitiva, che ti dice esattamente cosa non va. Quindi, in questo esempio, "Something" funzionerebbe bene, ma "Summer" non avrebbe superato la convalida.

ValidateRange garantisce che il valore del parametro sia compreso nell'intervallo previsto per un numero intero. Quindi 10 o 99 funzionerebbero, ma 101 genererebbe un'eccezione.

Un altro utile è ValidateSet, che consente di definire esplicitamente una matrice di valori accettabili. Se viene inserito qualcos'altro, verrà generata un'eccezione. Ce ne sono anche altri, ma probabilmente il più utile è ValidateScript. Questo richiede un blocco di script che deve essere valutato in $ true, quindi il limite è il cielo. Per esempio:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidateScript({ Test-Path $_ -PathType 'Leaf' })]
         [ValidateScript({ (Get-Item $_ | select -Expand Extension) -eq ".csv" })]
         [string] $Path
    )
}

In questo esempio, ci viene assicurato non solo che esiste $ Path, ma che è un file (al contrario di una directory) e ha un'estensione .csv. ($ _ si riferisce al parametro, all'interno del tuo scriptblock.) Puoi anche passare a blocchi di script multilinea molto più grandi se è richiesto quel livello, oppure usare più blocchi di script come ho fatto qui. È estremamente utile e rende piacevoli funzioni pulite ed eccezioni intuitive.


3
+1 per dimostrare lo My_Function -NamedParamater "ParamValue"stile della chiamata di funzione. Questo è un modello che dovrebbe seguire più codice di script PS per la leggibilità.
Mister_Tom,

46

Le funzioni di PowerShell vengono chiamate senza parentesi e senza utilizzare la virgola come separatore. Prova a usare:

test "ABC" "DEF"

In PowerShell la virgola (,) è un operatore di array, ad es

$a = "one", "two", "three"

Si imposta $asu un array con tre valori.


16
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test "ABC" "DEF"

11

Se sei uno sviluppatore C # / Java / C ++ / Ruby / Python / Pick-A-Language-From-This-Century e vuoi chiamare la tua funzione con virgole, perché è quello che hai sempre fatto, allora hai bisogno di qualcosa come questo:

$myModule = New-Module -ascustomobject { 
    function test($arg1, $arg2) { 
        echo "arg1 = $arg1, and arg2 = $arg2"
    }
}

Ora chiama:

$myModule.test("ABC", "DEF")

e vedrai

arg1 = ABC, and arg2 = DEF

Java, C ++, Ruby e Python non sono di questo secolo (solo C #), presumendo il calendario gregoriano (anche se alcuni si sono evoluti più di altri).
Peter Mortensen,

Eh. @PeterMortensen la tua tesi è che dovrei dire "Scegli una lingua da questo secolo o dall'ultima"? :-)
Ryan Shillington,

10

Se non sai (o ti interessa) quanti argomenti passerai alla funzione, potresti anche usare un approccio molto semplice come;

Codice :

function FunctionName()
{
    Write-Host $args
}

Ciò stamperebbe tutti gli argomenti. Per esempio:

FunctionName a b c 1 2 3

Produzione

a b c 1 2 3

Lo trovo particolarmente utile quando si creano funzioni che utilizzano comandi esterni che potrebbero avere molti parametri diversi (e facoltativi), ma si basa su detto comando per fornire feedback sugli errori di sintassi, ecc.

Ecco un altro esempio del mondo reale (creazione di una funzione per il comando tracert, che odio dover ricordare il nome troncato);

Codice :

Function traceroute
{
    Start-Process -FilePath "$env:systemroot\system32\tracert.exe" -ArgumentList $args -NoNewWindow
}

7

Se provi:

PS > Test("ABC", "GHI") ("DEF")

ottieni:

$arg1 value: ABC GHI
$arg2 value: DEF

Quindi vedi che le parentesi separano i parametri


Se provi:

PS > $var = "C"
PS > Test ("AB" + $var) "DEF"

ottieni:

$arg1 value: ABC
$arg2 value: DEF

Ora potresti trovare l'utilità immediata delle parentesi - uno spazio non diventerà un separatore per il prossimo parametro - invece hai una funzione di valutazione.


4
I genitori non separano i parametri. Definiscono array.
n.

1
I genitori non definiscono un array, ma definiscono un gruppo, che PowerShell può interpretare come un array. Le matrici vengono definiti con il simbolo ( @), prima degli paren principali, come questo array vuoto: @(); o l'array con due numeri: @(1, 2).
VertigoRay

5

Poiché questa è una domanda frequente, desidero menzionare che una funzione di PowerShell dovrebbe usare verbi approvati ( Verb-Noun come nome della funzione). La parte verbo del nome identifica l'azione eseguita dal cmdlet. La parte del nome del nome identifica l'entità su cui viene eseguita l'azione. Questa regola semplifica l'utilizzo dei cmdlet per utenti avanzati di PowerShell.

Inoltre, puoi specificare cose come se il parametro è obbligatorio e la posizione del parametro:

function Test-Script
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$arg1,

        [Parameter(Mandatory=$true, Position=1)]
        [string]$arg2
    )

    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Per passare il parametro alla funzione è possibile utilizzare la posizione :

Test-Script "Hello" "World"

Oppure si specifica il nome del parametro :

Test-Script -arg1 "Hello" -arg2 "World"

Non usi le parentesi come fai quando chiami una funzione in C #.


Consiglierei di passare sempre i nomi dei parametri quando si utilizza più di un parametro, poiché questo è più leggibile .


Cordiali saluti, il collegamento all'elenco dei verbi approvati non funziona più, ma ora può essere trovato qui - docs.microsoft.com/en-us/powershell/developer/cmdlet/…
Keith Langmead

@KeithLangmead Grazie Keith, ho aggiornato anche la mia risposta.
Martin Brandl,

1
"Verbo-Noun" come sia in verbo sia in sostantivo in maiuscolo? Forse cambiare la risposta per essere più esplicito al riguardo?
Peter Mortensen,

@PeterMortensen Grazie per averlo menzionato. Ho aggiornato la mia risposta.
Martin Brandl,

1
bene, considera di esporre un Get-Nodecmdlet. Sarebbe chiaro per noi che dobbiamo invocare Get-Node, non Retrieve-Node, non Receive-Node, non .....
Martin Brandl

4

Non so cosa stai facendo con la funzione, ma dai un'occhiata all'utilizzo della parola chiave "param". È un po 'più potente per passare i parametri in una funzione e la rende più user friendly. Di seguito è riportato un collegamento a un articolo eccessivamente complesso di Microsoft a riguardo. Non è così complicato come l'articolo sembra farlo sembrare.

Param Usage

Inoltre, ecco un esempio da una domanda su questo sito:

Controlla.


Grazie per la risposta. Tuttavia, ho riscontrato problemi durante la chiamata alla funzione. Non importava se la funzione fosse stata dichiarata con param o senza di essa.
Nasir,

3
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC") ("DEF")

Il risultato verrà fuori come dovrebbe:

2

Ho affermato quanto segue in precedenza:

Il problema comune è l'utilizzo della forma singolare $arg, che non è corretta. Dovrebbe essere sempre plurale come $args.

Il problema non è quello. Infatti,$arg può essere qualsiasi altra cosa. Il problema era l'uso della virgola e delle parentesi.

Corro il seguente codice che ha funzionato e l'output segue:

Codice:

Function Test([string]$var1, [string]$var2)
{
    Write-Host "`$var1 value: $var1"
    Write-Host "`$var2 value: $var2"
}

Test "ABC" "DEF"

Produzione:

$var1 value: ABC
$var2 value: DEF

4
Grazie amico mio, sei in ritardo di un paio d'anni :-) Le prime tre risposte qui hanno affrontato sufficientemente il problema. Posso suggerire di andare alla sezione senza risposta e provare alcune di queste domande?
Nasir,


1

Puoi anche passare parametri in una funzione come questa:

function FunctionName()
{
    Param ([string]$ParamName);
    # Operations
}

3
Sarebbe definire i parametri per una funzione, la domanda originale era su come specificare i parametri quando si chiama la funzione.
Nasir,

1

Non lo vedo menzionato qui, ma la suddivisione degli argomenti è un'alternativa utile e diventa particolarmente utile se si stanno sviluppando dinamicamente gli argomenti di un comando (invece di usareInvoke-Expression ). È possibile eseguire lo splat con matrici per argomenti posizionali e hashtabili per argomenti con nome. Ecco alcuni esempi:

Splat con array (argomenti posizionali)

Test-connessione con argomenti posizionali

Test-Connection www.google.com localhost

Con array Splatting

$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentArray

Si noti che durante lo splatting, si fa riferimento alla variabile splatted con un @invece di a $. È lo stesso quando si usa un Hashtable anche per splat.

Splat With Hashtable (Argomenti con nome)

Test-connessione con argomenti nominati

Test-Connection -ComputerName www.google.com -Source localhost

Con Splashing Hashtable

$argumentHash = @{
  ComputerName = 'www.google.com'
  Source = 'localhost'
}
Test-Connection @argumentHash

Splat Posizionale e argomenti nominati contemporaneamente

Test-Connection con argomenti sia posizionali che nominati

Test-Connection www.google.com localhost -Count 1

Splatting Array and Hashtables Together

$argumentHash = @{
  Count = 1
}
$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentHash @argumentArray
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.