Jak przekazać wiele parametrów do funkcji w PowerShell?

438

Jeśli mam funkcję, która akceptuje więcej niż jeden parametr łańcuchowy, pierwszy parametr wydaje się otrzymać wszystkie dane przypisane do niego, a pozostałe parametry są przekazywane jako puste.

Szybki skrypt testowy:

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

Test("ABC", "DEF")

Wygenerowane dane wyjściowe to

$arg1 value: ABC DEF
$arg2 value: 

Prawidłowe wyjście powinno być:

$arg1 value: ABC
$arg2 value: DEF

Wydaje się, że jest to spójne między wersją 1 i 2 na wielu komputerach, więc oczywiście robię coś złego. Czy ktoś może dokładnie wskazać, co?

Nasir
źródło
3
Po prostu dzwonisz w ten sposób:Test "ABC" "DEF"
Ranadip Dutta

Odpowiedzi:

576

Parametry wywołań funkcji w programie PowerShell (wszystkie wersje) są rozdzielane spacjami, a nie przecinkami . Ponadto nawiasy są całkowicie niepotrzebne i spowodują błąd analizy w programie PowerShell 2.0 (lub nowszym), jeśli Set-StrictModejest aktywny. Argumenty w nawiasach są używane tylko w metodach .NET.

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

ps> foo 1 2 3
a: 1; b: 2; c: 3
x0n
źródło
20
Najważniejszą rzeczą, która w końcu pomogła „utkwić” w moim umyśle, jest ostatnie zdanie: „Argumenty w nawiasach są używane tylko w metodach .NET”.
Ashley,
1
Wolę używać nawiasów i oddzielonych przecinkami .. czy można to zrobić w PowerShell?
sam yi
8
@ samyi Nie. Przekazywanie (1,2,3)do funkcji jest skutecznie traktowane jako tablica; pojedynczy argument. Jeśli chcesz użyć argumentów w stylu metody OO, użyj modułów:$m = new-module -ascustomobject { function Add($x,$y) { $x + $y } }; $m.Add(1,1)
x0n
4
Powershelljest językiem powłoki i języki powłoki często używają spacji jako separatora tokenów. Nie powiedziałbym, że Powershellto jest inny tutaj, to prawda zgodne z innymi domyślny system powłok takich jak cmd, sh, bashitp
Bender Greatest
270

Podano już prawidłową odpowiedź, ale ten problem wydaje się na tyle rozpowszechniony, że uzasadnia dodatkowe szczegóły dla osób chcących zrozumieć subtelności.

Dodałbym to tylko jako komentarz, ale chciałem załączyć ilustrację - oderwałem to od mojej skróconej tabeli funkcji programu PowerShell. Zakłada się, że sygnatura funkcji f to f($a, $b, $c):

Pułapki składniowe wywołania funkcji

W ten sposób można wywołać funkcję z parametrami pozycyjnymi oddzielonymi spacjami lub nazwanymi parametrami niezależnymi od kolejności . Inne pułapki ujawniają, że musisz być świadomy przecinków, nawiasów i białych znaków.

Więcej informacji można znaleźć w moim artykule W dół króliczej nory: studium potoków, funkcji i parametrów programu PowerShell . Artykuł zawiera również link do szybkiego odniesienia / tabeli ściennej.

Michael Sorens
źródło
4
Wyjaśnienie z bardziej szczegółową składnią wywołującą każdy parametr i przypisującą mu wartość naprawdę zacementowało go. Dzięki!
ConstantineK
7
Dziękuję, doprowadzało mnie to do szału, ponieważ nie rozumiałem, co robię źle. Kiedy w końcu zrobiłem to dobrze, byłem głodny wyjaśnienia tego zachowania.
BSAFH
1
Dziękujemy za opublikowanie tego jako odpowiedzi. Wiedza, dlaczego coś złego jest tak samo ważne jak to, co złego.
Gavin Ward
4
To zdecydowanie lepsza odpowiedź. Powinno być jeszcze wyżej.
Mark Bertenshaw
53

Jest tu kilka dobrych odpowiedzi, ale chciałem zwrócić uwagę na kilka innych rzeczy. Parametry funkcji są w rzeczywistości miejscem, w którym świeci PowerShell. Na przykład możesz mieć parametry nazwane lub pozycyjne w funkcjach zaawansowanych, takich jak:

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

Następnie możesz albo wywołać go, podając nazwę parametru, albo możesz po prostu użyć parametrów pozycyjnych, ponieważ je wyraźnie zdefiniowałeś. Więc którykolwiek z nich działałby:

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

Pierwszy przykład działa, mimo że Namepodany jest drugi, ponieważ jawnie użyliśmy nazwy parametru. Drugi przykład działa jednak w oparciu o pozycję, więc Namemusiałby być pierwszy. Jeśli to możliwe, zawsze staram się zdefiniować pozycje, aby obie opcje były dostępne.

PowerShell ma również możliwość definiowania zestawów parametrów. Wykorzystuje to zamiast przeciążania metod i znów jest całkiem przydatne:

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
    )
}

Teraz funkcja przyjmie nazwę lub identyfikator, ale nie jedno i drugie. Możesz użyć ich pozycjonować lub według nazwy. Ponieważ są innego typu, PowerShell to rozwiąże. Wszystko to działałoby:

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

Możesz także przypisać dodatkowe parametry do różnych zestawów parametrów. (To był oczywiście dość prosty przykład.) Wewnątrz funkcji można określić, który zestaw parametrów został użyty za pomocą właściwości $ PsCmdlet.ParameterSetName. Na przykład:

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

Następnie, w powiązanej notatce dodatkowej, istnieje również sprawdzanie poprawności parametrów w PowerShell. Jest to jedna z moich ulubionych funkcji PowerShell i sprawia, że ​​kod w twoich funkcjach jest bardzo czysty. Istnieje wiele walidacji, których możesz użyć. Oto kilka przykładów:

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

W pierwszym przykładzie ValidatePattern akceptuje wyrażenie regularne, które zapewnia, że ​​podany parametr odpowiada oczekiwaniom. Jeśli nie, zgłaszany jest intuicyjny wyjątek, który mówi dokładnie, co jest nie tak. W tym przykładzie „Coś” działałoby dobrze, ale „Lato” nie przejdzie weryfikacji.

ValidateRange zapewnia, że ​​wartość parametru mieści się w zakresie oczekiwanym dla liczby całkowitej. Więc 10 lub 99 działałoby, ale 101 rzuciłoby wyjątek.

Kolejnym przydatnym jest ValidateSet, który pozwala jawnie zdefiniować tablicę dopuszczalnych wartości. W przypadku wprowadzenia czegoś innego zostanie zgłoszony wyjątek. Są też inne, ale prawdopodobnie najbardziej przydatnym jest ValidateScript. To wymaga bloku skryptu, który musi mieć wartość $ true, więc niebo jest granicą. Na przykład:

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

W tym przykładzie mamy pewność, że nie tylko istnieje $ Path, ale jest to plik (w przeciwieństwie do katalogu) i ma rozszerzenie .csv. ($ _ odnosi się do parametru, gdy znajduje się w twoim bloku skryptu.) Możesz również przekazać znacznie większe, wieloliniowe bloki skryptu, jeśli ten poziom jest wymagany, lub użyć wielu bloków skryptu, jak ja tutaj. Jest niezwykle przydatny i zapewnia ładne czyste funkcje i intuicyjne wyjątki.

użytkownik2233949
źródło
3
+1 za demonstrację My_Function -NamedParamater "ParamValue"stylu wywołania funkcji. Jest to wzorzec, który powinien podążać więcej kodu skryptu PS w celu zapewnienia czytelności.
Mister_Tom,
46

Funkcje programu PowerShell wywoływane są bez nawiasów i bez przecinka jako separatora. Spróbuj użyć:

test "ABC" "DEF"

W PowerShell przecinek (,) jest operatorem tablicowym, np

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

Ustawia $ana tablicę z trzema wartościami.

Todd
źródło
16
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test "ABC" "DEF"
John B.
źródło
11

Jeśli jesteś programistą C # / Java / C ++ / Ruby / Python / Pick-A-Language-From-This-Century i chcesz wywołać swoją funkcję przecinkami, ponieważ zawsze tak robiłeś, potrzebujesz czegoś lubię to:

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

Teraz zadzwoń:

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

i zobaczysz

arg1 = ABC, and arg2 = DEF
Ryan Shillington
źródło
Java, C ++, Ruby i Python nie pochodzą z tego wieku (tylko C #), zakładając, że kalendarz gregoriański (choć niektóre ewoluowały bardziej niż inne).
Peter Mortensen
Heh @PeterMortensen twoim argumentem jest to, że powinienem powiedzieć „Wybierz język z tego wieku lub ostatniego”? :-)
Ryan Shillington
10

Jeśli nie wiesz (lub nie obchodzi Cię), ile argumentów przekażesz funkcji, możesz również zastosować bardzo proste podejście;

Kod :

function FunctionName()
{
    Write-Host $args
}

To wypisze wszystkie argumenty. Na przykład:

FunctionName a b c 1 2 3

Wynik

a b c 1 2 3

Uważam to za szczególnie przydatne podczas tworzenia funkcji, które używają poleceń zewnętrznych, które mogą mieć wiele różnych (i opcjonalnych) parametrów, ale polegają na tym poleceniu, aby przekazywać opinie na temat błędów składniowych itp.

Oto kolejny przykład ze świata rzeczywistego (tworzenie funkcji do polecenia tracert, której nienawidzę, pamiętając skróconą nazwę);

Kod :

Function traceroute
{
    Start-Process -FilePath "$env:systemroot\system32\tracert.exe" -ArgumentList $args -NoNewWindow
}
Draino
źródło
7

Jeśli spróbujesz:

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

dostajesz:

$arg1 value: ABC GHI
$arg2 value: DEF

Widzisz więc, że nawiasy oddzielają parametry


Jeśli spróbujesz:

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

dostajesz:

$arg1 value: ABC
$arg2 value: DEF

Teraz możesz znaleźć natychmiastową użyteczność nawiasów - spacja nie stanie się separatorem dla następnego parametru - zamiast tego masz funkcję eval.

RaSor
źródło
4
Pareny nie rozdzielają parametrów. Definiują tablicę.
n0rd
1
Pareny nie definiują tablicy, lecz grupę, którą PowerShell może interpretować jako tablicę. Tablice są definiowane na znak ( @) przed czołowymi paren jak pustego tablicy: @(); Tablica lub dwoma numerami: @(1, 2).
VertigoRay
5

Ponieważ jest to często zadawane pytanie, chcę wspomnieć, że należy użyć funkcji PowerShell zatwierdzonych czasowników ( rzeczownik-czasownik jako nazwa funkcji). Czasownikowa część nazwy identyfikuje akcję wykonywaną przez cmdlet. Rzeczownikowa część nazwy identyfikuje byt, na którym wykonywana jest akcja. Ta reguła upraszcza korzystanie z poleceń cmdlet dla zaawansowanych użytkowników PowerShell.

Możesz także określić takie rzeczy, jak parametr obowiązkowy i pozycję parametru:

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"
}

Aby przekazać parametr do funkcji, możesz użyć pozycji :

Test-Script "Hello" "World"

Lub określasz parametr nazwę :

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

Nie używasz nawiasów jak w przypadku wywoływania funkcji w języku C #.


Zalecam, aby zawsze podawać nazwy parametrów, gdy jest używany więcej niż jeden parametr, ponieważ jest to bardziej czytelne .

Martin Brandl
źródło
Do Twojej wiadomości, zatwierdzony link do listy czasowników już nie działa, ale można go teraz znaleźć tutaj - docs.microsoft.com/en-us/powershell/developer/cmdlet/…
Keith Langmead
@KeithLangmead Dziękuję Keith, zaktualizowałem również swoją odpowiedź.
Martin Brandl,
1
„Czasownik-rzeczownik” jak w czasowniku i rzeczowniku pisane wielką literą? Być może zmień odpowiedź, aby była bardziej jednoznaczna na ten temat?
Peter Mortensen
@PeterMortensen Dzięki za wzmiankę o tym. Zaktualizowałem swoją odpowiedź.
Martin Brandl
1
Cóż, rozważ odsłonięcie polecenia Get-Nodecmdlet. Będzie dla nas jasne, że musimy się odwoływać Get-Node, nie Retrieve-Node, ani Receive-Node, ani .....
Martin Brandl
4

Nie wiem, co robisz z funkcją, ale spójrz na użycie słowa kluczowego „param”. Jest o wiele bardziej wydajny do przekazywania parametrów do funkcji i czyni go bardziej przyjaznym dla użytkownika. Poniżej znajduje się link do zbyt złożonego artykułu Microsoft na ten temat. To nie jest tak skomplikowane, jak brzmi ten artykuł.

Użycie Param

Oto przykład z pytania na tej stronie:

Sprawdź to.

Rodney Fisk
źródło
Dziękuję za odpowiedź. Miałem jednak problemy podczas wywoływania funkcji. Nie miało znaczenia, czy funkcja została zadeklarowana z param czy bez.
Nasir,
3
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC") ("DEF")
kleopatra
źródło
Wynik wyjdzie tak, jak powinien:
2

Powiedziałem wcześniej:

Częstym problemem jest używanie formy pojedynczej $arg, co jest niepoprawne. Zawsze powinno być w liczbie mnogiej jak$args .

Problem nie jest taki. W rzeczywistości $argmoże być cokolwiek innego. Problem polegał na użyciu przecinka i nawiasów.

Uruchamiam następujący kod, który działał, a dane wyjściowe są następujące:

Kod:

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

Test „ABC” „DEF”

Wynik:

$var1 value: ABC
$var2 value: DEF
Eric
źródło
4
Dziękuję, przyjacielu, jednak spóźniłeś się kilka lat :-) Trzy najlepsze odpowiedzi tutaj wystarczająco rozwiązały ten problem. Czy mogę zasugerować przejście do sekcji Bez odpowiedzi i wypróbowanie niektórych z tych pytań?
Nasir,
2
Function Test {
    Param([string]$arg1, [string]$arg2)

    Write-Host $arg1
    Write-Host $arg2
}

To jest właściwa paramsdeklaracja.

Zobacz about_Functions_Advanced_Parameters .

I to naprawdę działa.

Serhii Kimlyk
źródło
1

Możesz przekazać parametry w funkcji takiej jak ta:

function FunctionName()
{
    Param ([string]$ParamName);
    # Operations
}
Kaushal Khamar
źródło
3
To byłoby określenie parametrów dla funkcji, pierwotne pytanie dotyczyło tego, jak określić parametry podczas wywoływania funkcji.
Nasir,
1

Nie widzę tu o tym wspominanych, ale splatanie argumentów jest przydatną alternatywą i staje się szczególnie przydatne, jeśli budujesz argumenty do polecenia dynamicznie (w przeciwieństwie do używania Invoke-Expression). Możesz splatać się z tablicami dla argumentów pozycyjnych i tablicami skrótów dla nazwanych argumentów. Oto kilka przykładów:

Ikona z tablicami (argumenty pozycyjne)

Połączenie testowe z argumentami pozycyjnymi

Test-Connection www.google.com localhost

Z rozpryskiwaniem szyku

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

Zauważ, że podczas rozpryskiwania odwołujemy się do zmiennej splatted @zamiast zamiast $. Podobnie jest w przypadku używania Hashtable do rozpryskiwania.

Ikona z Hashtable (nazwane argumenty)

Połączenie testowe z nazwanymi argumentami

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

Z Hashtable Splatting

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

Argumenty pozycyjne i nazwane jednocześnie

Połączenie testowe z argumentami pozycyjnymi i nazwanymi

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

Splatting Array i Hashtables Razem

$argumentHash = @{
  Count = 1
}
$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentHash @argumentArray
Bender the Greatest
źródło