2011-01-15 10 views
10

Próbuję napisać niektóre funkcje PowerShell, które robią pewne rzeczy, a następnie jawnie wywołują do istniejących wbudowanych funkcji. Chcę przekazać wszystkie nietknięte argumenty. Nie chcę znać żadnych szczegółów dotyczących argumentów.Jak przekazać "linię argumentu" jednej funkcji PowerShell do innej?

Zmęczony za pomocą "splat" to zrobić z @args, ale to nie działa tak, jak się spodziewałem.

W poniższym przykładzie napisałem funkcję zabawki o nazwie myls, która ma wydrukować witam!, a następnie wywołaj tę samą wbudowaną funkcję, Get-ChildItem, że wbudowany alias ls dzwoni z pozostałą częścią wiersza argumentu w stanie nienaruszonym. To, co mam do tej pory działa całkiem dobrze:

function myls 
{ 
    Write-Output "hello!" 
# $MyInvocation | Format-List   # <-- uncomment this line for debug info 
    Invoke-Expression ("Get-ChildItem " + $MyInvocation.UnboundArguments -join " ") 
} 

Prawidłowa wersja myls powinien być w stanie obsłużyć miano bez argumentów, z jednym argumentem, z wymienionych argumentów z linii zawierającej wiele średnik rozdzielany poleceń i ze zmiennymi w argumentach, w tym zmiennymi łańcuchowymi zawierającymi spacje. Zasadniczo powinna to być alternatywa w stosunku do ls. poniżej

Testy porównać myls a wbudowane ls:

[UWAGA: Wyjście pomijana i/lub sprasowany w celu zaoszczędzenia miejsca]

PS> md C:\p\d\x, C:\p\d\y, C:\p\d\"jay z" 
PS> cd C:\p\d 
PS> ls         # no args 
PS> myls        # pass 
PS> cd .. 
PS> ls d        # one arg 
PS> myls d        # pass 
PS> $a="A"; $z="Z"; $y="y"; $jz="jay z" 
PS> $a; ls d; $z      # multiple statements 
PS> $a; myls d; $z      # pass 
PS> $a; ls d -Exclude x; $z   # named args 
PS> $a; myls d -Exclude x; $z   # pass 
PS> $a; ls d -Exclude $y; $z   # variables in arg-line 
PS> $a; myls d -Exclude $y; $z   # pass 
PS> $a; ls d -Exclude $jz; $z   # variables containing spaces in arg-line 
PS> $a; myls d -Exclude $jz; $z  # FAIL! 

Czy istnieje sposób mogę ponownie napisać myls uzyskać pożądane zachowanie?

Krótka odpowiedź: Tak, jest to możliwe. Złe wieści: wymaga kodu, który zna szczegóły parametrów i innych metadanych dotyczących funkcji, do której chce się zadzwonić. Dobra wiadomość: nie trzeba pisać tego wszystkiego samodzielnie. Te metadane są dostępne programowo i istnieją dostępne moduły, za pomocą których można automatycznie generować szkieletowy kod proxy (patrz odpowiedź @ Jaykul poniżej). Wybieram użycie the module named "MetaProgramming". Po zaimportowaniu generowania drop-in myls skryptu jest martwy prosta:

New-ProxyCommand ls > .\myls.ps1

Następnie można rozpocząć dostosowywanie nowo wygenerowanego myls.ps1 skrypt tak:

... 
    begin 
    { 
    Write-Output "hello!"    # <-- add this line 
    try { 
     $outBuffer = $null 
    ... 

Voila! Ta nowa wersja przechodzi wszystkie testy.

Odpowiedz

4

Jeśli chcesz opakowanie typu "drop-in" dla ls, powinieneś napisać: proper proxy function. Istnieje kilka wersji generatora na PoshCode.org, w tym the one from Lee Holmes' PowerShell Cookbook

+0

masz rację, to jest to, czego naprawdę potrzebuję. Dzięki! – jwfearn

+0

Innymi słowy: zautomatyzowane wklejanie kopii. Po prostu zdumiewające. – alecov

+2

Jeśli możesz skopiować i wkleić, nie potrzebujesz generowania kodu. To, co robi OP, próbuje podklasować polecenie: napisanie nowego polecenia na podstawie oryginału, ale z niestandardowymi modyfikacjami ... i skryptem. Jeśli robisz to w języku C#, po prostu odziedziczysz.Ale jesteś w PowerShell, więc musisz napisać funkcję, która zaczyna się od powielenia zestawu parametrów z oryginalnego cmdletu (w PowerShellu nazywamy te funkcje proxy). Jeśli kiedykolwiek istniało coś, co płakało z powodu generowania kodu, to jest to. – Jaykul

2

wierzę w ten sposób:

myls funkcyjne {Write-Output; "Hello!" iex "Get-ChildItem @args"}

będzie bliżej do uzyskania oczekiwanego wyniku.

Aktualizacja: Jest podobno znany błąd z użyciem @args w ten sposób przekazać nazwanych parametrów:

https://connect.microsoft.com/PowerShell/feedback/details/368512/splat-operator-args-doesnt-properly-pass-named-arguments?wa=wsignin1.0

bym zaprzestanie korzystania, że ​​dopóki nie zostanie rozwiązany.

+0

Jest bliżej - nie powoduje błędu - niestety również ignoruje argumenty. Jeśli spróbujesz powyższego testu, wyjścia 'myls' i' myls -Exclude b' są niepoprawnie takie same. – jwfearn

+0

Wypróbuj bez tego pierwszego wiersza. – mjolinor

4

Wykonanie właściwego opakowania nie jest takie proste, niestety. Przede wszystkim operator splat powinien być prawdopodobnie zastosowany bezpośrednio do tablicy hashtable (na przykład automatycznej PSBoundParameters lub innej), a nie bezpośrednio do tablicy $args.

Dostępne są co najmniej dwie opcje (obie nie są idealne) i hack (patrz sekcja aktualizacji).

Opcja 1. Użyj "klasycznego" sposobu tworzenia opakowania. Przykład: zobacz, jak to zrobić dla funkcji help który jest opakowaniem z Get-Help cmdletu:

Get-Content function:\help 

Można zobaczyć, że funkcja otoki declarers wszystkie parametry że owinięty cmdlet ma w celu uzyskania że PSBoundParametersograniczone do istniejących parametrów funkcji.

Twój przykład. Jeśli twoja funkcja deklaruje parametr Exclude, wtedy przykładowy kod zaczyna działać. Ale to działa tylko dla Exclude nie Force, choć Force jest również przekazywana w:

function myls($Exclude) { 
    # only Exclude is in $PSBoundParameters even though we send Force, too: 
    $PSBoundParameters | Out-String | Out-Host 
    Write-Output "hello!" 
    Get-ChildItem @PSBoundParameters 
} 
cd d 
myls -Exclude b -Force 

Opcji 2. W teorii powinno być możliwe w celu budowania hashtable z tablicy $args ręcznie i zastosować operator ikona do niego. Ale to zadanie nie wygląda praktycznie atrakcyjnie.


UPDATE

Cóż, nie jest w rzeczywistości kolejna opcja ad-hoc (czysty siekać!), Który wymaga minimalnego wysiłku i będzie działać w niektórych błahych, głównie interaktywnych scenariuszy. To nie jest jakoś poważny kod!

+0

Hack działa i ma tę zaletę, że nie trzeba obsługiwać (lub nawet znać) wszystkich różnych opcjonalnych parametrów. – jwfearn

+1

Cóż, działa, ale nie na tyle dobrze, aby mógł być użyty. '$ MyInvocation.Line' to cała linia wywoławcza zawierająca również inne instrukcje. W rezultacie to nie działa: '$ result = myls ...'. Ale wydaje się, że to działa: 'myls ... -Out Variable result'. –

+0

Tak, też to zauważyłem. Musi być poprawka. Dam ci znać, jeśli ją znajdę. – jwfearn