2011-01-15 7 views
10

sto cercando di scrivere alcune funzioni PowerShell che fare alcune cose e poi in modo trasparente chiamare fino alle funzioni incorporate esistenti. Voglio passare lungo tutti gli argomenti intatti. Non voglio conoscere alcun dettaglio degli argomenti.Come passare la 'argomento-riga' di una funzione di PowerShell a un'altra?

mi stanco usando 'splat' di fare questo con @args ma che non ha funzionato come mi aspettavo.

Nell'esempio seguente, ho scritto una funzione giocattolo denominata myls che dovrebbe stampare ciao! e quindi chiamare la stessa funzione incorporata, Get-ChildItem, che il ls chiamate incorporato alias con il resto della linea argomentazione intatta. Quello che ho finora funziona piuttosto bene:

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

una versione corretta del myls dovrebbe essere in grado di gestire essere chiamato senza argomenti, con un argomento, con argomenti con nome, da una linea che contiene più comandi e virgola delimitati e con variabili negli argomenti incluse variabili stringa contenenti spazi. Fondamentalmente, dovrebbe essere un'alternativa drop-in a ls.

I test di seguito confrontano myls e integrato ls:

[NOTA: l'uscita eliso e/o compattata per risparmiare spazio]

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! 

C'è un modo per riscrivere myls ottenere il comportamento che voglio?

Risposta breve: Sì, è possibile. La cattiva notizia: richiede un codice che conosca i dettagli dei parametri e altri metadati relativi alla funzione che si desidera chiamare. La buona notizia: uno non ha bisogno di scrivere tutto questo se stessi. Questi metadati è disponibile programatically ed esistono i moduli disponibili che si possono utilizzare per generare automaticamente codice proxy scheletro (vedi @ risposta di Jaykul sotto). Ho scelto di usare the module named "MetaProgramming". Una volta importati, generando un drop-in myls script è morto semplice:

New-ProxyCommand ls > .\myls.ps1

Poi si può iniziare a personalizzare il myls.ps1 script appena generati, in questo modo:

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

Voila! Questa nuova versione supera tutti i test.

risposta

4

Se si desidera un wrapper drop-in per ls, si dovrebbe scrivere un proper proxy function. Ci sono un paio di versioni del generatore su PoshCode.org, tra cui the one from Lee Holmes' PowerShell Cookbook

+0

hai ragione questo è ciò di cui ho veramente bisogno. Grazie! – jwfearn

+0

In altre parole: copia-incolla automatizzata. Semplicemente sbalorditivo. – alecov

+2

Se potessi copiare e incollare, non avresti bisogno della generazione del codice. Ciò che l'OP sta facendo è cercare di sottoclasse il comando: scrivere un nuovo comando basato sull'originale, ma con modifiche personalizzate ... e in script. Se lo stavi facendo in C#, ereditavi.Ma sei in PowerShell, quindi devi scrivere una funzione che inizia con la duplicazione del set di parametri dal cmdlet originale (in PowerShell, chiamiamo quelle funzioni proxy). Se c'è mai stato qualcosa che ha pianto per la generazione del codice, è questo. – Jaykul

2

Credo che questo:

miols funzione {Write-Output, "ciao!" IEX "Get-ChildItem @args"}

si avvicinerà a produrre il risultato atteso.

Aggiornamento: A quanto pare c'è un bug noto con l'utilizzo di @args in questo modo per passare parametri denominati:

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

mi piacerebbe interrompere utilizzando tale finché non è risolto.

+0

È più vicino - non produce un errore - sfortunatamente ignora anche gli argomenti. Se provi il test di cui sopra, gli output di 'myls' e' myls -Exclude b' sono, erroneamente, gli stessi. – jwfearn

+0

Provalo senza quella prima riga. – mjolinor

4

Fare un wrapper correttamente non è così facile, sfortunatamente. Innanzitutto, l'operatore di splat dovrebbe presumibilmente essere applicato a un hashtable (ad esempio automatico PSBoundParameters o altro), non direttamente all'array $args.

ci sono almeno due opzioni (entrambi non sono perfetti), e un hack (vedere la sezione di aggiornamento).

Opzione 1. Utilizzare il modo "classico" di creare un wrapper. Esempio: vedere come questo viene fatto per la funzione help che è un wrapper del Get-Help cmdlet:

Get-Content function:\help 

Si può vedere che la funzione wrapper dichiaranti tutti i parametri che il cmdlet avvolto ha in modo da avere che PSBoundParameterslimitato ai parametri di funzione esistenti.

Il tuo esempio. Se la funzione dichiara il parametro Exclude, il codice di esempio inizia a funzionare. Ma funziona per Exclude soltanto, non Force, anche se Force è anche passato in:

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 

Opzione 2. In teoria dovrebbe essere possibile costruire una tabella hash dalla matrice $args manualmente e applicare l'operatore splat ad esso. Ma questo compito non sembra praticamente attraente.


UPDATE

Bene, c'è in realtà ancora un'altra opzione (mod puro!) Ad-hoc che richiede il minimo sforzo e lavorerà in alcuni scenari banali, per lo più interattivo,. Non è per un codice in qualche modo serio!

function myls { 
    # extra job 
    Write-Output "hello!" 

    # invoke/repeat the calling code line with myls "replaced" with Get-ChildItem 
    Set-Alias myls Get-ChildItem 
    Invoke-Expression $MyInvocation.Line 
} 

cd d 

# variables can be used as the parameter values, too 
$exclude = 'b' 

myls -Exclude $exclude -Force 
+0

L'hack funziona e ha il vantaggio che non è necessario gestire (o nemmeno sapere) tutti i vari parametri opzionali. – jwfearn

+1

Bene, funziona ma non abbastanza bene per l'uso reale. '$ MyInvocation.Line' è l'intera linea chiamante che include anche altre istruzioni. Di conseguenza, questo non funziona: '$ result = myls ...'. Ma questo sembra funzionare: "myls ... -Variabilità variabile". –

+0

Sì, l'ho notato anche io. Ci deve essere una soluzione. Ti farò sapere se trovo uno. – jwfearn