PowerShell che elimina le virgolette doppie dagli argomenti della riga di comando

Recentemente ho avuto qualche problema nell’usare GnuWin32 da PowerShell quando sono coinvolte doppie virgolette.

A seguito di ulteriori indagini, PowerShell sta mettendo a nudo le virgolette doppie dagli argomenti della riga di comando, anche se opportunamente scappato.

PS C:\Documents and Settings\Nick> echo '"hello"' "hello" PS C:\Documents and Settings\Nick> echo.exe '"hello"' hello PS C:\Documents and Settings\Nick> echo.exe '\"hello\"' "hello" 

Si noti che le doppie virgolette ci sono quando vengono passate al cmdlet echo di PowerShell, ma quando vengono passate come argomento a echo.exe , le virgolette doppie vengono rimosse a meno che non siano precedute da una barra rovesciata (anche se il carattere di escape di PowerShell è un apice, non una barra rovesciata).

Questo mi sembra un insetto. Se sto passando le stringhe di escape corrette a PowerShell, PowerShell dovrebbe occuparsi di qualsiasi operazione di escape potrebbe essere necessaria per il richiamo del comando.

Che cosa sta succedendo qui?

Per ora, la correzione è di sfuggire gli argomenti della riga di comando in conformità con queste regole (che sembrano essere utilizzate dalla CreateProcess API CreateProcess che PowerShell utilizza per richiamare i file .exe):

  • Per passare una virgoletta doppia, sfuggire con una barra rovesciata: \" -> "
  • Per passare una o più barre rovesciate seguite da una virgola doppia, sfuggire a ciascuna barra inversa con un’altra barra rovesciata e sfuggire alla citazione: \\\\\" -> \\"
  • Se non seguito da una virgoletta doppia, non è necessario eseguire l’escape per i backslash: \\ -> \\

Si noti che potrebbe essere necessaria un’ulteriore escape delle virgolette per sfuggire alle virgolette nella stringa di escape dell’API di Windows in PowerShell.

Ecco alcuni esempi con echo.exe di GnuWin32:

 PS C:\Documents and Settings\Nick> echo.exe "\`"" " PS C:\Documents and Settings\Nick> echo.exe "\\\\\`"" \\" PS C:\Documents and Settings\Nick> echo.exe "\\" \\ 

Immagino che questo possa rapidamente diventare un inferno se devi passare un complicato parametro da riga di comando. Naturalmente, nulla di ciò è documentato nella documentazione di CreateProcess() o PowerShell.

Si noti inoltre che questo non è necessario per passare argomenti con virgolette alle funzioni .NET o ai cmdlet di PowerShell. Per questo, è sufficiente sfuggire le virgolette doppie a PowerShell.

È una cosa nota :

È FAR troppo difficile passare i parametri alle applicazioni che richiedono stringhe tra virgolette. Ho fatto questa domanda in IRC con un “roomful” di esperti di PowerShell, e ci è voluta un’ora prima che qualcuno capisse un modo (inizialmente ho iniziato a postare che semplicemente non è ansible). Questo interrompe completamente la capacità di PowerShell di servire come shell generica, perché non possiamo fare cose semplici come l’esecuzione di sqlcmd. Il lavoro numero uno di una shell di comando deve essere in esecuzione le applicazioni della riga di comando … Ad esempio, se si prova ad utilizzare SqlCmd da SQL Server 2008, esiste un parametro -v che accetta una serie di parametri name: value. Se il valore contiene spazi, è necessario citarlo …

… non esiste un unico modo per scrivere una riga di comando per richiamare correttamente questa applicazione, quindi anche dopo aver padroneggiato tutti i 4 o 5 modi diversi di quotare e fuggire le cose, stai ancora supponendo quale funzionerà quando … oppure, puoi semplicemente eseguire il bombardamento su cmd e averne fatto.

Personalmente evito di usare “\” per evitare cose in PowerShell, perché tecnicamente non è un carattere di escape. Ho ottenuto risultati imprevedibili con esso. Nelle stringhe tra virgolette doppie, puoi usare "" per ottenere una doppia virgoletta incorporata, o scappare con una contromossa:

 PS C:\Users\Droj> "string ""with`" quotes" string "with" quotes 

Lo stesso vale per le virgolette singole:

 PS C:\Users\Droj> 'string ''with'' quotes' string 'with' quotes 

La cosa strana dell’invio di parametri a programmi esterni è che esiste un ulteriore livello di valutazione delle quote. Non so se si tratta di un bug, ma suppongo che non verrà modificato, perché il comportamento è lo stesso quando si utilizza Start-Process e si passa argomenti. Start-Process prende un array per gli argomenti, il che rende le cose un po ‘più chiare, in termini di quanti argomenti vengono effettivamente inviati, ma tali argomenti sembrano essere valutati per un tempo extra.

Quindi, se ho una matrice, posso impostare i valori di arg per avere citazioni incorporate:

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> echo $aa arg="foo" arg=""""bar"""" 

L’argomento ‘bar’ ha abbastanza per coprire la valutazione extra nascosta. È come se invii quel valore a un cmdlet tra virgolette, quindi mandi di nuovo il risultato tra virgolette:

 PS C:\cygwin\home\Droj> echo "arg=""""bar""""" # level one arg=""bar"" PS C:\cygwin\home\Droj> echo "arg=""bar""" # hidden level arg="bar" 

Ci si aspetterebbe che questi argomenti vengano passati a comandi esterni così come lo sono per i cmdlet come “echo” / “write-output”, ma non lo sono, a causa di quel livello nascosto:

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> start c:\cygwin\bin\echo $aa -nonew -wait arg=foo arg="bar" 

Non conosco il motivo esatto per farlo, ma il comportamento è come se ci fosse un altro passo non documentato che veniva preso sotto le copertine che rianalizza le stringhe. Ad esempio, ottengo lo stesso risultato se invio l’array a un cmdlet, ma aggiungo un livello di analisi eseguendolo tramite invoke-expression :

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> iex "echo $aa" arg=foo arg="bar" 

… che è esattamente quello che ottengo quando invio questi argomenti al mio cygwin esterno ‘echo.exe’:

 PS C:\cygwin\home\Droj> c:\cygwin\bin\echo 'arg="foo"' 'arg=""""bar""""' arg=foo arg="bar" 

Questo sembra essere stato risolto nelle versioni recenti di PowerShell al momento della stesura di questo articolo, quindi non è più qualcosa di cui preoccuparsi.

Se pensi ancora di vedere questo problema, ricorda che potrebbe essere correlato a qualcos’altro, come il programma che invoca PowerShell, quindi se non puoi riprodurlo quando invochi PowerShell direttamente da un prompt dei comandi o dall’ISE, dovresti eseguire il debug altrove.

Ad esempio, ho trovato questa domanda durante l’analisi di un problema relativo alla scomparsa delle virgolette durante l’esecuzione di uno script PowerShell dal codice C # utilizzando Process.Start . Il problema era in realtà C # Process Start ha bisogno di argomenti con virgolette doppie – scompaiono

Affidarsi alla CMD per risolvere il problema come indicato nella risposta accettata non ha funzionato per me poiché le virgolette doppie venivano ancora eliminate quando si chiamava l’eseguibile CMD.

La buona soluzione per me era strutturare la mia linea di comando come una serie di stringhe invece di una singola stringa completa contenente tutti gli argomenti. Quindi passa semplicemente quell’array come argomento per l’invocazione binaria:

 $args = New-Object System.Collections.ArrayList $args.Add("-U") | Out-Null $args.Add($cred.UserName) | Out-Null $args.Add("-P") | Out-Null $args.Add("""$($cred.Password)""") $args.Add("-i") | Out-Null $args.Add("""$SqlScriptPath""") | Out-Null & SQLCMD $args 

In tal caso, le virgolette doppie che circondano gli argomenti vengono passate correttamente al comando invocato.

Se necessario, è ansible testarlo e eseguirne il debug con EchoArgs dalle estensioni della community PowerShell

Puoi risolvere il problema del comportamento di PowerShell triplicando tutte le doppie virgolette:

 PS> .\echo.exe '"""help"""' "help" PS> .\echo.exe '"""help""""""' "help"" 

Questo comportamento è documentato in ProcessStartInfo.Argument .

Funziona per gli eseguibili che non fanno nulla di divertente con gli argomenti stessi. L’esempio specifico di qualcuno dell’argomento sqlcmd -v ha qualche strano comportamento nel citare prima, anche da Prompt dei comandi.