Powershell: Los argumentos para ejecutables externos no se escapan correctamente

Creado en 21 ago. 2016  ·  170Comentarios  ·  Fuente: PowerShell/PowerShell

pasos para reproducir

  1. escribe un programa C native.exe que adquiere ARGV
  2. Ejecutar native.exe "`"a`""

    Comportamiento esperado

ARGV [1] == "a"

Comportamiento real

ARGV [1] == a

Datos ambientales

Windows 10 x64

Name                           Value
----                           -----
PSVersion                      5.1.14393.0
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.14393.0
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
Committee-Reviewed WG-Engine

Comentario más útil

Hacer un operador especial para esto no tiene ningún sentido para un shell de línea de comandos, ya que su trabajo principal es lanzar programas y pasarles argumentos. Presentar un nuevo operador que hace system() para este trabajo es como si Matlab presentara una forma de llamar calc.exe porque tiene un error en su aritmética. En cambio, lo que debería hacerse es que:

  • El equipo de pwsh se prepara para una nueva versión principal que corrige las cosas de la línea de comandos, moviendo el comportamiento actual detrás de un cmdlet integrado.
  • Como solución provisional, la próxima versión de pwsh obtiene un cmdlet integrado que usa el nuevo comportamiento correcto para pasar la línea de comandos.

Lo mismo se aplica a Start-Process . (En realidad, es un candidato bastante bueno para el cmdlet "nuevo" con algunas opciones como -QuotingBehavior Legacy ...) Consulte # 13089.

Todos 170 comentarios

¿Por qué cree que "\"a\"" el comportamiento esperado? Mi comprensión de los escapes de PowerShell dice que el comportamiento real es el comportamiento correcto y esperado. " "a "" es un par de comillas que rodean un par de comillas de escape que rodean a un a , por lo que PowerShell interpreta el par externo sin escape como" este es un argumento de cadena "y entonces los deja caer, luego interpreta el par de escape como comillas de escape y así los mantiene, dejándolo con "a" . En ningún momento se agregó un \ a la cadena.

El hecho de que Bash use \ como carácter de escape es irrelevante. En PowerShell, el carácter de escape es una comilla invertida. Consulte Caracteres de escape de PowerShell.

Si desea pasar literalmente "\"a\"" , creo que usaría:

> echo `"\`"a\`"`"
"\"a\""

@andschwa
Sí, los escapes funcionan bien para los cmdlets internos, pero las cosas se ponen raras cuando se comunican con binarios nativos , especialmente en Windows.
Cuando se ejecuta native.exe " "a "" , el ARGV [1] debe ser

"a"

(tres caracteres)

en vez de

a

(un carácter).

Actualmente, para hacer que native.exe reciba correctamente un ARGV con dos comillas y un carácter a , debe usar esta extraña llamada:

native.exe "\`"a\`""

Ah, ya veo. Reapertura.

Por una gran curiosidad, ¿qué sucede si prueba una compilación con el número 1639?

@andschwa Lo mismo. TIENE que duplicar esacpe para satisfacer tanto PowerShell como CommandLineToArgvW . Esta línea:

native.exe "`"a`""

da como resultado un StartProcess igual a cmd

native.exe ""a""

@ be5invis @douglaswth ¿esto se resuelve a través de https://github.com/PowerShell/PowerShell/pull/2182?

No, todavía necesitamos agregar una barra invertida antes de una comilla doble con comillas invertidas. Esto no resuelve el problema del doble escape. (Es decir, tenemos que escapar de una comilla doble para PowerShell y CommandLineToArgvW).

Dado que " "a "" es igual a '"a"' , ¿sugiere que native.exe '"a"' debería resultar en "\"a\"" ?

Esto parece una solicitud de función que, si se implementa, podría romper una gran cantidad de scripts de PowerShell ya existentes que usan el doble escape requerido, por lo que se requeriría un cuidado extremo con cualquier solución.

@vors Sí.
@douglaswth El doble escape es realmente tonto: ¿por qué necesitamos los escapes “internos” que se hicieron en la era de DOS?

@vors @douglaswth
Este es el código C que se usa para mostrar los resultados de GetCommandLineW y CommandLineToArgvW:

#include <stdio.h>
#include <wchar.h>
#include <Windows.h>

int main() {
  LPWSTR cmdline = GetCommandLineW();
  wprintf(L"Command Line : %s\n", cmdline);

  int nArgs;
  LPWSTR *szArglist = CommandLineToArgvW(cmdline, &nArgs);
  if (NULL == szArglist) {
    wprintf(L"CommandLineToArgvW failed\n");
    return 0;
  } else {
    for (int i = 0; i < nArgs; i++) {
      wprintf(L"argv[%d]: %s\n", i, szArglist[i]);
    }
  }
  LocalFree(szArglist);
}

Aqui esta el resultado

$ ./a "a b"
Command Line : "Z:\playground\ps-cmdline\a.exe" "a b"
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a b

$ ./a 'a b'
Command Line : "Z:\playground\ps-cmdline\a.exe" "a b"
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a b

$ ./a 'a"b'
Command Line : "Z:\playground\ps-cmdline\a.exe" a"b
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: ab

$ ./a 'a"b"c'
Command Line : "Z:\playground\ps-cmdline\a.exe" a"b"c
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: abc

$ ./a 'a\"b\"c'
Command Line : "Z:\playground\ps-cmdline\a.exe" a\"b\"c
argv[0]: Z:\playground\ps-cmdline\a.exe
argv[1]: a"b"c

@ be5invis No estoy en desacuerdo con usted acerca de que el doble escape es molesto, pero simplemente estoy diciendo que un cambio en esto debería ser compatible con versiones anteriores de lo que usan los scripts de PowerShell existentes.

Cuantos son ellos? No creo que haya escritores de guiones que conozcan este tipo de citas dobles. Es un error, no una característica y no está documentado.

???? iPhone

? 2016? 9? 21 ?? 01: 58? Douglas Thrift < [email protected] [email protected] > ???

@ be5i nvishttps: //github.com/be5invis No estoy en desacuerdo con usted acerca de que el doble escape es molesto, pero simplemente estoy diciendo que un cambio en esto tendría que ser retrocompatible con lo que usan los scripts de PowerShell existentes.

Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en Gi readhttps: //github.com/notifications/unsubscribe-auth/AAOp20f_W0mTl2YiJKi_aaaaaa.

PowerShell existe desde hace 9 años, por lo que es muy probable que exista una buena cantidad de scripts. Encontré mucha información sobre la necesidad de un doble escape de StackOverflow y otras fuentes cuando encontré la necesidad, así que no sé si estoy de acuerdo con sus afirmaciones de que nadie sabe sobre la necesidad de hacerlo o que no está documentado .

Para el contexto adicional, me gustaría hablar un poco sobre la implementación.
PowerShell llama a la API .NET para generar un nuevo proceso, que llama a una API Win32 (en Windows).

Aquí, PS crea StartProcessInfo que se usa
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/NativeCommandProcessor.cs#L1063

La API proporcionada toma una sola cadena para los argumentos y luego se vuelve a analizar en una matriz de argumentos para realizar la ejecución.
Las reglas de este nuevo análisis no están controladas por PowerShell. Es una API de Win32 (y, afortunadamente, es coherente con las reglas de dotnet core y unix).
En particular, este contrato describe el comportamiento de \ y " .

Aunque PowerShell puede intentar ser más inteligente y brindar una experiencia más agradable, el comportamiento actual es consistente con cmd y bash: puede copiar la línea ejecutable nativa de ellos y usarla en PowerShell y funciona igual.

@ be5invis Si conoce una manera de mejorar la https://github.com/PowerShell/PowerShell/blob/master/docs/dev-process/breaking-change-contract.md

Esto se aplica a Windows, pero cuando se ejecutan comandos en Linux o Unix, es extraño que sea necesario utilizar comillas de escape dobles.

En Linux, los procesos no tienen una sola línea de comandos, sino una matriz de argumentos.
Por lo tanto, los argumentos en PowerShell deben ser los mismos que los que se pasan al ejecutable, en lugar de fusionar todos los argumentos y luego volver a dividirlos.

Incluso en Windows, el comportamiento actual es inconsistente:
Si un argumento no contiene espacios, se pasa sin cambios.
Si un argumento contiene espacios, si estará entre comillas, para mantenerlo junto a través de CommandLineToArgvW call. => El argumento se cambia para cumplir con el requisito de CommandLineToArgvW .
Pero si el argumento contiene comillas, no se escapan. => El argumento no se cambia, aunque CommandLineToArgvW requiere.

Creo que los argumentos nunca deben cambiarse o siempre deben cambiarse para cumplir con los requisitos de CommandLineToArgvW , pero no en la mitad de los casos.

Respecto a la ruptura del contrato:
Como no pude encontrar ninguna documentación oficial sobre el doble escape, lo consideraría como categoría "Bucket 2: Área gris razonable", por lo que hay posibilidades de cambiar esto, ¿o me equivoco?

@vors Esto es extremadamente molesto si su argumento es una variable u otra cosa: debe escapar manualmente antes de enviarlo a una aplicación nativa.
Un operador de "escape automático" puede ayudar. como ^"a " " -> "a\ " "`

Creo que @TSlivede lo

Creo que los argumentos nunca deben cambiarse o siempre deben cambiarse para cumplir con los requisitos de CommandLineToArgvW, pero no en la mitad de los casos.

No estoy seguro acerca de la cubeta, pero incluso la cubeta de "cambio claramente roto" podría potencialmente cambiarse. Queremos mejorar PowerShell, pero la compatibilidad con versiones anteriores es una de nuestras principales prioridades. Por eso no es tan fácil.
Tenemos una gran comunidad y estoy seguro de que podemos encontrar consenso.

¿Alguien querría iniciar un proceso de RFC?

Valdría la pena investigar el uso de P / Invoke en lugar de .Net para iniciar un proceso si eso evita la necesidad de que PowerShell agregue comillas a los argumentos.

@lzybkr por lo que puedo decir, PInvoke no ayudaría.
Y aquí es donde las API de Unix y Windows son diferentes:

https://msdn.microsoft.com/en-us/library/20y988d2.aspx (trata los espacios como separadores)
https://linux.die.net/man/3/execvp (no trata los espacios como separadores)

No estaba sugiriendo cambiar la implementación de Windows.

Intentaría evitar tener un comportamiento específico de la plataforma aquí. Dañará la portabilidad de los scripts.
Creo que podemos considerar cambiar el comportamiento de Windows de una manera ininterrumpida. Es decir, con variable de preferencia. Y luego podemos tener diferentes valores predeterminados o algo así.

Estamos hablando de llamar a comandos externos, algo dependiente de la plataforma de todos modos.

Bueno, creo que no puede ser realmente independiente de la plataforma, ya que Windows y Linux solo tienen diferentes formas de llamar a los ejecutables. En Linux, un proceso obtiene una matriz de argumentos, mientras que en Windows, un proceso solo obtiene una única línea de comando (una cadena).
(compare el más básico
CreateProcess -> línea de comandos (https://msdn.microsoft.com/library/windows/desktop/ms682425)
y
execve -> matriz de comandos (https://linux.die.net/man/2/execve)
)

Como Powershell agrega esas citas cuando los argumentos tienen espacios, me parece que powershell intenta ** pasar los argumentos de alguna manera, que CommandLineToArgvW divide la línea de comandos en los argumentos que se dieron originalmente en powershell. (De esta manera, un programa c típico obtiene los mismos argumentos en su matriz argv que una función de PowerShell obtiene como $ args).
Esto coincidiría perfectamente con pasar los argumentos a la llamada al sistema de Linux (como se sugiere a través de p / invoke).

** (y falla, ya que no escapa a las comillas)

PD: ¿Qué es necesario para iniciar un proceso de RFC?

Exactamente: PowerShell intenta asegurarse de que CommandLineToArgvW produzca el comando correcto y _después_ de analizar lo que PowerShell ya ha analizado.

Este ha sido un problema de larga data en Windows, veo una razón para llevar esa dificultad a * nix.

Para mí, esto se siente como un detalle de implementación, que realmente no necesita un RFC. Si cambiamos el comportamiento en Windows PowerShell, podría justificar una RFC, pero incluso entonces, el cambio correcto podría considerarse una corrección de errores (posiblemente riesgosa).

Sí, también creo que cambiarlo en Linux para usar una llamada directa al sistema haría que todos se sintieran más felices.

Sigo pensando que también debería cambiarse en Windows,
(Tal vez agregando una variable de preferencia para aquellos que no quieren cambiar sus scripts)
porque ahora está mal, es un error. Si esto se corrigiera, una llamada directa al sistema en Linux ni siquiera sería necesaria, porque cualquier argumento llegaría al siguiente proceso sin cambios.

Pero como hay ejecutables, que dividen la línea de comandos de una manera, incompatible con CommandLineToArgvW , me gusta la idea de @ be5invis de un operador para argumentos, pero no crearía un operador de escape automático (debería ser predeterminado para todos los argumentos), pero en su lugar agregue un operador para no escapar de un argumento (no agregue comillas, no escape nada).

Este problema nos surgió hoy cuando alguien probó el siguiente comando en PowerShell y no estaba usando PowerShell cuando no funcionó, pero CMD sí:

wmic useraccount where name='username' get sid

De PSCX echoargs, wmic.exe ve esto:

94> echoargs wmic useraccount where name='tso_bldadm' get sid
Arg 0 is <wmic>
Arg 1 is <useraccount>
Arg 2 is <where>
Arg 3 is <name=tso_bldadm>
Arg 4 is <get>
Arg 5 is <sid>

Command line:
"C:\Users\hillr\Documents\WindowsPowerShell\Modules\Pscx\3.2.2\Apps\EchoArgs.exe" wmic useraccount where name=tso_bldadm get sid

Entonces, ¿qué API usa CMD.exe para invocar el proceso / formar la línea de comando? De hecho, ¿qué hace -% para que este comando funcione?

@rkeithhill CreateProcessW . llamada directa. De Verdad.

¿Por qué Powershell se comporta de manera diferente en estas dos situaciones? Específicamente, está envolviendo de manera inconsistente los argumentos que contienen espacios entre comillas dobles.

# Desired argv[1] is 4 characters: A, space, double-quote, B
$ .\echoargs.exe 'A \"B'
<"C:\test\echoargs.exe" "A \"B">
<A "B>
# Correct!

# Desired argv value is 4 characters: A, double-quote, space, B
$ .\echoargs.exe 'A\" B'
<"C:\test\echoargs.exe" A\" B>
<A"> <B>
# Wrong...

Parece que no hay rima ni razón. En la primera situación, envuelve mi arg con comillas dobles, pero en la segunda situación no lo hace. Necesito saber exactamente cuándo lo hará y cuándo no incluirá entre comillas dobles para poder ajustar manualmente (o no) mi script.

.echoargs.exe se crea compilando lo siguiente con cl echoargs.c

// echoargs.c
#include <windows.h>
#include <stdio.h>
int wmain(int argc, WCHAR** argv) {
    wprintf(L"<%ls>\n", GetCommandLineW());
    for(int i = 1; i < argc; i++) {
        wprintf(L">%s< ", argv[i]);
    }
    wprintf(L"\n");
}

EDITAR: Aquí está mi $ PSVersionTable:

Name                           Value
----                           -----
PSVersion                      5.1.15063.296
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.15063.296
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

El comportamiento con respecto a las citas cambió varias veces, por lo que sugiero usar algo como esto:

Editar: formulario actualizado a continuación
Versión antigua:

# call helper

function Run-Native($command) {
    $env:commandlineargumentstring=($args | %{'"'+ ($_ -replace '(\\*)"','$1$1\"' -replace '(\\*)$','$1$1') + '"'}) -join ' ';
    & $command --% %commandlineargumentstring%
}

# some test cases

Run-Native .\echoargs.exe 'A "B' 'A" B'
Run-Native .\echoargs.exe 'A "B'
Run-Native .\echoargs.exe 'A" B'
Run-Native .\echoargs.exe 'A\" B\\" \'

Salida:

<"C:\test\echoargs.exe"  "A \"B" "A\" B">
<A "B> <A" B>

<"C:\test\echoargs.exe"  "A \"B">
<A "B>

<"C:\test\echoargs.exe"  "A\" B">
<A" B>

<"C:\test\echoargs.exe"  "A\\\" B\\\\\" \\">
<A\" B\\" \>

El primer -replace dobla las barras invertidas delante de las comillas y agrega una barra invertida adicional, para escapar de la qoute.
El segundo -replace duplica las barras diagonales inversas al final del argumento, de modo que la cita de cierre no se escape.

Esto usa --% (PS v3 y superior), que es AFAIK la única forma confiable de pasar comillas a ejecutables nativos.


Editar:

Versión actualizada de Run-Native , ahora llamada Invoke-NativeCommand (como se sugiere )

function Invoke-NativeCommand() {
    $command, [string[]] $argsForExe = $args
    if($argsForExe.Length -eq 0){
        & $command
    } else {
        $env:commandlineargumentstring=($argsForExe | %{
            if($_ -match '^[\w\d\-:/\\=]+$'){
                $_ #don't quote nonempty arguments consisting of only letters, numbers, or one of -:/\=
            } else {
                $_ <# double backslashes in front of quotes and escape quotes with backslash #> `
                    -replace '(\\*)"','$1$1\"' `
                   <# opening quote after xxx= or after /xxx: or at beginning otherwise #> `
                    -replace '^([\w\d]+=(?=.)|[/-][\w\d]+[:=](?=.)|^)','$1"' `
                   <# double backslashes in front of closing quote #> `
                    -replace '(\\*)$','$1$1' `
                   <# add closing quote #> `
                    -replace '$','"'
            }
        }) -join ' ';
        & $command --% %commandlineargumentstring%
    }
}

(con algo de inspiración de iep )

  • no cita interruptores simples
  • todavía funciona con argumentos vacíos
  • funciona si no hay argumentos presentes
  • debería funcionar principalmente con msiexec, cmdkey, etc.
  • todavía siempre funciona para programas, que siguen las reglas comunes
  • no usa "" no estándar como escapado " - por lo tanto, aún no funcionará para comillas incrustadas en .bat o argumentos msiexec

Gracias, no sabía nada de --% . ¿Hay alguna forma de hacerlo sin filtrar la variable de entorno al proceso nativo? (y a cualquier proceso que pueda invocar)

¿Existe un módulo de PowerShell que implemente un Run-Native Cmdlet para que todos lo usen? Esto suena como algo que debería estar en la Galería Powershell. Si fuera lo suficientemente bueno, podría ser la base para un RFC.

"filtrar" suena como si estuviera preocupado por la seguridad. Sin embargo, observe que la línea de comandos es visible para los procesos secundarios de todos modos. (Por ejemplo: gwmi win32_process |select name,handle,commandline|Format-Table en Windows y ps -f en Linux)

Si aún desea evitar una variable de entorno, es posible que pueda construir algo usando invoke-expression.

Respecto al RFC:
No creo que tal comando debería ser necesario, en su lugar, este debería ser el comportamiento predeterminado:

https://github.com/PowerShell/PowerShell-RFC/issues/90

Estoy de acuerdo en que el comportamiento predeterminado de PowerShell debería corregirse. Había estado asumiendo pesimistamente que nunca cambiaría por razones de compatibilidad con versiones anteriores, por lo que sugerí escribir un módulo. Sin embargo, me gusta mucho la forma en que su RFC permite volver a habilitar el antiguo comportamiento de escape a través de una variable de preferencia.

Permítanme resumir la discusión, con la dosis justa de opinión:

  • Está claro que tenemos un problema de compatibilidad con versiones anteriores, por lo que el comportamiento anterior debe seguir estando disponible.

  • La propuesta de RFC de @TSlivede da cuenta de eso mientras señala de manera encomiable _ el camino hacia el futuro_.
    Desafortunadamente, su propuesta languidece como _PR_ al momento de escribir este artículo, y ni siquiera ha sido aceptada como un _draft_ de RFC todavía.


Por _el futuro_, quiero decir:

  • PowerShell es un shell por derecho propio que, con suerte, pronto perderá su bagaje relacionado con cmd.exe , por lo que las únicas consideraciones que importan cuando se trata de llamar a utilidades externas (ejecutables que son (típicamente) aplicaciones de consola / terminal) son :

    • Los argumentos a pasar deben especificarse mediante las reglas del análisis del modo de argumento de _PowerShell_ _only_.

    • Cualquier _literal_ resultante de ese proceso debe pasarse _ como está_ al ejecutable de destino, como argumentos _individuales_.

    • En otras palabras: como usuario, todo lo que debería tener que centrarse es en cuál será el resultado del análisis de _PowerShell_, y poder confiar en que ese resultado se pase tal como está, con PowerShell ocupándose de cualquier -codificación de escenas - si es necesario.


_Implementando_ el futuro:

  • En _Windows_:

    • Por razones _históricas_, Windows _no_ permite pasar argumentos _ como una matriz de literales_ al ejecutable de destino; en cambio, se necesita una _single_ string que codifique _todos_ los argumentos usando _pseudo shell syntax_. Lo que es peor, depende en última instancia del ejecutable de destino individual interpretar esa única cadena y dividirla en argumentos.

    • Lo mejor que puede hacer PowerShell es formar esa única cadena, entre bastidores, después de haber realizado su _propia_ división de la línea de comando en argumentos individuales, de una _ manera estandarizada y predecible_.

    • La propuesta de RFC de @TSlivede propone precisamente eso, al sugerir que PowerShell sintetice la línea de comandos de pseudo shell de una manera que hará que el tiempo de ejecución de Windows C / C ++ recupere los argumentos de entrada tal como están al realizar su análisis :

      • Dado que, en última instancia, depende de cada ejecutable de destino interpretar la línea de comando, no hay garantía de que esto funcione en todos los casos, pero dichas reglas son la opción más sensata, porque la mayoría de las utilidades existentes utilizan estas convenciones.

      • Las únicas excepciones notables son los _archivos por lotes_, que podrían recibir un tratamiento especial, como sugiere la propuesta de RFC.

  • En plataformas _Unix_:

    • Estrictamente hablando, los problemas que plagan el análisis de argumentos de Windows _necesitan nunca surgir_, porque las llamadas nativas de la plataforma para crear nuevos procesos _ aceptan argumentos como matrices de literales_ - cualquier argumento con el que PowerShell termine después de realizar su análisis _propiedad_ debería simplemente pasarse _ como está_ .
      Para citar a @lzybkr : "No veo ninguna razón para llevar esa dificultad a * nix".

    • Lamentablemente, debido a las limitaciones actuales de .NET Core (CoreFX), estos problemas _do_ entran en juego, porque la API CoreFX fuerza innecesariamente la anarquía del argumento de _Windows_ que también pasa al mundo Unix, al requerir el uso de una pseudo línea de comando incluso en Unix.

    • He creado este problema de CoreFX para solicitar que se solucione.

    • Mientras tanto, dado que CoreFX vuelve a dividir la pseudo línea de comando en argumentos basados ​​en las reglas de C / C ++ citadas anteriormente, la propuesta de @TSlivede también debería funcionar en plataformas Unix.

Como https://github.com/PowerShell/PowerShell/issues/4358 se cerró como un duplicado de esto, aquí hay un breve resumen de ese problema:

Si un argumento de un ejecutable externo con una barra invertida al final contiene un espacio, actualmente se cita ingenuamente (agregue comillas antes y después del argumento). Cualquier ejecutable, que siga las reglas habituales, lo interpreta así:
De @ mklement0 's comentario :

El segundo " en ".\test 2\" , debido a que está precedido por \, se interpreta como un "escapado", lo que hace que el resto de la cadena, a pesar de que falte un cierre en ese momento, se interprete como parte del mismo argumento.

Ejemplo:
(de @akervinen 's comentario )

PS X:\scratch> .\ps-args-test.exe '.\test 2\'
Argumento recibido: .\test 2"

El problema ocurre con mucha frecuencia, porque PSReadLine agrega una barra invertida al final de la función de autocompletar para directorios.

Dado que corefx parece estar abierto a producir la API que necesitamos, lo aplazaré a 6.1.0. Para 6.0.0, veré si podemos arreglar # 4358

@TSlivede Tomé su función, la Invoke-NativeCommand (ya que Run no es un verbo válido) y agregué un alias ^ y lo publiqué como un módulo en PowerShellGallery:

install-module NativeCommand -scope currentuser
^ ./echoargs 'A "B' 'A" B'

@ SteveL-MSFT:

Es bueno tener un recurso provisional, pero uno menos engorroso sería, mientras esperamos una solución CoreFX, implementar las reglas oficiales bien definidas de citación / análisis de argumentos como se detalla en la propuesta RFC de @TSlivede nosotros mismos de manera preliminar, lo cual no No suena demasiado difícil de hacer.

Si solo solucionamos el problema \" , el paso de argumentos sigue estando fundamentalmente roto, incluso en escenarios simples como los siguientes:

PS> bash -c 'echo "hi there"'
hi    # !! Bash sees the following tokens:  '-c', 'echo hi', 'there'

Creo que en este punto hay suficiente acuerdo sobre cuál debería ser el comportamiento, por lo que no necesitamos un proceso completo de RFC, ¿verdad?

La única decisión pendiente es cómo tratar los problemas de compatibilidad con versiones anteriores en _Windows_.

@ mklement0 @ SteveL-MSFT
¿ Ya hemos roto la compatibilidad?

La única decisión pendiente es cómo tratar los problemas de compatibilidad con versiones anteriores en Windows.

Sí, pero esa es la parte difícil, ¿verdad?

@ be5invis ¿a qué te refieres con "compatibilidad ya

Además, si CoreFX está al borde de una solución en su capa, prefiero no crear una solución provisional en nuestra capa antes que ellos.

Y como alguien dijo anteriormente en el hilo, esto es molesto, pero también está bastante bien documentado en la comunidad. No estoy seguro de que debamos romperlo dos veces en los próximos dos lanzamientos.

@joeyaiello :

¿No es la solución para # 4358 ya un cambio importante para aquellos que han solucionado el problema al duplicar el \ final? por ejemplo, "c:\tmp 1\\" ? En otras palabras: si limita los cambios a esta corrección, _dos_ cambios importantes están garantizados: este ahora y otro más tarde después de cambiar a la futura API CoreFx; y aunque eso _podría_ suceder también si se implementara una solución provisional completa ahora, es poco probable, dado lo que sabemos sobre este cambio que se avecina.

Por el contrario, puede dificultar la adopción en Unix si escenarios de cotización comunes como
bash -c 'echo "hi there"' no funcionan correctamente.

Sin embargo, me doy cuenta de que arreglar esto es un cambio radical mucho mayor.

@ PowerShell / powershell-Committee discutió esto y acordó que, como mínimo, el uso de --% debería tener el mismo comportamiento que bash en que las comillas se escapan para que el comando nativo las reciba. Lo que todavía está abierto a debate es si este debería ser el comportamiento predeterminado sin usar --%

Nota:

  • Supongo que es necesaria una llamada a un ejecutable de shell real cuando se usa --% en _Unix_, en lugar de intentar _emulatar_ el comportamiento del shell, que es lo que sucede en _Windows_. Emular no es difícil en Windows, pero sería mucho más difícil en Unix, dadas las muchas más características que necesitarían emular.

  • El uso de un shell real plantea la pregunta de qué shell usar: mientras que bash es ubicuo, su comportamiento predeterminado no es compatible con POSIX ni POSIX requiere que esté presente, por lo que, para la portabilidad, otros lenguajes de scripting requieren /bin/sh , el ejecutable de shell decretado por POSIX (que _ puede_ ser Bash ejecutándose en modo de compatibilidad (por ejemplo, en macOS), pero ciertamente no tiene que hacerlo (por ejemplo, Dash en Ubuntu)).

Podría decirse que también deberíamos apuntar a /bin/sh , lo que, sin embargo, significa que algunas características de Bash, en particular la expansión de llaves, ciertas variables automáticas, ... - no estarán disponibles


Uso de --%

Usaré el comando echoargs --% 'echo "hi there"' como un ejemplo a continuación.

el mismo comportamiento que bash en que las comillas se escapan para que el comando nativo las reciba.

La forma de hacerlo _en el futuro, una vez que se haya extendido la API CoreFX_ sería no realizar ningún escape y, en su lugar, hacer lo siguiente:

  • Cree un proceso de la siguiente manera:

    • /bin/sh como ejecutable, (efectivamente) asignado a ProcessStartInfo.FileName .

    • La siguiente matriz de tokens de argumento _literal_, _individual_ como ProcessStartInfo.ArgumentList :

    • -c como primer argumento

    • echoargs 'echo "hi there"' como segundo argumento, es decir, la línea de comando original usó _literalmente_, exactamente como se especificó, excepto que --% fue eliminado.

En efecto, la línea de comando se pasa a través de _ como está_ al ejecutable de shell, que luego puede realizar _su_ análisis.

Entiendo que, en la ausencia actual de una forma basada en matrices para pasar argumentos literales, necesitamos combinar -c y echoargs 'echo "hi there"' en una _solo_ cadena _con escape_, lamentablemente _solo para beneficio de la CoreFX API_, que, cuando llega el momento de crear el proceso real, luego _ invierte_ este paso y divide la cadena única de nuevo en tokens literales, y garantizar que esta inversión siempre dé como resultado la lista original de tokens literales es la parte desafiante.

Nuevamente: la única razón para involucrar escapar aquí es debido a la limitación actual de CoreFX.
Para trabajar con esta limitación, la siguiente cadena de escape única debe, por lo tanto, asignarse a la propiedad .Arguments de una instancia ProcessStartInfo , con el escape realizado según lo especificado por Parsing C ++ Command-Line Arguments :

  • /bin/sh como ejecutable, (efectivamente) asignado a ProcessStartInfo.FileName .
  • La siguiente cadena de escape única como el valor de ProcessStartInfo.Arguments :
    -c "echoargs 'echo \"hi there\"'"

## Comportamiento por defecto

Lo que todavía está abierto a debate es si este debería ser el comportamiento predeterminado sin usar -%

El comportamiento predeterminado en Unix debería ser muy diferente:

  • No deben entrar en juego consideraciones de escape _que no sean las propias de PowerShell_ (excepto en _Windows_, donde eso no se puede evitar, lamentablemente; pero allí las reglas de MS C ++ son el camino a seguir, _para ser aplicadas detrás de escena_; en su defecto, --% proporciona una trampilla de escape).

  • Cualquier argumento con el que termine _PowerShell_, después de su propio análisis, debe pasarse como un _array de literales_ , a través de la próxima propiedad ProcessStartInfo.ArgumentList .

Aplicado al ejemplo sin --% : echoargs 'echo "hi there"' :

  • PowerShell realiza su análisis habitual y termina con los siguientes 2 argumentos:

    • echoargs
    • echo "hi there" (comillas simples, que solo tenían función sintáctica para _PowerShell_, eliminado)
  • ProcessStartInfo se completa de la siguiente manera, con la próxima extensión CoreFX en su lugar:

    • echoargs como el valor (efectivo) de la propiedad .FileName
    • _Literal_ echo "hi there" como el único elemento para agregar a la instancia Collection<string> expuesta por .ArgumentList .

Nuevamente, en ausencia de .ArgumentList esa no es una opción _ todavía_, pero _ mientras tanto_ se podría emplear el mismo escape auxiliar compatible con MS C ++ como se describe anteriormente.

@ SteveL-MSFT
Como ya mencioné en Hacer que el símbolo de detención de análisis (-%) funcione en Unix (# 3733) , recomiendo encarecidamente no cambiar el comportamiento de --% .

Si se necesita alguna funcionalidad especial para /bin/sh -c , utilice un símbolo diferente y deje --% tal como está.

@TSlivede :

_Si_ algo --% -_like_ se implementa en Unix - y con el globbing nativo y una multitud generalmente más conocedora de la línea de comandos en Unix, percibo menos necesidad de ello - luego elijo un _símbolo diferente_ - como --$ - probablemente tenga sentido (lo siento, había perdido la pista de todos los aspectos de este extenso debate de múltiples temas).

Los diferentes símbolos también servirían como recordatorios visualmente conspicuos de que se está invocando un comportamiento específico de plataforma no portátil.

Eso deja la pregunta de qué debería hacer PowerShell cuando se encuentra con --% en Unix y --$ en Windows.

Estoy bien dejando --% como está. Presentar algo como --$ que llama a / bin / sh y supongo que cmd.exe en Windows puede ser una buena manera de resolver esto.

¿No hay posibilidad de crear un cmdlet para estos comportamientos?

@iSazonov, ¿estás sugiriendo algo como Invoke-Native ? No estoy seguro de ser fan de eso.

Sí, como Start-Native .
Como broma :-), ¿no te gustan los cmdlets en PowerShell?

En Build.psm1 tenemos Start-NativeExecution con enlace a https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/

@ SteveL-MSFT

Me voy bien,% como está.

Creo que todos estamos de acuerdo en que --% debe continuar comportándose como lo hace en _Windows_.

En _Unix_, por el contrario, este comportamiento no tiene sentido, como he intentado demostrar aquí , en resumen:

  • las comillas simples no se manejan correctamente
  • la única forma de hacer referencia a la variable de entorno es como _cmd.exe-style_ ( %var% )
  • las características nativas importantes, como el globbing y la división de palabras, no funcionan.

La principal motivación para introducir --% fue, si lo he entendido correctamente, habilitar la _reutilización de las líneas de comando cmd.exe existentes_ tal cual.

  • Como tal, --% es inútil en Unix con su comportamiento actual.
  • _Si quisiéramos una característica _análoga_ en Unix, tendría que estar basada en /bin/sh -c , como se propuso anteriormente, probablemente usando un símbolo diferente.

No creo que haya una necesidad de una función basada en cmd /c en Windows, ya que --% tiene eso _ principalmente_ cubierto, posiblemente de una manera que sea _ suficientemente buena_.
@TSlivede ha señalado que no se están emulando todas las características del shell, pero en la práctica eso parece no ser una preocupación (por ejemplo, las sustituciones de valores variables como %envVar:old=new% no son compatibles, ^ no es un carácter de escape, y el uso de --% está limitado a un comando _single_ - no se usan los operadores de control y redirección de cmd.exe ; dicho esto, no creo que --% estuvo destinado a emular el comando completo _lines_).

Como tal, algo como --$ , si se implementa, sería la _contraparte_ de Unix para --% .

En cualquier caso, al menos el tema de ayuda about_Parsing merece una advertencia notoria de que --% será inútil en Unix en todos los casos excepto en unos pocos.

@iSazonov Puede tener sentido tener Start-Native para manejar algunos escenarios específicos, pero deberíamos intentar mejorar PowerShell para que el uso de exes nativos sea más natural y predecible

@ PowerShell / powershell-Committee revisó esto y acordó que --% debería significar tratar los argumentos como lo harían en sus plataformas relevantes, lo que significa que se comportan de manera diferente en Windows y Linux, pero consistentes dentro de Windows y consistentes dentro de Linux. Sería más confuso para el usuario introducir un nuevo sigilo. Dejaremos la implementación al ingeniero sobre cómo habilitar esto.

Los scripts multiplataforma tendrían que ser conscientes de esta diferencia de comportamiento, pero parece poco probable que los usuarios se den cuenta de esto. Si el comentario de los usuarios es que existe una necesidad debido a un mayor uso de plataformas cruzadas, entonces podemos volver a presentar un nuevo sello.

Por segunda vez he sido mordido por estas diferencias de análisis de argumentos de cadenas nativas vs cmdlets cuando se usa ripgrep .

Aquí está el resultado de las llamadas de PowerShell (echo.exe es de "C: \ Archivos de programa \ Git \ usrbin \ echo.exe")

Ahora sé que debería tener cuidado con esta " peculiaridad:

> echo.exe '"test'
test

Pero esta peculiaridad me supera ...

echo.exe '^\+.+;'
^\+.+;
echo.exe '^\+.*;'
^+.*;

En el segundo caso, necesito poner el doble \ para pasar \ al comando nativo, en el primer caso no necesito hacerlo 😑

Entiendo que esto sería un cambio importante para cambiar este comportamiento, por lo que no habrá una diferencia entre los cmlets y los comandos nativos. Sin embargo, creo que peculiaridades como esta es algo que disuade a las personas de usar powershell como shell predeterminado.

@mpawelski Acabo de probar esto en mi caja de Windows con git 2.20.1.vfs.1.1.102.gdb3f8ae y no me reproduce con 6.2-RC.1. Lo ejecuté varias veces y constantemente hace eco ^\+.+;

@ SteveL-MSFT, creo que @mpawelski especificó accidentalmente el mismo comando dos veces. Verá el problema si pasa '^\"+.*;' por ejemplo, tenga en cuenta la parte \" , con la expectativa razonable de que el contenido de la cadena entre comillas simples se pasará tal cual , para que el programa de destino externo vea ^\"+.*; como el valor del argumento:

# Note how the "\" char. is eaten.
PS> bash -c 'printf %s "$1"' - '^\"+.*;'
^"+.*; 

#"# Running the very same command from Bash does NOT exhibit the problem:
$ bash -c 'printf %s "$1"' - '^\"+.*;'
^\"+.*; 

-% fue, si lo he entendido correctamente, para permitir la reutilización de las líneas de comando cmd.exe existentes tal como están.

Eso no es todo. --% se introdujo porque muchas utilidades de línea de comandos de Windows toman argumentos que son interpretados por PowerShell, lo que elimina completamente la invocación de la utilidad. Esto puede amargar a la gente rápidamente si ya no pueden usar fácilmente sus utilidades nativas favoritas. Si tuviera una moneda de veinticinco centavos por cada vez que respondí preguntas sobre SO y otros comandos exe nativos de RE que no funcionan correctamente debido a este problema, probablemente podría llevar a la familia a cenar a Qoba. :-) Por ejemplo, tf.exe permite ; como parte de la especificación de un espacio de trabajo. Git permite {} y @~1 , etc., etc.

Se agregó --% para decirle a PowerShell que NO analice el resto de la línea de comando, simplemente envíelo "tal cual" al exe nativo. Con un frote, permita variables usando la sintaxis de cmd env var. Es un poco feo, pero hombre, realmente, realmente es útil todavía en Windows.

Al hacer de esto un cmdlet, no estoy seguro de ver cómo funciona. --% es una señal al analizador para simplificar el análisis hasta el final de su vida útil

Francamente, como usuario de PowerShell desde hace mucho tiempo y esta característica en particular, tiene sentido para mí usar el mismo operador en otras plataformas para simplemente significar: simplificar el análisis hasta el final de su vida útil. Existe la cuestión de cómo permitir alguna forma de sustitución de variables. Aunque se siente un poco feo, en macOS / Linux puede tomar% envvar% y sustituir el valor de cualquier env var correspondiente. Entonces podría ser portátil entre plataformas.

La cuestión es que, si no haces esto, terminas con un código condicional, no exactamente lo que yo llamaría portátil:

$env:Index = 1
if ($IsWindows) {
    git show --% @~%Index%
}
else {
    git show --$ @~$Index
}

Preferiría este trabajo en todas las plataformas:

$env:Index = 1
git show --% @~%Index%

El comportamiento en Windows debe permanecer como está debido a la compatibilidad.

@rkeithhill

Eso no es todo.

Por proceso de eliminación, las líneas de comando que son anteriores a PowerShell en el mundo de Windows se escribieron por cmd.exe , y eso es precisamente lo que --% pretende emular , como lo demuestra lo siguiente:

  • --% expande cmd.exe -style %...% referencias de variables de entorno, como %USERNAME% (si no hay un shell ni una lógica especial involucrada, dichos tokens se pasarán _literal_)

  • directamente de la boca de PowerShell (si lo desea; énfasis agregado):

La web está llena de líneas de comando escritas para Cmd.exe . Estas líneas de comandos funcionan con bastante frecuencia en PowerShell, pero cuando incluyen ciertos caracteres, por ejemplo, un punto y coma (;) un signo de dólar ($) o llaves, debe realizar algunos cambios, probablemente agregando algunas comillas. Esto parecía ser la fuente de muchos dolores de cabeza menores.

Para ayudar a abordar este escenario, agregamos una nueva forma de "escapar" del análisis de las líneas de comando. Si usa un parámetro mágico -%, detenemos nuestro análisis normal de su línea de comando y cambiamos a algo mucho más simple. No coincidimos con las citas. No nos detenemos en el punto y coma. No expandimos las variables de PowerShell. Expandimos las variables de entorno si usa la sintaxis Cmd.exe (por ejemplo,% TEMP%). Aparte de eso, los argumentos hasta el final de la línea (o tubería, si está entubando) se pasan tal cual. Aquí hay un ejemplo:

Tenga en cuenta que este enfoque no se ajusta al mundo _Unix_ (para recapitular desde arriba):

  • La agrupación de argumentos no citados como *.txt no funcionará.

  • Los shells tradicionales (como POSIX) en el mundo Unix _no_ usan %...% para referirse a variables de entorno; esperan la sintaxis $... .

  • (Afortunadamente) no hay una línea de comando _raw_ en el mundo Unix: cualquier ejecutable externo debe pasar un _array_ de _literals_, por lo que sigue siendo PowerShell o CoreFx el que debe analizar primero la línea de comando en argumentos.

  • Los shells tradicionales (similares a POSIX) en el mundo Unix aceptan cadenas '...' (entre comillas simples), que --% no reconoce - vea # 10831.

Pero incluso en el mundo de Windows --% tiene limitaciones graves y no obvias :

  • Lo más obvio es que no puede hacer referencia directamente a las variables de PowerShell en su línea de comandos si usa --% ; la única - engorrosa - solución es definir temporalmente las variables _environment_, que luego debe hacer referencia con %...% .
  • No puede encerrar la línea de comando en (...) - porque el cierre ) se interpreta como una parte literal de la línea de comando.
  • No puede seguir la línea de comando con ; y otra instrucción, porque ; se interpreta como una parte literal de la línea de comando.
  • No puede usar --% dentro de un bloque de secuencia de comandos de una sola línea, porque el cierre } se interpreta como una parte literal de la línea de comando.
  • No puede usar redirecciones, porque se tratan como una parte literal de la línea de comando; sin embargo, puede usar cmd --% /c ... > file para permitir que cmd.exe maneje la redirección.
  • No puede usar caracteres de continuación de línea, ni PowerShell ( ` ) ni cmd.exe ( ^ ); se tratarán como _literales_.

    • --% solo analiza (como máximo) hasta el final de la línea.


Afortunadamente, ya tenemos una sintaxis multiplataforma: la sintaxis propia de PowerShell.

Sí, usar eso requiere que sepa lo que _PowerShell_ considera metacaracteres, que es un _supersconjunto_ de lo que cmd.exe y shells tipo POSIX como Bash consideran metacaracteres, pero ese es el precio a pagar por una plataforma más rica. experiencia de línea de comandos agnóstica.

Qué desafortunado es, entonces, que PowerShell maneje tan mal las citas de " caracteres, que es el tema mismo de este problema y que se resume en este número de documentos .

@rkeithhill , comencé un poco por la tangente; déjame intentar cerrarlo:

  • --% realidad ya _está_ implementado a partir de PowerShell Core 6.2.0; p.ej,
    /bin/echo --% %HOME% imprime el valor de la variable de entorno HOME ; por el contrario,
    /bin/ls --% *.txt no funcionará como se esperaba, porque *.txt se pasa como un literal.

  • En última instancia, cuando no usamos --% , necesitamos ayudar a los usuarios a diagnosticar cómo se ve la línea de comandos / matriz de argumentos que se construye _detrás de escena_ (lo que nos lleva de regreso al venerable # 1761):

    • Su útil echoArgs.exe hace precisamente eso, y en el problema vinculado pidió sensatamente que dicha funcionalidad sea parte de PowerShell.
    • @ SteveL-MSFT reflexionó sobre la inclusión de la línea de comando resultante en el registro de errores.

Finalmente, para aplicar mi argumento anterior, usando la propia sintaxis de PowerShell como la inherentemente portátil, a su ejemplo:

# Works cross-platform, uses PowerShell syntax 
# Note: No need for an aux. *environment* variable (which should be cleaned up afterward)
$Index = 1
git show "@~$Index"

# Alternative, quoting just the '@'
git show `@~$Index

Sí, requiere que sepa que un token inicial @ es un metacarácter que, por lo tanto, debe citar, pero a medida que PowerShell se usa más ampliamente, el conocimiento de tales requisitos también debería generalizarse.

Para su información, este problema debería ser casi el mismo en Windows y en los similares a Unix. La implementación de CoreFx de Unix SDProcess tiene una cosa llamada ParseArgumentsIntoList , que implementa CommandLineToArgvW casi exactamente sin _setargv conmutadores (y con los "" indocumentados entre comillas → " función). Unix no debería ser un problema adicional en esto porque en la forma actual está roto de la misma manera que Windows.

_setargv no es algo que todos los programas usen después de todo, y probablemente no valga la pena considerarlo porque, bueno, está un poco afectado por cambios de comportamiento entre las versiones de CRT . Lo mejor que podemos y debemos hacer es rodear todo con comillas dobles, agregar algunos backslahes agradables y eso es todo.

Otro ejemplo en el que los argumentos no se analizan correctamente:

az "myargs&b"

En este caso, az obtiene myargs y se intenta ejecutar b como un nuevo comando.

La solución alternativa es: az -% "myargs & b"

@ SteveL-MSFT, podemos eliminar la etiqueta Waiting - DotNetCore , dado que la característica requerida: la propiedad ProcessStartInfo.ArgumentList basada en colecciones ha estado disponible desde .NET Core 2.1

@TSlivede está al tanto del nuevo método y planea usarlo, pero el RFC asociado, https://github.com/PowerShell/PowerShell-RFC/pull/90 , está languideciendo, desafortunadamente.

Sugiero que continuemos la discusión sobre la implementación allí.

Sin embargo, en la discusión de RFC, @joeyaiello habla de hacer de los cambios una característica experimental, pero cada vez me queda más claro que no se puede arreglar el comportamiento de las citas sin romper masivamente el código existente:

Cualquiera que haya tenido que trabajar alrededor:

  • la imposibilidad de pasar un argumento de cadena vacía ( foo.exe "" actualmente pasa _no_ argumentos)
  • la eliminación efectiva inesperada de las comillas dobles debido a la falta de escape automático de las comillas dobles incrustadas ( foo.exe '{ "foo": "bar" }' se pasa como un escape incorrecto "{ "foo": "bar" }" )
  • las peculiaridades de ciertas CLI como msiexec que no aceptan ciertos argumentos entre comillas dobles _como un todo_ ( foo.exe foo="bar none" se pasa como "foo=bar none" ).
    Nota: Es msiexec culpable aquí, y con los cambios propuestos aplicados, pasar la forma requerida foo="bar none" de cotización requerirá --% .

estará en problemas, porque las soluciones se romperán con los cambios propuestos aplicados.

Por tanto, una pregunta adicional es:

  • ¿Cómo podemos hacer que el comportamiento correcto esté disponible al menos como una función _opt-in_?

  • Un problema fundamental con tales mecanismos - típicamente, por variable de preferencia, pero cada vez más también por declaraciones using - es el alcance dinámico de PowerShell; es decir, el comportamiento de inclusión voluntaria también se aplicará de forma predeterminada al código llamado _desde_ el código de inclusión voluntaria, lo cual es problemático.

    • Quizás es hora de introducir en general el alcance _léxico_ de características, una generalización de la propuesta using strict alcance léxico en el RFC de modo estricto léxico, igualmente languideciente, creado por @lzybkr.

    • ¿Algo así como un using preference ProperArgumentQuoting ámbito léxico? (Nombre obviamente negociable, pero estoy luchando por encontrar uno).

Dadas todas esas advertencias, y que este también es un problema con la invocación directa, donde no podemos simplemente agregar un nuevo parámetro, estoy firmemente a favor de romper el comportamiento anterior.

Sí, probablemente se romperá mucho. Pero en realidad, solo porque estaba completamente roto para empezar. No creo que sea particularmente factible priorizar el mantenimiento de lo que equivale a una gran cantidad de soluciones para el comportamiento defectuoso en lugar de tener una característica que _en realidad funciona_.

Como nueva versión principal, creo que la v7 es realmente la única oportunidad que tendremos de rectificar esta situación correctamente durante bastante tiempo, y deberíamos aprovechar la oportunidad. Siempre que informemos a los usuarios, no creo que la transición sea mal recibida en general.

Si sentimos que romper el cambio es inevitable, entonces quizás deberíamos diseñar la solución ideal, teniendo en cuenta que debería ser fácil de imprimir en una sesión interactiva y es posible tener una versión de script que funcione mejor en todas las plataformas.

De acuerdo, @ vexx32 :

Un shell que no pasa argumentos de manera confiable a programas externos está fallando en uno de sus mandatos principales.

Ciertamente tiene _mi_ voto para hacer el cambio radical, pero me temo que otros se sentirán de manera diferente, sobre todo porque se promociona que la v7 permite a los usuarios de WinPS a largo plazo migrar a PSCore.


@iSazonov : https://github.com/PowerShell/PowerShell-RFC/pull/90 describe la solución correcta.

Para recapitular su espíritu:

PowerShell, como shell, necesita analizar los argumentos de acuerdo con _sus_ reglas y luego pasar los valores de argumento expandidos resultantes _verbatim_ al programa de destino; los usuarios nunca deberían tener que pensar en cómo PowerShell hace que eso suceda; de lo único que deberían preocuparse es de conseguir la sintaxis correcta de _PowerShell_.

  • En plataformas similares a Unix, ProcessStartInfo.ArgumentList ahora nos da una manera de implementar esto perfectamente, dado que el _array_ de valores de argumentos expandidos se puede pasar _ como está_ al programa de destino, porque así es como el paso de argumentos - sensiblemente - funciona en este mundo.

  • En Windows, tenemos que lidiar con la desafortunada realidad de la anarquía que es el análisis de la línea de comandos de Windows , pero, como shell, nos corresponde _simplemente hacer que funcione entre bastidores, tanto como sea posible_, que es lo que el RFC describe, aunque acabo de descubrir una arruga que hace uso exclusivo de ProcessStartInfo.ArgumentList no lo suficientemente bueno, desafortunadamente (debido a la generalización de _archivos por lotes_ como puntos de entrada CLI, como lo demuestra @ SteveL-MSFT az[.cmd] ejemplo anterior). Para aquellos casos extremos en los que hacer lo sensato no es lo suficientemente bueno, hay --% .

Quizás PSSA pueda ayudar a mitigar el cambio radical advirtiendo a los usuarios que usan un formato de argumento que se cambiará.

Creo que debemos considerar la posibilidad de adoptar algo como características opcionales para avanzar en este cambio radical junto con algunos otros .

¿Tiene realmente algún valor mantener el comportamiento existente? Aparte de la compatibilidad con versiones anteriores, quiero decir.

No creo que valga la pena mantener dos rutas de código para esto, simplemente para retener una implementación rota porque algún código antiguo puede necesitarlo de vez en cuando. No creo que sea irrazonable esperar que folx actualice su código de vez en cuando. 😅

Doblemente si esperamos que PS7 sea un sustituto de WinPS; La única razón por la que puedo ver para mantener el comportamiento de la v7 es si esperamos que la gente use el mismo script para ejecutar comandos tanto en 5.1 como en 7, lo que (con suerte) debería ser un caso bastante raro si PS7 es un buen reemplazo para 5.1.

E incluso entonces, no sería demasiado difícil para los usuarios contabilizar ambos. Siempre que no cambiemos la sintaxis del lenguaje real, debería ser bastante fácil hacer algo como esto:

if ($PSVersionTable.PSVersion.Major -lt 7) {
    # use old form
}
else {
    # use new form
}

Siempre que informemos a los usuarios de la diferencia, creo que sería un alivio bienvenido por el dolor que ha sido tener que manejar extraños ejecutables nativos en PS hasta ahora. 😄

Como @TylerLeonhardt mencionó en la discusión de las características opcionales, implementar eso significa que ahora mantiene _múltiples_ implementaciones distintas, cada una de las cuales debe mantenerse y probarse, además de mantener y probar el marco de características opcionales. Realmente no parece que valga la pena por esto, tbh.

La compatibilidad con versiones anteriores de https://github.com/PowerShell/PowerShell/issues/1761 y https://github.com/PowerShell/PowerShell/issues/10675. "Arreglar" la interoperabilidad con comandos nativos es algo que me gustaría resolver para vNext. Por lo tanto, si alguien ve problemas nuevos o existentes en esta categoría, envíeme una copia y lo etiquetaré como corresponde (o si tiene permiso de clasificación, etiquetarlo como los demás).

Las características opcionales son módulos :-) Tener características opcionales en Engine es un gran dolor de cabeza para el soporte, especialmente para el soporte de Windows. Podríamos modularizar Engine reduciendo las dependencias internas y reemplazando las API internas con públicas; después de esto, podríamos implementar funciones opcionales en Engine de manera fácil.

@iSazonov una de las cosas que mi equipo verá en vNext es hacer que el motor sea más modular :)

¿Cuál es la solución recomendada aquí para los usuarios finales?

Esto es artificial, pero es la forma más sencilla de llegar al manejo correcto de * ArgumentList desde el propio marco .NET:

Add-Type -AssemblyName "System"
function startProcess([string] $FileName, [string[]] $ArgumentList) {
    $proc = ([System.Diagnostics.Process]::new())
    $proc.StartInfo.UseShellExecute = $false
    $proc.StartInfo.FileName = $FileName
    $proc.StartInfo.CreateNoWindow = $true
    foreach ($a in $ArgumentList) { $proc.StartInfo.ArgumentList.Add($a) }
    $proc.Start()
    return $proc
}

startProcess -FileName 'C:\Program Files\nodejs\node.exe' -ArgumentList '-e','console.log(process.argv.join(''\n''))','--','abc" \" messyString'

Por supuesto, puede hacerlo menos complicado de usar aquí mediante el uso de parámetros posicionales y algunos trucos get-command .

* DESCARGO DE RESPONSABILIDAD: No existe una única forma correcta de analizar una línea de cmd en Windows. Por "correcto" me refiero al estilo MSVCRT de conversión de cmdline desde / a argv, implementado por .NET en todas las plataformas para el manejo de ArgumentList, procesamiento main (string[] args) y llamadas de generación externas en Unix. Esta muestra se proporciona TAL CUAL, sin garantía de interoperabilidad general. Consulte también la sección "línea de comandos de Windows" de la documentación propuesta de child_process de NodeJS .

@ Artoria2e5 Esa es exactamente la conclusión a la que llegué. System.Diagnostics.Process es la única forma confiable de ejecutar un ejecutable externo, pero escapar de los argumentos puede ser complicado debido a las reglas stdlib:

2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal " 
N backslashes ==> N backslashes

Como resultado, se me ocurrió la siguiente lógica para escapar de los argumentos y envolverlos entre comillas dobles " para la ejecución del proceso:
https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L244

También puede ser complicado obtener STDOUT y STDERR en tiempo real mientras se ejecuta un ejecutable externo, así que he creado este paquete

https://github.com/choovick/ps-invoke-externalcommand

que estoy usando mucho en Windows, Linux y Mac y hasta ahora sin problemas y puedo pasar argumentos con nueva línea y otros caracteres especiales en ellos.

GitHub
Contribuya al desarrollo del comando choovick / ps-invoke-externalcommand creando una cuenta en GitHub.
GitHub
Contribuya al desarrollo del comando choovick / ps-invoke-externalcommand creando una cuenta en GitHub.

@choovick, ¡ todo el redireccionamiento de stdio es genial!

Sin embargo, no estoy de acuerdo un poco en la parte de escape, ya que hay una cosa que lo hace por ti llamada ArgumentList. Entiendo que es una adición relativamente reciente (?), Y se vuelve decepcionante ya que MS olvidó poner un inicializador String, String [] para SDProcessStartInfo. (¿Hay un lugar para estas ... propuestas de interfaz .NET?)


charla

Mi función de escape de ese ejemplo de NodeJS es un poco diferente a la suya: usa el indocumentado (pero se encuentra en .NET core y MSVCRT) "" escape entre comillas. Hacerlo simplifica el trabajo de selección de barra invertida. Hice esto principalmente porque se usó para el cmd todopoderoso, que no entiende que \" no debe quitar las comillas del resto de la cadena. En lugar de luchar con \^" pensé que estaría mejor con algo que ha estado en uso secreto desde el principio de los tiempos.

@ Artoria2e5 desafortunadamente ArgumentList no está disponible en PowerShell 5.1 en Windows, usando su ejemplo

`` No puede llamar a un método en una expresión de valor nulo.
En C: Users \ yser \ dev \ test.ps1: 7 char: 37

  • ... oreach ($ a en $ ArgumentList) {$ proc.StartInfo.ArgumentList.Add ($ a)}
  • ~ ~ ~ ~ ~ ~ ~ ~

    • CategoryInfo: InvalidOperation: (:) [], RuntimeException

    • FullyQualifiedErrorId: InvokeMethodOnNull

      ''

Dado que el argumento personalizado escapa a la lógica ...

charla

en lo que respecta a `\ ^" `en NodeJS, creo que tuve que hacer eso hace varios años :) y creo que funcionó

System.Diagnostics.Process es la única forma confiable de ejecutar ejecutables externos

El problema es que no obtendrá la integración con los flujos de salida de PowerShell y no obtendrá el comportamiento de transmisión en la canalización.

Aquí está el resumen de las soluciones alternativas necesarias si aún desea permitir que PowerShell realice la invocación (que es definitivamente preferible) :

  • Si necesita pasar argumentos con caracteres _embedded_ " ., _Double_ ellos en Windows, si es posible, o \ -escape de ellos:

    • En Windows, al llamar a _batch files_ y si sabe que el programa de destino entiende "" como un " escapado, use $arg -replace '"', '""'

      • El uso de "" es preferible en Windows (evita el problema de Windows PowerShell y funciona con CLI que usan archivos por lotes como _stubs_, como Node.js y Azure), pero no todos los ejecutables lo admiten (en particular, no Ruby y Perl).
    • De lo contrario (siempre en Unix), use $arg -replace '"', '\"'

    • Nota: En _Windows PowerShell_, esto todavía no siempre funciona correctamente si el valor también contiene _spaces_, porque la presencia del literal \" en el valor situacionalmente _no_ activa la inclusión de comillas dobles, a diferencia de PowerShell Core; por ejemplo, pasar los descansos '3\" of snow' .

    • Además, antes del escape anterior, debe duplicar la instancia de \ inmediatamente anterior a " , si se van a tratar como literales:

      • $arg = $arg -replace '(\\+)"', '$1$1"'
  • Si necesita pasar un argumento _empty_, pase '""' .

    • '' -eq $arg ? '""' : $arg (alternativa de WinPS: ($arg, '""')['' -eq $arg]
  • Solo Windows PowerShell, no hagas esto en PS Core (donde se ha solucionado el problema):

    • Si su argumento _contiene espacios_ y _ termina en_ (uno o más) \ , duplique las instancias \ finales.

      • if ($arg -match ' .*?(\\+)$') { $arg = $arg + $Matches[1] }
  • Si cmd / se invoca un archivo por lotes con argumentos que _no_ tienen espacios (por lo tanto, _no_ activan comillas dobles automáticas por parte de PowerShell) pero contienen cualquiera de &|<>^,; (por ejemplo, a&b ), use _embedded enclosing double-quoting_ para asegurarse de que PowerShell pase un token entre comillas dobles y, por lo tanto, no rompa la llamada cmd / batch-file:

    • $arg = '"' + $arg + '"'
  • Si necesita lidiar con ejecutables que no se comportan bien como msiexec.exe , escriba el argumento entre comillas simples:

    • 'foo="bar none"'

Como se indicó, estas soluciones se _brotarán_ una vez que se solucione el problema subyacente.


A continuación se muestra una función simple (no avanzada) iep (para "invocar programa externo"), que:

  • Realiza todos los escapes descritos anteriormente, incluida la selección automática de mayúsculas y minúsculas para msiexec y prefiere el escape de "" sobre \" según el programa de destino.

    • La idea es que puede pasar cualquier argumento centrándose únicamente en la sintaxis de cadena de _PowerShell_, y confiar en la función para realizar el escape necesario para que el programa de destino también vea el valor literal que ve PowerShell.

    • En PowerShell _Core_, esto debería funcionar bastante bien; en Windows PowerShell todavía tiene casos extremos con comillas dobles incrustadas que se rompen si se debe usar \" escape (como se discutió anteriormente).

  • Conserva la sintaxis de invocación de comandos de shell.

    • Simplemente anteponga iep  a su línea de comando.

  • Como lo haría la invocación directa:

    • se integra con las transmisiones de PowerShell

    • envía la salida línea por línea a través de la tubería

    • establece $LASTEXITCODE basado en el código de salida del programa externo; sin embargo, no se puede confiar en $? .

Nota: La función es deliberadamente minimalista (sin declaraciones de parámetros, sin ayuda en la línea de comandos, nombre corto (irregular)), porque está destinada a ser lo más discreta posible: simplemente anteponga iep a su línea de comandos, y demás Deberia trabajar.

Ejemplo de invocación usando EchoArgs.exe (instalable a través de Chocolatey desde una sesión _elevated_ con choco install echoargs -y ):

PS> iep echoargs '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'
Arg 0 is <>
Arg 1 is <a&b>
Arg 2 is <3" of snow>
Arg 3 is <Nat "King" Cole>
Arg 4 is <c:\temp 1\>
Arg 5 is <a \" b>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "" a&b "3\" of snow" "Nat \"King\" Cole" "c:\temp 1\\" "a \\\" b"

Lo anterior muestra la salida de PowerShell Core. Observe cómo todos los argumentos se pasaron correctamente tal como los ve literalmente PowerShell, incluido el argumento vacío.

En Windows PowerShell, el argumento 3" of snow no se pasará correctamente, porque el escape \" se usa debido a la llamada a un ejecutable desconocido (como se discutió anteriormente).

Para verificar que los archivos por lotes pasan los argumentos correctamente, puede crear echoargs.cmd como contenedor para echoargs.exe :

'@echoargs.exe %*' | Set-Content echoargs.cmd

Invocar como iep .\echoargs.cmd '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'

Dado que ahora se llama a un archivo por lotes, se emplea "" -escaping, que soluciona el problema 3" of snow al llamar desde Windows PowerShell.

La función funciona igualmente en plataformas similares a Unix, que puede verificar creando un script de shell sh llamado echoargs :

@'
#!/bin/sh
i=0; for a; do printf '%s\n' "\$$((i+=1))=[$a]"; done
'@ > echoargs; chmod a+x echoargs

Invocar como iep ./echoargs '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b'


Importante : Desde entonces se ha publicado una versión más completa de esta función como ie ( I nvoke (external) E xecutable) en Acabo de publicar un módulo Native , que le animo a utilizar en lugar. Instale el módulo con
Install-Module Native -Scope CurrentUser .
El módulo también contiene un comando ins ( Invoke-NativeShell ) que aborda el caso de uso discutido en # 13068 - ver https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -671572939 para detalles.

Función iep 's código fuente (use el módulo Native lugar - vea arriba):

function iep {

  Set-StrictMode -Version 1
  if (-not (Test-Path Variable:IsCoreClr)) { $IsCoreCLR = $false }
  if (-not (Test-Path Variable:IsWindows)) { $IsWindows = $env:OS -eq 'Windows_NT' }

  # Split into executable name/path and arguments.
  $exe, [string[]] $argsForExe = $args

  # Resolve to the underlying command (if it's an alias) and ensure that an external executable was specified.
  $app = Get-Command -ErrorAction Stop $exe
  if ($app.ResolvedCommand) { $app = $app.ResolvedCommand }
  if ($app.CommandType -ne 'Application') { Throw "Not an external program, non-PS script, or batch file: $exe" }

  if ($argsForExe.Count -eq 0) {
    # Argument-less invocation
    & $exe
  }
  else {
    # Invocation with arguments: escape them properly to pass them through as literals.
    # Decide whether to escape embedded double quotes as \" or as "", based on the target executable.
    # * On Unix-like platforms, we always use \"
    # * On Windows, we use "" where we know it's safe to do. cmd.exe / batch files require "", and Microsoft compiler-generated executables do too, often in addition to supporting \",
    #   notably including Python and Node.js
    #   However, notable interpreters that support \" ONLY are Ruby and Perl (as well as PowerShell's own CLI, but it's better to call that with a script block from within PowerShell).
    #   Targeting a batch file triggers "" escaping, but in the case of stub batch files that simply relay to a different executable, that could still break
    #   if the ultimate target executable only supports \" 
    $useDoubledDoubleQuotes = $IsWindows -and ($app.Source -match '[/\\]?(?<exe>cmd|msiexec)(?:\.exe)?$' -or $app.Source -match '\.(?<ext>cmd|bat|py|pyw)$')
    $doubleQuoteEscapeSequence = ('\"', '""')[$useDoubledDoubleQuotes]
    $isMsiExec = $useDoubledDoubleQuotes -and $Matches['exe'] -eq 'msiexec'
    $isCmd = $useDoubledDoubleQuotes -and ($Matches['exe'] -eq 'cmd' -or $Matches['ext'] -in 'cmd', 'bat')
    $escapedArgs = foreach ($arg in $argsForExe) {
      if ('' -eq $arg) { '""'; continue } # Empty arguments must be passed as `'""'`(!), otherwise they are omitted.
      $hasDoubleQuotes = $arg.Contains('"')
      $hasSpaces = $arg.Contains(' ')
      if ($hasDoubleQuotes) {
        # First, always double any preexisting `\` instances before embedded `"` chars. 
        # so that `\"` isn't interpreted as an escaped `"`.
        $arg = $arg -replace '(\\+)"', '$1$1"'
        # Then, escape the embedded `"` chars. either as `\"` or as `""`.
        # If \" escaping is used:
        # * In PS Core, use of `\"` is safe, because its use triggers enclosing double-quoting (if spaces are also present).
        # * !! In WinPS, sadly, that isn't true, so something like `'foo="bar none"'` results in `foo=\"bar none\"` -
        #   !! which - due to the lack of enclosing "..." - is seen as *2* arguments by the target app, `foo="bar` and `none"`.
        #   !! Similarly, '3" of snow' would result in `3\" of snow`, which the target app receives as *3* arguments, `3"`, `of`, and `snow`.
        #   !! Even manually enclosing the value in *embedded* " doesn't help, because that then triggers *additional* double-quoting.
        $arg = $arg -replace '"', $doubleQuoteEscapeSequence
    }
      elseif ($isMsiExec -and $arg -match '^(\w+)=(.* .*)$') { 
        # An msiexec argument originally passed in the form `PROP="value with spaces"`, which PowerShell turned into `PROP=value with spaces`
        # This would be passed as `"PROP=value with spaces"`, which msiexec, sady, doesn't recognize (`PROP=valueWithoutSpaces` works fine, however).
        # We reconstruct the form `PROP="value with spaces"`, which both WinPS And PS Core pass through as-is.
        $arg = '{0}="{1}"' -f $Matches[1], $Matches[2]
      }
      # As a courtesy, enclose tokens that PowerShell would pass unquoted in "...", 
      # if they contain cmd.exe metachars. that would break calls to cmd.exe / batch files.
      $manuallyDoubleQuoteForCmd = $isCmd -and -not $hasSpaces -and $arg -match '[&|<>^,;]'
      # In WinPS, double trailing `\` instances in arguments that have spaces and will therefore be "..."-enclosed,
      # so that `\"` isn't mistaken for an escaped `"` - in PS Core, this escaping happens automatically.
      if (-not $IsCoreCLR -and ($hasSpaces -or $manuallyDoubleQuoteForCmd) -and $arg -match '\\') {
        $arg = $arg -replace '\\+$', '$&$&'
      }
      if ($manuallyDoubleQuoteForCmd) {
        # Wrap in *embedded* enclosing double quotes, which both WinPS and PS Core pass through as-is.
        $arg = '"' + $arg + '"'
      }
      $arg
    }
    # Invoke the executable with the properly escaped arguments.
    & $exe $escapedArgs
  }
}

@ mklement0 Impresionante, pero aquí hay un par que no me funciona en Windows:

iep echoargs 'somekey="value with spaces"' 'te\" st'

Arg 0 is <somekey="value>
Arg 1 is <with>
Arg 2 is <spaces">
Arg 3 is <te\">
Arg 4 is <st>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" somekey=\"value with spaces\" te\\\" st

aquí está mi matriz de prueba de argumentos :)

$Arguments = @(
    'trippe slash at the end \\\',
    '4 slash at the end \\\\',
    '\\servername\path\',
    'path=\\servername\path\',
    'key="\\servername\pa th\"',
    '5 slash at the end \\\\\',
    '\\" double slashed double quote',
    'simple',
    'white space',
    'slash at the end \',
    'double slash at the end \\',
    'trippe slash at the end \\\',
    'trippe slash at the end with space \\\ ',
    '\\" double slashed double quote',
    'double slashed double quote at the end \\"',
    '\\\" triple slashed double quote',
    'triple slashed double quote at the end \\\"',
    # slash
    'single slashes \a ^ \: \"',
    'path="C:\Program Files (x86)\test\"'
    # quotes
    'double quote " and single quote ''',
    # windows env var syntax
    "env var OS: %OS%",
    # utf16
    ('"utf16 ETHIOPIC WORDSPACE: \u1361"' | ConvertFrom-Json),
    # special chars
    "newLine`newLine"
    "tab`tab"
    "backspace`bbackspace"
    "carriage`rafter",
    "formFeed`fformFeed",
    # JSON Strings
    @"
[{"_id":"5cdab57e4853ea7b5a707070","index":0,"guid":"25319946-950e-4fe8-9586-ddd031cbb0fc","isActive":false,"balance":"`$2,841.15","picture":"http://placehold.it/32x32","age":39,"eyeColor":"blue","name":{"first":"Leach","last":"Campbell"},"company":"EMOLTRA","email":"[email protected]","phone":"+1 (864) 412-3166","address":"127 Beadel Street, Vivian, Vermont, 1991","about":"Ex labore non enim consectetur id ullamco nulla veniam Lorem velit cillum aliqua amet nostrud. Occaecat ipsum do est qui sint aliquip anim culpa laboris tempor amet. Aute sint anim est sint elit amet nisi veniam culpa commodo nostrud cupidatat in ex.","registered":"Monday, August 25, 2014 4:04 AM","latitude":"-12.814443","longitude":"75.880149","tags":["pariatur","voluptate","sint","Lorem","eiusmod"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Lester Bender"},{"id":1,"name":"Concepcion Jarvis"},{"id":2,"name":"Elsie Whitfield"}],"greeting":"Hello, Leach! You have 10 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57e8cd0ac577ab534a4","index":1,"guid":"0be10c87-6ce7-46c4-8dd6-23b1d9827538","isActive":false,"balance":"`$1,049.56","picture":"http://placehold.it/32x32","age":33,"eyeColor":"green","name":{"first":"Lacey","last":"Terrell"},"company":"XSPORTS","email":"[email protected]","phone":"+1 (858) 511-2896","address":"850 Franklin Street, Gordon, Virginia, 4968","about":"Eiusmod nostrud mollit occaecat Lorem consectetur enim pariatur qui eu. Proident aliqua sunt incididunt Lorem adipisicing ea esse do ullamco excepteur duis qui. Irure labore cillum aliqua officia commodo incididunt esse ad duis ea. Occaecat officia officia laboris veniam id dolor minim magna ut sit. Aute quis occaecat eu veniam. Quis exercitation mollit consectetur magna officia sit. Irure ullamco laborum cillum dolore mollit culpa deserunt veniam minim sunt.","registered":"Monday, February 3, 2014 9:19 PM","latitude":"-82.240949","longitude":"2.361739","tags":["nostrud","et","non","eiusmod","qui"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Meyers Dillard"},{"id":1,"name":"Jacobson Franco"},{"id":2,"name":"Hunt Hernandez"}],"greeting":"Hello, Lacey! You have 8 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57eae2f9bc5184f1768","index":2,"guid":"3c0de017-1c2a-470e-87dc-5a6257e8d9d9","isActive":true,"balance":"`$3,349.49","picture":"http://placehold.it/32x32","age":20,"eyeColor":"green","name":{"first":"Knowles","last":"Farrell"},"company":"DAYCORE","email":"[email protected]","phone":"+1 (971) 586-2740","address":"150 Bath Avenue, Marion, Oregon, 991","about":"Eiusmod sint commodo eu id sunt. Labore esse id veniam ea et laborum. Dolor ad cupidatat Lorem amet. Labore ut commodo amet commodo. Ipsum reprehenderit voluptate non exercitation anim nostrud do. Aute incididunt ad aliquip aute mollit id eu ea. Voluptate ex consequat velit commodo anim proident ea anim magna amet nisi dolore.","registered":"Friday, September 28, 2018 7:51 PM","latitude":"-11.475201","longitude":"-115.967191","tags":["laborum","dolor","dolor","magna","mollit"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Roxanne Griffith"},{"id":1,"name":"Walls Moore"},{"id":2,"name":"Mattie Carney"}],"greeting":"Hello, Knowles! You have 8 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57e80ff4c4085cd63ef","index":3,"guid":"dca20009-f606-4b99-af94-ded6cfbbfa38","isActive":true,"balance":"`$2,742.32","picture":"http://placehold.it/32x32","age":26,"eyeColor":"brown","name":{"first":"Ila","last":"Hardy"},"company":"OBLIQ","email":"[email protected]","phone":"+1 (996) 556-2855","address":"605 Hillel Place, Herald, Delaware, 9670","about":"Enim eiusmod laboris amet ex laborum do dolor qui occaecat ex do labore quis sunt. Veniam magna non nisi ipsum occaecat anim ipsum consectetur ex laboris aute ut consectetur. Do eiusmod tempor dolore eu in dolore qui anim non et. Minim amet exercitation in in velit proident sint aliqua Lorem reprehenderit labore exercitation.","registered":"Friday, April 21, 2017 6:33 AM","latitude":"64.864232","longitude":"-163.200794","tags":["tempor","eiusmod","mollit","aliquip","aute"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Duncan Guy"},{"id":1,"name":"Jami Maxwell"},{"id":2,"name":"Gale Hutchinson"}],"greeting":"Hello, Ila! You have 7 unread messages.","favoriteFruit":"banana"},{"_id":"5cdab57ef1556326f77730f0","index":4,"guid":"f2b3bf60-652f-414c-a5cf-094678eb319f","isActive":true,"balance":"`$2,603.20","picture":"http://placehold.it/32x32","age":27,"eyeColor":"brown","name":{"first":"Turner","last":"King"},"company":"DADABASE","email":"[email protected]","phone":"+1 (803) 506-2511","address":"915 Quay Street, Hinsdale, Texas, 9573","about":"Consequat sunt labore tempor anim duis pariatur ad tempor minim sint. Nulla non aliqua veniam elit officia. Ullamco et irure mollit nulla do eiusmod ullamco. Aute officia elit irure in adipisicing et cupidatat dolor in sint elit dolore labore. Id esse velit nisi culpa velit adipisicing tempor sunt. Eu sunt occaecat ex pariatur esse.","registered":"Thursday, May 21, 2015 7:44 PM","latitude":"88.502961","longitude":"-119.654437","tags":["Lorem","culpa","labore","et","nisi"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Leanne Lawson"},{"id":1,"name":"Jo Shepard"},{"id":2,"name":"Effie Barnes"}],"greeting":"Hello, Turner! You have 6 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57e248f8196e1a60d05","index":5,"guid":"875a12f0-d36a-4e7b-aaf1-73f67aba83f8","isActive":false,"balance":"`$1,001.89","picture":"http://placehold.it/32x32","age":38,"eyeColor":"blue","name":{"first":"Petty","last":"Langley"},"company":"NETUR","email":"[email protected]","phone":"+1 (875) 505-2277","address":"677 Leonard Street, Ticonderoga, Utah, 1152","about":"Nisi do quis sunt nisi cillum pariatur elit dolore commodo aliqua esse est aute esse. Laboris esse mollit mollit dolor excepteur consequat duis aute eu minim tempor occaecat. Deserunt amet amet quis adipisicing exercitation consequat deserunt sunt voluptate amet. Ad magna quis nostrud esse ullamco incididunt laboris consectetur.","registered":"Thursday, July 31, 2014 5:16 PM","latitude":"-57.612396","longitude":"103.91364","tags":["id","labore","deserunt","cillum","culpa"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Colette Mullen"},{"id":1,"name":"Lynnette Tanner"},{"id":2,"name":"Vickie Hardin"}],"greeting":"Hello, Petty! You have 9 unread messages.","favoriteFruit":"banana"},{"_id":"5cdab57e4df76cbb0db9be43","index":6,"guid":"ee3852fe-c597-4cb6-a336-1466e8978080","isActive":true,"balance":"`$3,087.87","picture":"http://placehold.it/32x32","age":33,"eyeColor":"brown","name":{"first":"Salas","last":"Young"},"company":"PLAYCE","email":"[email protected]","phone":"+1 (976) 473-2919","address":"927 Elm Place, Terlingua, North Carolina, 2150","about":"Laborum laboris ullamco aliquip occaecat fugiat sit ex laboris veniam tempor tempor. Anim quis veniam ad commodo culpa irure est esse laboris. Fugiat nostrud elit mollit minim. Velit est laborum ut quis anim velit aute enim culpa amet ipsum.","registered":"Thursday, October 1, 2015 10:59 AM","latitude":"-57.861212","longitude":"69.823065","tags":["eu","est","et","proident","nisi"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Day Solomon"},{"id":1,"name":"Stevens Boyd"},{"id":2,"name":"Erika Mayer"}],"greeting":"Hello, Salas! You have 10 unread messages.","favoriteFruit":"apple"},{"_id":"5cdab57ed3c91292d30e141d","index":7,"guid":"ef7c0beb-8413-4f39-987f-022c4e8ec482","isActive":false,"balance":"`$2,612.45","picture":"http://placehold.it/32x32","age":36,"eyeColor":"brown","name":{"first":"Gloria","last":"Black"},"company":"PULZE","email":"[email protected]","phone":"+1 (872) 513-2364","address":"311 Guernsey Street, Hatteras, New Mexico, 2241","about":"Laborum sunt exercitation ea labore ullamco dolor pariatur laborum deserunt adipisicing pariatur. Officia velit duis cupidatat eu officia magna magna deserunt do. Aliquip cupidatat commodo duis aliquip in aute dolore occaecat esse ad. Incididunt est magna in pariatur ut do ex sit minim cupidatat culpa. Voluptate eu veniam cupidatat exercitation.","registered":"Friday, June 26, 2015 7:59 AM","latitude":"38.644208","longitude":"-45.481555","tags":["sint","ea","anim","voluptate","elit"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Abby Walton"},{"id":1,"name":"Elsa Miranda"},{"id":2,"name":"Carr Abbott"}],"greeting":"Hello, Gloria! You have 5 unread messages.","favoriteFruit":"strawberry"},{"_id":"5cdab57edc91491fb70b705d","index":8,"guid":"631ff8a0-ce4c-4111-b1e4-1d112f4ecdc7","isActive":false,"balance":"`$2,550.70","picture":"http://placehold.it/32x32","age":25,"eyeColor":"brown","name":{"first":"Deirdre","last":"Huber"},"company":"VERBUS","email":"[email protected]","phone":"+1 (871) 468-3420","address":"814 Coles Street, Bartonsville, Tennessee, 7313","about":"Ipsum ex est culpa veniam voluptate officia consectetur quis et irure proident pariatur non. In excepteur est aliqua duis duis. Veniam consectetur cupidatat reprehenderit qui qui aliqua.","registered":"Monday, April 1, 2019 2:33 AM","latitude":"-75.702323","longitude":"45.165458","tags":["labore","aute","nisi","laborum","laborum"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"Genevieve Clarke"},{"id":1,"name":"Black Sykes"},{"id":2,"name":"Watson Hudson"}],"greeting":"Hello, Deirdre! You have 8 unread messages.","favoriteFruit":"strawberry"}]
"@
)

System.Diagnostics.Process tiene limitaciones cuando se usa, es por eso que tuve que escribir mi propio corredor con habilidades para capturar STDOUT STDERR y una combinación de entonces en variables, lo cual es suficiente para mis casos de uso, pero no perfecto.

Editar: como dijiste, parece tener casos extremos en Windows Poweshell 5. ¡Probé en Powershell 6 y funciona muy bien! Desafortunadamente, tengo que lidiar con Poweshell 5 hasta que 7 se haga cargo en el caso de Windows ...

Impresionante, pero aquí hay un par que no me funciona en Windows:

Sí, estas limitaciones se aplican a _Windows PowerShell_, con ejecutables para los cuales no se puede asumir la compatibilidad con "" escape de " incrustados, como se detalla en mi comentario anterior.
Si está dispuesto a asumir el soporte para el escape de "" en todas sus invocaciones (_most_, pero no todos los ejecutables en Windows lo admiten), puede modificar fácilmente la función.

Y, para confirmar lo que dijo en su edición: En PowerShell _Core_:

  • iep echoargs 'somekey="value with spaces"' 'te\" st' funciona bien.
  • Su matriz de prueba de argumentos también parece funcionar bien.

Si está dispuesto a asumir el soporte para el escape de "" en todas sus invocaciones (_most_, pero no todos los ejecutables en Windows lo admiten), puede modificar fácilmente la función.

Gracias, lo intentaré en un futuro próximo. En ocasiones trato con sqlcmd y no es compatible con "" con seguridad. Para ese caso, es fácil proporcionar una opción para omitir la lógica de escape para argumentos específicos.

La forma en que Windows analiza los argumentos de la línea de comandos se puede encontrar en Análisis de los argumentos de la línea de comandos de

El código de inicio de Microsoft C / C ++ utiliza las siguientes reglas al interpretar los argumentos dados en la línea de comandos del sistema operativo:

  • Los argumentos están delimitados por espacios en blanco, que puede ser un espacio o una pestaña.

  • El carácter de intercalación (^) no se reconoce como carácter de escape o delimitador. El carácter es manejado completamente por el analizador de línea de comandos en el sistema operativo antes de ser pasado a la matriz argv en el programa.

  • Una cadena rodeada de comillas dobles (" cadena ") se interpreta como un solo argumento, independientemente del espacio en blanco que contenga. Una cadena entre comillas se puede incrustar en un argumento.

  • Una comilla doble precedida por una barra invertida (\ ") se interpreta como un carácter literal de comillas dobles (").

  • Las barras invertidas se interpretan literalmente, a menos que precedan inmediatamente a una comilla doble.

  • Si un número par de barras invertidas va seguido de comillas dobles, se coloca una barra invertida en la matriz argv para cada par de barras invertidas, y las comillas dobles se interpretan como un delimitador de cadena.

  • Si un número impar de barras invertidas va seguido de una comilla doble, se coloca una barra invertida en la matriz argv por cada par de barras invertidas, y la barra invertida restante "escapa" de la comilla doble, lo que genera un literal comillas dobles (") para colocarlas en argv .

Esto también se discute en _exec, _wexec Funciones :

Los espacios incrustados en cadenas pueden provocar un comportamiento inesperado; por ejemplo, pasar _exec la cadena "hi there" resultará en que el nuevo proceso obtenga dos argumentos, "hi" y "there" . Si la intención era que el nuevo proceso abriera un archivo llamado "hola", el proceso fallaría. Puede evitar esto citando la cadena: "\"hi there\"" .

Cómo Python llama a los ejecutables nativos

subprocess.Popen Python puede manejar el escape correctamente al convertir args en una cadena de la manera descrita en Conversión de una secuencia de argumentos en una cadena en Windows . La implementación es subprocess.list2cmdline .

Espero que esto pueda arrojar algo de luz sobre cómo PowerShell puede manejar esto de una manera más elegante, en lugar de usar --% o doble escape (siguiendo la sintaxis de PowerShell y CMD). Las soluciones provisionales actuales realmente afectan a los clientes de la CLI de Azure (que se basa en python.exe).

Todavía no tenemos un método claro y conciso para entender cómo manejar este problema. ¿Alguien del equipo de Powershell puede arrojar luz si esto se simplifica con la v7?

Bueno, la buena noticia es que uno de los enfoques de PS 7.1 es facilitar la invocación de comandos nativos. De una publicación de blog sobre inversiones 7.1 :

La mayoría de los comandos nativos funcionan bien desde PowerShell, sin embargo, hay algunos casos en los que el análisis de argumentos no es ideal (como manejar las comillas correctamente). La intención es permitir a los usuarios cortar líneas de comando de muestra para cualquier herramienta nativa popular, pegarla en PowerShell y simplemente funciona sin necesidad de un escape específico de PowerShell.

Entonces, tal vez (con suerte) esto se abordará en 7.1

Gracias, @rkeithhill , pero no lo hace:

La intención es permitir a los usuarios cortar líneas de comando de muestra para cualquier herramienta nativa popular, pegarla en PowerShell y simplemente funciona sin necesidad de un escape específico de PowerShell.

suena como otra versión del concepto inherentemente problemático --% (símbolo de detener el análisis)?

@ SteveL-MSFT, ¿puede contarnos más sobre esta próxima función?


En resumen, la solución propuesta aquí es que todo lo que necesita para concentrarse es satisfacer los requisitos de sintaxis de _PowerShell_ y que PowerShell se encarga de todos los escapes _detrás de escena_ (en Windows; en Unix esto ya no es necesario, ahora que .NET permite nosotros para pasar una serie de tokens textuales al programa de destino), pero no hay duda de que la solución tendría que ser opt-in, si se debe mantener la compatibilidad con versiones anteriores.

Con esta solución, todavía será necesario _algunos_ manipular líneas de comando para otros shells, pero será más sencillo y, en comparación con --% , conservará todo el poder de la sintaxis de PowerShell y las expansiones de variables (expresiones con (...) , redirecciones, ...):

  • Necesita reemplazar \" con `" , pero _sólo dentro de "..." _.

  • Debe tener en cuenta que _no (otro) shell_ está involucrado, de modo que las referencias de variables de entorno de estilo Bash como $USER _no_ funcionarán (se interpretarán como variables _PowerShell_), a menos que las reemplace con el sintaxis equivalente de PowerShell, $env:USER .

    • Aparte: --% intenta compensar eso - notablemente _invariablemente_ - expandiendo referencias de variables de entorno de estilo cmd.exe como %USERNAME% , pero tenga en cuenta que no solo no lo hace ' t admite referencias de estilo Bash ( $USER ) se pasa _verbatim_ al programa de destino, pero también expande inesperadamente las referencias de estilo cmd.exe en plataformas similares a Unix y no reconoce '...' -citando.
    • Vea a continuación una alternativa que _es_ involucra el shell nativo de la plataforma respectivo.
  • Debe tener en cuenta que PowerShell tiene metacaracteres _additional_ que requieren citar / escapar para su uso literal; estos son (tenga en cuenta que @ solo es problemático como el _primer_ carácter de un argumento):

    • para shells similares a POSIX (por ejemplo, Bash): @ { } ` (y $ , si desea evitar la expansión inicial de PowerShell)
    • por cmd.exe : ( ) @ { } # `
    • Individualmente ` escapando de tales caracteres. es suficiente (por ejemplo, printf %s `@list.txt ).

Un ejemplo algo artificial:

Tome la siguiente línea de comando de Bash:

# Bash
$ printf '"%s"\n' "3\" of snow"
"3" of snow"        # output

Con la corrección propuesto en su lugar, todo lo que se necesita es reemplazar los \" instancias dentro del "..." con cerramiento discusión con `" :

# PowerShell - WISHFUL THINKING
PS> printf '"%s"\n' "3`" of snow"
"3" of snow"        # output

Es decir, no tendría que preocuparse por " incrustados dentro de '...' , y dentro de "..." solo necesita escapar de ellos para hacer feliz a _PowerShell_ (lo que también podría hacer con "" aquí).

La función iep implementa esta solución (como una solución provisional), de modo que iep printf '"%s"\n' "3`" of snow" funcione según lo previsto.


Compare esto con el comportamiento actual, roto, donde necesita saltar a través de los siguientes aros para que el comando funcione como en Bash (inexplicablemente, necesita una ronda _additional_ de escape con \ ):

# PowerShell - messy workaround to compensate for the current, broken behavior.
PS> printf '\"%s\"\n' "3\`" of snow"
"3" of snow"        # output

Con la solución en su lugar, aquellos que quieran usar una línea de comando dada _ como está_ a través del _ shell predeterminado de la plataforma_, podrán usar una _here-string_ literal para pasar a sh -c (o bash -c ) / cmd /c ; p.ej:

# PowerShell - WISHFUL THINKING
PS> sh -c @'
printf '"%s"\n' "3\" of snow"
'@
"3" of snow"  # output

Tenga en cuenta que el uso de --% _no_ funciona aquí ( printf --% '"%s"\n' "3\" of snow" ), y la ventaja adicional del enfoque basado en cadenas aquí es que las diversas limitaciones de --% no se aplican , en particular la incapacidad de usar una redirección de salida ( > ).

Si cambia a un _double_-quoted here-string ( @"<newline>....<newline>"@ ), incluso puede incrustar _Variables y expresiones de PowerShell_, a diferencia de --% ; sin embargo, debe asegurarse de que los valores expandidos no rompan la sintaxis del shell de destino.

Podemos pensar en un cmdlet dedicado con un alias sucinto (por ejemplo, Invoke-NativeShell / ins ) para tales llamadas (de modo que sh -c / cmd /c no necesite ser especificado), pero para pasar líneas de comando complejas como están, no creo que haya una forma de evitar el uso de here-strings:

# PowerShell - WISHFUL THINKING
# Passes the string to `sh -c` / `cmd /c` for execution, as appropriate.
# Short alias: ins
PS> Invoke-NativeShell @'
printf '"%s"\n' "3\" of snow"
'@
"3" of snow"  # output

Por supuesto, si confía en las características del shell nativo de la plataforma, dichas llamadas serán, por definición, específicas de la plataforma [-familia] - no funcionarán en _tanto_ Windows como en plataformas similares a Unix.

Esta es la razón por la que confiar en PowerShell _alone_, con su _sintaxis propia_, es preferible a largo plazo : proporciona una experiencia multiplataforma predecible para llamar a programas externos, incluso si eso significa que no puede usar líneas de comando diseñadas para otros shells como- es; A medida que PowerShell gana en popularidad, espero que el dolor de descubrir y conocer las modificaciones necesarias disminuya, y espero que cada vez más documentación muestre las versiones de PowerShell de las líneas de comando (también).

  • Necesita reemplazar \" con `" , pero _sólo dentro de "..." _.

Esto no funciona en todas partes.

# working everywhere but polluted with \
❯ node -e 'console.log(\"hey\")'
hey

# working in Node:
❯ node -e 'console.log(`"hey`")'
hey

# not working in Julia:
❯ julia -e 'print(`"hey`")'
`hey`

# not working anywhere:
❯ node -e "console.log(`"hey`")"
❯ node -e "console.log("hey")"
❯ node -e "console.log(""hey"")"
❯ node -e 'console.log(""hey"")'

Sintaxis bash:

❯ node -e 'console.log("hey")'
hey

Sugerencia de Powershell:

Si el equipo de PowerShell solo está buscando un símbolo, que no rompa la sintaxis anterior, ¿por qué no usar algo como una comilla invertida `para un comportamiento similar a Bash, que escapa de los literales automáticamente y permite la interpolación de cadenas? Esto también es similar a la sintaxis de JavaScript.

❯ node -e `console.log("hey")`
hey

❯ $a=hey 
❯ node -e `console.log($hey)`
hey

¿Por qué no usar algo como un backticks `para un comportamiento similar a Bash?

Las comillas invertidas ya se usan para escapar de cosas, el mismo propósito que las barras invertidas en el shell POSIX. El comentario al que te refieres ya lo señala. Hemos usado todas las cosas similares a citas ASCII. Agregar un prefijo $ a los literales de cadena normales podría funcionar, pero no creo que tenga suficiente sentido.


La forma en que Windows analiza los argumentos de la línea de comandos se puede encontrar en ...

El problema es que Windows MSVCR no solo hace eso: maneja casos de esquina de manera indocumentada . Las cosas "" están tan sólidamente configuradas que incluso las pusieron en CoreFX cuando portaron .NET a Unix. Pero de todos modos, siempre es lo suficientemente bueno para escapar, al menos hasta que alguien pida globbing.

También existe el problema clásico de que todos lo hagan de manera diferente, pero no debemos preocuparnos por eso porque siempre tenemos .NET para cmdline sin formato.

¿Por qué no usar algo como un backticks `para un comportamiento similar a Bash?

Las comillas invertidas ya se usan para escapar de cosas, el mismo propósito que las barras invertidas en el shell POSIX. Hemos usado todas las cosas similares a citas ASCII.

Existe la posibilidad de utilizar una combinación de símbolos si el analizador no es capaz de detectar que aquí `está introduciendo una cadena. Algo como '' incluso podría funcionar.

@aminya aquí hay una solución si no está usando Windows Powershell 5.1 y en 6+:
https://github.com/PowerShell/PowerShell/issues/1995#issuecomment -562334606

Como tengo que lidiar con PowerShell 5,1 en windows y 6+ en linux / mac, tengo mi propia implementación que ha estado funcionando sin problemas durante años y que permite trabajar con herramientas como kubectl, helm, terraform y otras que pasan JSON complejas. objetos dentro de los parámetros:
https://github.com/choovick/ps-invoke-externalcommand

GitHub
Contribuya al desarrollo del comando choovick / ps-invoke-externalcommand creando una cuenta en GitHub.

@choovick se proporcionó una implementación algo más corta en este hilo , todavía tengo que encontrar un caso en el que me fallaría. Esto funciona en PS a partir de v3.

La función Run-Native @AndrewSav @TSlivede es muy inteligente y concisa, y es encomiable que también funcione de manera confiable en _Windows PowerShell_; algunas cosas dignas de mención: por necesidad, el proceso hijo ve el aux. commandlineargumentstring variable de entorno (probablemente rara vez, o nunca, un problema en la práctica), las invocaciones sin argumentos actualmente no se manejan correctamente (se solucionan fácilmente y ni siquiera son un problema, si se asegura de que solo usa la función _con_ argumentos, que es para lo que sirve), _todos_ los argumentos se citan dos veces (por ejemplo, algo como 42 se pasa como "42" ), que en Windows puede (desafortunadamente) tener efectos secundarios para programas que interpretan los argumentos entre comillas dobles (o parcialmente, como en el caso msiexec ) de forma diferente.

@aminya , la única razón por la que node -e 'console.log(`"hey`")' (más o menos) funciona es por el comportamiento actual y defectuoso (ver más abajo). Supongo que lo que pretendía pasar era _verbatim_ console.log("hey") , que, si PowerShell en Windows escapaba correctamente como se propone aquí, pasaría tal cual entre comillas simples: node -e 'console.log("hey")' . Esto debería _automáticamente_ traducirse a node -e "console.log(\"hey\")" ( \ comillas dobles, " ) _detrás de escena_.

Dado cuánto tiempo ha pasado este hilo, permítanme intentar recapitular:
Solo debería tener que preocuparse por los requisitos de sintaxis de _PowerShell_, y el trabajo de PowerShell _como shell_ es asegurarse de que los _valores de los argumentos textuales_ que resultan del propio análisis de PowerShell se pasen al programa externo _ como está_.

  • En plataformas similares a Unix, ahora que tenemos soporte para .NET Core, hacer esto es trivial, ya que los valores textuales se pueden pasar tal cual como los elementos de un _array_ de argumentos, que es como los programas reciben argumentos de forma nativa allí. .
  • En Windows, los programas externos reciben argumentos como _una sola cadena de línea de comandos_ (una decisión de diseño histórica lamentable) y deben realizar su propio análisis de la línea de comandos. Pasar múltiples argumentos como parte de una sola cadena requiere reglas de citación y análisis para delinear correctamente los argumentos; Si bien esto es, en última instancia, gratuito para todos (los programas son libres de analizar como lo deseen), la convención de sintaxis más utilizada (como se indicó anteriormente y también se propuso en el RFC lamentablemente lo que implementan los compiladores C / C ++ de Microsoft , por lo que tiene sentido ir con eso.

    • _Update_: Incluso en Windows podemos aprovechar la propiedad de colección de tokens textuales ArgumentList de System.Diagnostics.ProcessStartInfo en .NET Core: en Windows se traduce automáticamente a un comando correctamente citado y escapado -cadena de línea cuando se inicia el proceso; sin embargo, para _archivos por lotes_ es posible que aún necesitemos un manejo especial; consulte https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -552231174

La implementación de lo anterior es, sin duda, un cambio radical masivo, por lo que presumiblemente requiere una suscripción.
Creo que seguir adelante con esto es imprescindible, si queremos deshacernos de todos los dolores de cabeza actuales de cotización, que siguen apareciendo y obstaculizan la adopción de PowerShell, especialmente en el mundo Unix.


En cuanto a node -e 'console.log(`"hey`")' : dentro de '...' , no use ` , a menos que desee que ese carácter se pase como está. Debido a que PowerShell actualmente _no_ escapa de los caracteres " textuales. en su argumento como \" detrás de escena, lo que node ve en la línea de comando es console.log(`"hey`") , que se analiza como dos cadenas literales directamente adyacentes: sin comillas console.log(` y "hey`" entre comillas dobles. Después de eliminar los " , que tienen la función _syntactic_ debido a que no se han escapado \ , el código JavaScript que se ejecuta es finalmente console.log(`hey`) , y eso solo funciona porque `....` token adjunto es una forma de literal de cadena en JavaScript, es decir, un _template literal_.

@AndrewSav ¡ He probado con mi loco objeto de prueba y funcionó para mí! Solución muy elegante, probada en Windows 5.1 y PS 7 en linux. Estoy de acuerdo con tener todo entre comillas dobles, no trato con msiexec o sqlcmd también conocidos por tratar " explícitamente.

Mi implementación personal también tiene una lógica de escape simple similar a la que mencionas: https://github.com/choovick/ps-invoke-externalcommand/blob/master/ExternalCommand/ExternalCommand.psm1#L278

pero he escrito un montón de código para mostrar y capturar subprocesos STDOUT y STDERR en tiempo real dentro de ese módulo ... Probablemente se pueda simplificar mucho, pero no tenía necesidad ...

@ mklement0 este hilo nunca terminará (: Necesitamos proporcionar un módulo PowerShell publicado en la galería ps que se adaptará a la mayoría de los casos de uso y será lo suficientemente simple de usar, o esperar las próximas mejoras de shell.

GitHub
Contribuya al desarrollo del comando choovick / ps-invoke-externalcommand creando una cuenta en GitHub.

@ mklement0 este hilo nunca terminará (: Necesitamos proporcionar un módulo PowerShell publicado en la galería ps que se adaptará a la mayoría de los casos de uso y será lo suficientemente simple de usar, o esperar las próximas mejoras de shell.

Si el equipo de PowerShell decide no arreglar esto en el programa, entonces diría que un shell que no puede ejecutar programas externos de forma nativa y correcta no será el shell de mi elección. Estas son las cosas básicas que faltan en PowerShell.

@aminya sí, descubrí que ese problema es muy molesto para mí cuando tuve que pasar a él. Pero características como las siguientes hicieron que valiera la pena.

  • Marco de parámetros flexible, CMDlets confiables fáciles de construir.
  • Módulos y repositorios de módulos internos para compartir la lógica común en toda la organización, evitando una gran cantidad de duplicación de código y centralización de la funcionalidad principal que se puede refactorizar en un solo lugar.
  • Plataforma cruzada. Tengo gente que ejecuta mis herramientas en Windows en PS 5.1 y en linux / mac en PS 6/7

Realmente espero que el equipo de PS mejore esto en el futuro para hacerlo menos complicado.

La implementación de lo anterior es, sin duda, un cambio radical masivo, por lo que presumiblemente requiere una suscripción.

¿Te refieres a implementar https://github.com/PowerShell/PowerShell-RFC/pull/90?

@aminya , estoy de acuerdo en que llamar a programas externos con argumentos es un mandato central de un shell y debe funcionar correctamente.

Un módulo, como lo sugirió @choovick , todavía tiene sentido para _Windows PowerShell_, que está en modo de mantenimiento solo para arreglos críticos de seguridad.
De manera complementaria, si / cuando se escribe el tema de ayuda conceptual propuesto sobre la llamada a programas externos, consulte https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5152 - una función auxiliar que corrige los problemas en versiones anteriores / Windows PowerShell, como el de @TSlivede anterior, podría publicarse allí directamente.

@iSazonov Sí, lo que quise decir es implementar https://github.com/PowerShell/PowerShell-RFC/pull/90 .

En cuanto al hilo interminable y las preocupaciones del cambio de ruptura:

La última respuesta oficial al RFC vinculado fue este comentario de @joeyaiello del 8 de julio de 2019 (énfasis agregado):

pero creemos que este [RFC] tiene mucho sentido independientemente del comportamiento existente y sin tener en cuenta su ruptura . Ahora que tenemos funciones experimentales, creemos que es perfectamente razonable implementar esto hoy detrás de una marca de función experimental, y podemos averiguar más adelante si se trata de un comportamiento de suscripción o de exclusión , si hay alguna transición ruta, y si una variable de preferencia es el mecanismo correcto para activarla y desactivarla.

_Personalmente_, no me importaría arreglar el comportamiento _por defecto_, aunque es un cambio rotundo; el RFC de hecho propone eso y sugiere una suscripción voluntaria si desea el comportamiento _old_ (roto).

Sin embargo, sospecho que aquellos con código heredado que mantener se opondrán, ya que _todas las soluciones alternativas existentes dejarán de funcionar_ (ver más arriba ) y mantener la compatibilidad con versiones anteriores todavía parece ser el objetivo general.

Si el nuevo comportamiento fijo se habilita, todavía tiene la incomodidad de tener que hacer algo solo para obtener el comportamiento correcto, pero al menos el código existente no se romperá.

Pero los mecanismos de aceptación existentes son en sí mismos problemáticos:
Ahora que se han rechazado las características opcionales de RFC de @KirkMunro , eso prácticamente deja una _variable de preferencia_, y el desafío es el alcance dinámico de PowerShell: código de terceros llamado desde un alcance que optó por no haber sido diseñado para usar el nuevo la implementación podría entonces romperse (a menos que la variable de preferencia se restablezca temporalmente).

Aquí se requiere el alcance _Lexical_ de la habilitación, que actualmente no tenemos. El RFC para el alcance _léxico_ del modo estricto propone la implementación de una característica con alcance léxico (o un propósito diferente), a través de una declaración using (que, en particular, generalmente tiene un alcance _dinámico_). Siguiendo este patrón, vale la pena considerar una declaración using ProperExternalArgumentQuoting ámbito léxico (el nombre es un WIP :), si es técnicamente factible.

Necesitamos que el comité de PowerShell intervenga (nuevamente) y brinde una guía clara sobre el camino a seguir, con comentarios _ oportuna_ sobre las preguntas a medida que surjan. @ SteveL-MSFT?


Tenga en cuenta que una solución similar a --% insinuada en la publicación del blog 7.1 (ver arriba ) (que personalmente creo que no vale la pena perseguir, ver arriba y este comentario ), sería una característica _separada_: arreglar el nativo de PowerShell El comportamiento (sin emulación) sigue siendo imprescindible.

Personalmente, no me importaría arreglar el comportamiento por defecto, aunque es un cambio rotundo; de hecho, el RFC propone eso y sugiere una suscripción opcional si desea el comportamiento anterior (roto).

Si el nuevo comportamiento fijo se habilita, todavía tiene la incomodidad de tener que hacer algo solo para obtener el comportamiento correcto, pero al menos el código existente no se romperá.

De acuerdo, de hecho, diría que tiene menos sentido tener un valor predeterminado roto que un valor predeterminado implementado correctamente. Dado el hecho de que la implementación actual es de hecho un error , el nuevo comportamiento debe ser opt-in, no opt-out, ya que realmente no tiene sentido continuar fomentando llamadas de shell externas rotas que son propensas a romperse inesperadamente formas. En cualquier caso, PowerShell 7 debería esforzarse por mejorar sobre el Windows PowerShell heredado.

@ SteveL-MSFT y yo acordamos que deberíamos cerrar este a favor de # 13068. Todo lo que toquemos aquí es un cambio demasiado importante, y deberíamos abordar el problema con un nuevo operador que sirva como modo de suscripción.

No veo absolutamente cómo el # 13068 resolvería esto: si ese operador se introduce según lo previsto, todavía no tenemos forma de llamar correctamente a ningún ejecutable nativo con una matriz dada de argumentos o con algunos argumentos explícitos cuyo contenido se origina a partir de variables.

El ejemplo que @JustinGrote dio en ese hilo actualmente no funciona de manera confiable (si las comillas incrustadas son posibles en la carga útil del argumento) y agregar ese operador no dará ninguna alternativa que mejore nada.

@joeyaiello ¿Puede al menos dejar este problema abierto hasta que ese operador realmente exista y alguien pueda mostrar cómo ese operador mejoraría algo que se mencionó en este hilo?

Ah, y también ¿qué pasa con Linux? Este problema es estúpido e inesperado en Windows, pero en Linux tiene incluso mucho menos sentido, especialmente porque no hay un historial muy largo de scripts de PowerShell de Linux que se rompan.

Hacer un operador especial para esto no tiene ningún sentido para un shell de línea de comandos, ya que su trabajo principal es lanzar programas y pasarles argumentos. Presentar un nuevo operador que hace system() para este trabajo es como si Matlab presentara una forma de llamar calc.exe porque tiene un error en su aritmética. En cambio, lo que debería hacerse es que:

  • El equipo de pwsh se prepara para una nueva versión principal que corrige las cosas de la línea de comandos, moviendo el comportamiento actual detrás de un cmdlet integrado.
  • Como solución provisional, la próxima versión de pwsh obtiene un cmdlet integrado que usa el nuevo comportamiento correcto para pasar la línea de comandos.

Lo mismo se aplica a Start-Process . (En realidad, es un candidato bastante bueno para el cmdlet "nuevo" con algunas opciones como -QuotingBehavior Legacy ...) Consulte # 13089.

¿Por qué Powershell se comporta de manera diferente en estas dos situaciones? Específicamente, está envolviendo de manera inconsistente los argumentos que contienen espacios entre comillas dobles.

Obtengo resultados consistentes en la v.7. Parece fijo.

PING 'A \"B'

La solicitud de ping no pudo encontrar el host A "B.

PING 'A\" B'

La solicitud de ping no pudo encontrar el host A "B.

No es fijo, porque los nombres de host textuales que debe ver ping son A \"B y A\" B - _con_ los caracteres \ .

PowerShell, como shell, debe analizar los argumentos según _sus_ reglas, únicamente, y luego asegurarse de forma transparente de que el proceso de destino ve los mismos valores textuales que fueron el resultado del propio análisis de PowerShell.

Esos _otros_ shells - y esos programas deficientes que se ejecutan en Windows que deben actuar como su propio shell, por así decirlo, al tener que analizar una _ línea de comando_ solo para extraer los argumentos individuales pasados ​​- use \ como escape El personaje no debe entrar en la imagen aquí - acomodar eso (necesario solo en Windows, en Unix simplemente pasa los argumentos textuales directamente como una matriz) es el trabajo de PowerShell como un shell, _detrás de escena_.

Por otro lado, al igual que PowerShell en sí mismo no requiere escapar de " _inside '...' _ (cadenas entre comillas simples), tampoco los shells compatibles con POSIX como bash : ejecutado desde bash , por ejemplo, /bin/echo 'A \"B' (sensatamente) imprime A \"B (los \ se tratan como literales en cadenas entre comillas simples) - que ejecutar el mismo comando de PowerShell (inesperadamente) produce
A "B - falta el \ - es una manifestación de los problemas discutidos aquí.

Debo aclarar:

  • Desde la perspectiva de querer pasar finalmente A "B _verbatim_, debería poder usar 'A "B' de PowerShell.

  • La línea de comando que PowerShell construye actualmente detrás de escena contiene "A "B" - que el proceso de destino ve como A B - es decir, el recinto ciego en "..." , sin escapar del _embedded_ " resultó en la pérdida efectiva del " incrustado. Lo que PowerShell _debería_ usar en la línea de comando detrás de escena en este caso es "A \"B" , es decir, el " integrado necesita \ -escaping.

  • De manera similar, el mismo cerramiento ciego hace que 'A \"B' se represente como "A \"B" en la línea de comandos detrás de escena, que da la casualidad de que convierte el _embedded_ \" en un _escaped_ " carácter, que el proceso de destino ve como A "B ; es decir, la falta de escape _automatic_ resultó en la pérdida efectiva del \ incrustado. Lo que PowerShell _debería_ usar en la línea de comando detrás de escena en este caso es "A \\\"B" , es decir, tanto el \ como el " necesitan escapar.

No es fijo, porque los nombres de host textuales que debe ver ping son A \"B y A\" B - _con_ los caracteres \ .

"Eso" se refiere aquí a la denuncia citada, que afortunadamente no puedo reproducir.

@ yecril71pl , ya veo: mi suposición (incorrecta) fue que "los argumentos que contienen de manera inconsistente espacios entre comillas dobles" se refieren a la falta de escape automático de los caracteres " y \ incrustados_, como explicado en mi comentario anterior, y ese es el meollo de esta cuestión.

Ha habido pequeñas correcciones en PowerShell Core que Windows PowerShell no tiene; Solo puedo pensar en uno ahora mismo:

  • Windows PowerShell usa comillas dobles ciegas en caso de un \ final: 'A B\' convierte en (roto) "A B\" - PS Core lo maneja correctamente ( "A B\\" ) .

Dado que este repositorio es solo para PS Core, es suficiente centrarse en lo que todavía está roto en PS Core (puede ser útil mencionar las diferencias _como un aparte_, pero es mejor hacer explícito ese aspecto).


E incluso el escenario que tenía en mente _ está_ todavía roto en PS Core, pero solo _ si omite el \ del argumento_:

Pasar 'A" B' todavía da como resultado _no_-dobles comillas A" B detrás de escena (mientras que 'A\" B' da como resultado "A\" B" , que también está roto, como se discutió, solo diferentemente).

Dado que este repositorio es solo para PS Core, es suficiente centrarse en lo que todavía está roto en PS Core (puede ser útil mencionar las diferencias _como un aparte_, pero es mejor hacer explícito ese aspecto).

Meta aparte: me parece útil saber que el comportamiento incorrecto mencionado en el comentario de otro usuario no se aplica. Por supuesto, podríamos haber descartado el comentario simplemente porque el usuario no se molestó en verificar la versión actual. Bien. Reporteros rebeldes siendo rebeldes, es mejor estar seguro. EN MI HUMILDE OPINIÓN.

No hay argumento allí, pero es importante proporcionar _ encuadre y contexto adecuados_ a tales _asides_, especialmente en un hilo muuuucho largo donde el comentario original, necesario para el contexto, se publicó hace mucho tiempo y, de hecho, ahora está _escondido_ por defecto (nunca está de más en realidad _enlace_ al comentario original que se está citando).

Me pregunto si estas discusiones marcan la diferencia. Claramente, las decisiones del comité son independientes de lo que quiere la comunidad. Mire las etiquetas: Resolution- won't fix. Pero como PowerShell es de código abierto (MIT), la comunidad puede solucionar esto en una bifurcación separada y llamar a esta PowerShellCommunity ( pwshc ) para abreviar.

Esto elimina la necesidad de compatibilidad con versiones anteriores. Más adelante en PowerShell 8, el comité podría integrar la bifurcación.

Acerca de la compatibilidad con versiones anteriores: PowerShell no viene preinstalado en ningún sistema operativo y, para mí, la dificultad de instalar PowerShellCommunity es lo mismo que PowerShell . Prefiero instalar la versión comunitaria y usarla de inmediato en lugar de esperar alguna versión 8 futura (o hacer el código más complejo con un nuevo operador).

Dado que el Comité ha decidido mantener las cosas rotas, es mejor saber qué tan rotas están. Creo que la comunidad puede vivir con Invoke-Native que hace lo correcto. No es trabajo de la comunidad salvar la cara de Microsoft en contra de su voluntad.

es mejor saber lo mal rotos que están

Estoy totalmente de acuerdo, incluso si el problema no se puede solucionar en el momento, saber cómo hacer las cosas bien _ en principio, si llega el momento_ es importante, incluso si ese momento _nunca_ viene en el contexto de un idioma dado.

la comunidad puede vivir con Invoke-Native que hace lo correcto

Para ser claro:

  • Algo como Invoke-NativeShell aborda un _ caso de uso diferente_, que es el tema de # 13068, y para ese caso de uso, tal cmdlet _no_ es una solución provisional: es la solución adecuada; consulte https://github.com/ PowerShell / PowerShell / issues / 13068 # issuecomment -656781439

  • _Este_ problema se trata de arreglar _PowerShell en sí mismo_ (no se trata de la funcionalidad de plataforma _nativa_), y la solución _stopgap_ para eso es proporcionar un _opt-in_ de poca ceremonia, de ahí la propuesta de enviar la función iep -en función, pendiente de una corrección _propia_ en una versión futura que se permite romper sustancialmente la compatibilidad con versiones anteriores.

No es trabajo de la comunidad salvar la cara de Microsoft

No creo que la preocupación de @aminya sea ​​salvar la cara de nadie, se trata de arreglar un comportamiento fundamentalmente roto en un área que es el mandato central de un caparazón.

Dicho esto, no estoy seguro de que fragmentar el ecosistema de PowerShell con una bifurcación sea el camino correcto.

No tengo tiempo para responder a todo esto en este momento, pero creo que esto es razonable, así que estoy reabriendo:

¿Puede al menos dejar este problema abierto hasta que ese operador realmente exista y alguien pueda mostrar cómo ese operador mejoraría algo que se mencionó en este hilo?

Como dije en un tema relacionado, el operador de llamadas nativo agrega complejidad en UX y es una dirección equivocada.
Al mismo tiempo, toda la discusión aquí trata de simplificar la interacción con aplicaciones nativas.

Lo único que nos detiene es que es un cambio radical. Hemos dicho muchas veces que esto es demasiada destrucción, pero sopesémoslo.

Veamos una sesión interactiva. Un usuario instalará una nueva versión de PowerShell y descubrirá un nuevo comportamiento al invocar aplicaciones nativas. ¿Qué dirá? Especularé que dirá: "Gracias, ¡finalmente puedo escribir y funciona!".

Veamos el escenario de ejecución / alojamiento de un script. Cualquier versión nueva de una aplicación (¡incluso con un pequeño cambio!) Puede romper un proceso empresarial. Siempre esperamos esto y verificamos nuestros procesos después de cualquier actualización. Si encontramos un problema, tenemos varias formas:

  • deshacer la actualización
  • preparar una solución rápida para el script
  • desactivar una función que rompa nuestro guión hasta que estas cosas se solucionen o mueran con el tiempo.

_Dado que las actualizaciones de versión siempre rompen algo, la última opción es la mejor que podemos tener y aceptar.

(Quiero señalar que en este momento PowerShell Core no es un componente de Windows y, por lo tanto, no puede estropear las aplicaciones de alojamiento directamente, solo los scripts se ven afectados directamente).

Quiero recordarte lo que Jason dijo anteriormente: esta es una corrección de errores.
Arreglemoslo y simplifiquemos todo para todos. Permitir que los usuarios trabajen powershell-y .

Como dije en un tema relacionado, el operador de llamada nativo agrega complicidad en UX y es una dirección equivocada.

complejidad ❓

Veamos una sesión interactiva. Un usuario instalará una nueva versión de PowerShell y descubrirá un nuevo comportamiento al invocar aplicaciones nativas. ¿Qué dirá? Especularé que dirá: "Gracias, ¡finalmente puedo escribir y funciona!".

Tengo un escenario diferente: los nuevos usuarios prueban PowerShell, se dan cuenta de que falla misteriosamente, lo mueven a la Papelera de reciclaje y nunca regresan. Eso es lo que haría yo.

preparar una solución rápida para el script

Eso es algo que deberíamos hacer para cubrir los casos obvios en los scripts de usuario.

date cuenta de que misteriosamente falla

Toda la discusión aquí se trata solo de deshacerse de este "falla misteriosa". Y es mejor hacer esto simplificando pero no agregando nuevas cosas misteriosas.

Toda la discusión aquí se trata solo de deshacerse de esto. Y es mejor hacer esto simplificando pero no agregando nuevas cosas misteriosas.

No queremos deshacernos de la capacidad de invocar directamente programas ejecutables.

La antigua invocación tampoco es directa a cmdline con su adivinación de si algo ya está citado. Además, dicha capacidad todavía se mantendría con -%.

Además, dicha capacidad todavía se mantendría con -%.

--% requiere una línea de comando predefinida, por lo que su utilidad es limitada.

@ yecril71pl

No queremos deshacernos de la capacidad de invocar directamente programas ejecutables.

Creo que @iSazonov significa deshacerse erróneo_ , es decir, corregir el error correctamente (sin optar por una sintaxis adicional), aunque significa romper las soluciones existentes. Correcto, @iSazonov?

@ Artoria2e5 :

La antigua invocación tampoco es directa a cmdline con su adivinación de si algo ya está citado

[_Actualización_: Leí mal la línea citada, pero espero que la información siga siendo de interés]

  • No tienes que _ adivinar_, y la regla es simple:

    • _Sólo si_ su nombre de comando o ruta está _comitado_ - '/foo bar/someutil '- y / o contiene _referencias variables (o expresiones) - $HOME/someutil - & es _requisito_.
  • Si bien puede considerar que esta necesidad es desafortunada, está en el corazón del lenguaje de PowerShell y es necesaria por la necesidad de poder distinguir sintácticamente entre los dos modos de análisis fundamentales, el modo de argumento y el modo de expresión.

    • Tenga en cuenta que el problema no es específico de llamar a _programas externos_; Los comandos nativos de PowerShell también requieren & para la invocación si se especifican mediante una referencia de cadena / variable entre comillas.
  • Si no quiere memorizar esta regla simple, la regla más simple es: _siempre_ use & , y estará bien - es algo menos conveniente que _no_ necesitar nada, pero no exactamente una dificultad (y más corto que --% , que es absolutamente la solución incorrecta - ver más abajo)

dicha capacidad aún se mantendría con -%.

[_Actualización_: Leí mal la línea citada, pero espero que la información siga siendo de interés.]

No, a pesar de la desafortunada combinación de # 13068 con _este_ problema, --% - la capacidad de llamar al _ shell nativo_, usando _su_, invariablemente _platform-specific_ syntax - no es de ninguna manera una solución para el problema en cuestión y la discusión como tal, aumenta la confusión .

--% es un caso de uso muy diferente y, como se propone actualmente, tiene graves limitaciones; Si hay algo para lo que _no_ vale la pena introducir un _operador_ (o cambiar el comportamiento de uno existente), es la capacidad de pasar una línea de comando textualmente al shell nativo (que, por supuesto, ya puede hacer usted mismo, con sh -c '...' en Unix, y cmd /c '...' , _pero solo de manera sólida si este problema se soluciona_; una implementación binaria de Invoke-NativeShell / ins , mientras que principalmente abstrae los detalles del shell de destino La sintaxis CLI evitaría el problema en cuestión (mediante el uso directo de System.Diagnostics.ProcessStartInfo.ArgumentList ) y, por lo tanto, se puede implementar de forma independiente).

@ yecril71pl

Tengo un escenario diferente: los nuevos usuarios prueban PowerShell, se dan cuenta de que falla misteriosamente, lo mueven a la Papelera de reciclaje y nunca regresan. Eso es lo que haría yo.

Podrías aclarar: ¿Temes que el comportamiento actual de powershell pueda llevar a esta desagradable experiencia de usuario, o temes que el cambio propuesto lleve a esa experiencia de usuario?

Porque en mi opinión, el comportamiento actual de powershell es mucho más probable que genere tal experiencia. Como se dijo anteriormente: el comportamiento actual de powershell debe considerarse un error.

¿Por qué un nuevo usuario, sin ningún conocimiento de que PowerShell está roto, emitirá comandos con argumentos retorcidos?

@ yecril71pl Solo para estar absolutamente seguro: Considera que la forma requerida actualmente para los argumentos está "retorcida", no la solución sugerida.

Considero que su pregunta proviene de su suposición de que soy un nerd incurable. Eso es correcto, pero he conservado la capacidad de imaginar lo que un tipo al azar normal consideraría retorcido.

@ mklement0
Creo que @ Artoria2e5 estaba hablando de convertir la matriz de argumentos a la única cadena lpCommandLine al decir

La antigua invocación tampoco es directa a cmdline con su adivinación de si algo ya está citado

Porque al llamar

echoargs.exe 'some"complicated_argument'

de hecho, debe adivinar más o menos si powershell agrega comillas alrededor de some"complicated_argument .

Ejemplo 1: en la mayoría de las versiones de PowerShell
echoarg.exe 'a\" b' y echoarg.exe '"a\" b"' se traducirán a
"C:\path\to\echoarg.exe" "a\" b" (versiones probadas 2.0; 4.0; 6.0.0-alpha.15; 7.0.1)
pero mi powershell predeterminado en Win10 (versión 5.1.18362.752) se traduce
echoarg.exe 'a\" b' a "C:\path\to\echoarg.exe" a\" b y
echoarg.exe '"a\" b"' a "C:\path\to\echoarg.exe" ""a\" b"" .

Ejemplo 2: traducciones de versiones anteriores de powershell
echoarg.exe 'a"b c"' a "C:\path\to\echoarg.exe" "a"b c"" (versiones probadas 2.0; 4.0;)
mientras que las versiones más nuevas se traducen
echoarg.exe 'a"b c"' a "C:\path\to\echoarg.exe" a"b c" (versiones probadas 5.1.18362.752; 6.0.0-alpha.15; 7.0.1).


Como el comportamiento claramente ya se cambió varias veces, no entiendo por qué no se puede cambiar una vez más para obtener el comportamiento esperado.

Ya veo, @TSlivede , gracias por aclarar y perdón por la mala interpretación, @ Artoria2e5.

En cuanto al problema real: estamos 100% de acuerdo; de hecho, nunca debería tener que pensar en lo que lpCommandLine termina usándose detrás de escena en Windows; si PowerShell hiciera lo correcto, nadie tendría que hacerlo (excepto en los casos extremos, pero no son culpa de PowerShell, y ahí es cuando --% (como se implementa actualmente) puede ayudar; con una solución adecuada, nunca habrá ser casos extremos en plataformas similares a Unix).

En cuanto a simplemente solucionar el problema correctamente: ciertamente tiene mi voto (pero las soluciones alternativas existentes se romperán).

TL; DR: La suposición de que podemos pasar de manera confiable cualquier valor como argumento a cualquier programa en el subsistema de Microsoft Windows NT es incorrecta , por lo que debemos dejar de fingir que ese es nuestro objetivo. Sin embargo, aún queda mucho por rescatar si consideramos el alcance del argumento.

Al invocar ejecutables nativos de Windows, debemos conservar las comillas originales. Ejemplo:

CMD /CSTART="WINDOW TITLE"

El sistema no puede encontrar el archivo VENTANA.

 { CMD /CSTART="WINDOW TITLE" }. Ast. EndBlock. Statements. PipelineElements. CommandElements[1]

StringConstantType
BareWord
Valor
/ CSTART = TÍTULO DE VENTANA
StaticType
System.String
Grado
/ CSTART = "TÍTULO DE VENTANA"
Padre
CMD / CSTART = "TÍTULO DE VENTANA"

Si tomáramos la extensión como plantilla, no perderíamos nada y podríamos llamar al ejecutable nativo como se esperaba. La solución alternativa de usar un argumento de cadena funciona aquí, pero no creo que sea estrictamente necesario desde el punto de vista técnico, siempre que se implemente el soporte adecuado dentro de PowerShell. Este enfoque funcionaría para todos los casos.

Las comillas dentro de las comillas presentan un problema insuperable porque hay herramientas que interpretan el escape de barra invertida ( TASKLIST "\\\"PROGRAM FILES" ) y herramientas que no lo hacen ( DIR "\""PROGRAM FILES" /B ) y herramientas que no molestan ( TITLE A " B ). Sin embargo, si tuviéramos que escapar, el escape estándar con barras invertidas envenena todas las herramientas comunes de administración de archivos porque simplemente no admiten comillas en absoluto y las barras invertidas dobles \\ significan algo completamente diferente para ellas (pruebe DIR "\\\"PROGRAM FILES" /B ), por lo que enviar un argumento con una comilla dentro debería ser un error de tiempo de ejecución. Pero no podemos lanzar un error porque no sabemos cuál es cuál. Si bien el uso del mecanismo de escape normal no debería causar ningún daño a los argumentos que no contienen comillas, no podemos estar seguros de que, cuando se aplica a argumentos que los contienen y se alimenta a una herramienta que no admite comillas como valores, lo haría Causaría necesariamente un error de aborto en lugar de un comportamiento inesperado, y el comportamiento inesperado sería realmente muy malo. Esta es una carga seria que colocamos sobre el usuario. Además, nunca podremos proporcionar herramientas de 'indiferencia' ( CMD /CECHO='A " B' ).

Tenga en cuenta que las variables de entorno no representan valores en CMD , representan fragmentos de código que se analizan a medida que se expanden las variables de entorno y no existe ninguna disposición para tratarlas de manera confiable como argumentos para otros comandos. CMD simplemente no opera sobre objetos de ningún tipo, ni siquiera cadenas, lo que parece ser la causa principal del presente acertijo.

TL; DR: La suposición de que podemos pasar de manera confiable cualquier valor como argumento a cualquier programa en el subsistema de Microsoft Windows NT es incorrecta, por lo que debemos dejar de fingir que ese es nuestro objetivo.

Ese debería ser el objetivo, aunque ¿no? No es problema de PowerShell si un programa no puede interpretar los argumentos que recibe.

Al invocar ejecutables nativos de Windows, debemos conservar las comillas originales. Ejemplo:

CMD /CSTART="WINDOW TITLE"

¿Está sugiriendo que llamar a un programa debería cambiar dinámicamente el idioma de PowerShell a lo que utilice el programa invocado? Escribiste ese ejemplo en PowerShell, lo que significa que debería ser equivalente a cualquiera de los siguientes

CMD "/CSTART=WINDOW TITLE"
CMD '/CSTART=WINDOW TITLE'
CMD /CSTART=WINDOW` TITLE

TL; DR: La suposición de que podemos pasar de manera confiable cualquier valor como argumento a cualquier programa en el subsistema de Microsoft Windows NT es incorrecta, por lo que debemos dejar de fingir que ese es nuestro objetivo.

Ese debería ser el objetivo, aunque ¿no? No es problema de PowerShell si un programa no puede interpretar los argumentos que recibe.

El programa CMD puede interpretar el argumento /CECHO=A " B pero PowerShell no puede pasarlo sin distorsionarlo.

Al invocar ejecutables nativos de Windows, debemos conservar las comillas originales. Ejemplo:

CMD /CSTART="WINDOW TITLE"

¿Está sugiriendo que llamar a un programa debería cambiar dinámicamente el idioma de PowerShell a lo que utilice el programa invocado? Escribiste ese ejemplo en PowerShell, lo que significa que debería ser equivalente a cualquiera de los siguientes

CMD "/CSTART=WINDOW TITLE"
CMD '/CSTART=WINDOW TITLE'
CMD /CSTART=WINDOW` TITLE

Intenté sugerir que, al interactuar con programas externos bajo el subsistema de Microsoft Windows NT, PowerShell tiene una miríada de formas de codificar los argumentos que son todos equivalentes a PowerShell pero no equivalentes al programa receptor. Ser franco y forzar el One True Way ™ de codificar argumentos, sin prestar atención a qué arreglo de citas usó realmente el usuario, no es útil, por decirlo suavemente.

@ yecril71pl Estoy realmente confundido por sus comentarios. ¿Qué propones exactamente aquí? Todos sus casos de uso están cubiertos por --% . Lo descartó antes diciendo

--% requiere una línea de comando predefinida, por lo que su utilidad es limitada.

Pero, de hecho, puede usar variables de entorno con --% . Prueba esto:

PS > $env:mytitle='WINDOW TITLE'
PS > cmd --% /CSTART="%mytitle%"

Entonces, ¿qué me estoy perdiendo?

Nos falta la sintaxis CMD /CSTART="$mytitle" , sin filtrar cosas a ENV: .

Como una idea terrible, tenemos la opción de reemplazar Environment.ExpandEnvironmentVariables con otra cosa. De todos modos, no existe una implementación nativa en Unix, y no creo que las cosas que procesa se vuelvan críticas para el rendimiento cuando se reescriban en C #.

Dado que los signos de igual no están permitidos en los nombres de var env de todos modos, podemos tener %=$a% mean $a . Esto no rompería nada existente y permitiría algunas extensiones muy flexibles (y posiblemente malas) como hacer que funcione como las cadenas de plantillas de JS. Demonios, también podemos definir %VARNAME=$var% como una especie de sintaxis alternativa.

En cuanto al infierno de documentación que esto causaría ... me disculpo.

  • _No_ tenemos un problema de análisis.

  • Lo que sí tenemos es un problema con _cómo PowerShell pasa los argumentos textuales y en cadena que han resultado de _su_ análisis a ejecutables externos (nativos )_:

    • En _Windows_, el problema es que la línea de comando para invocar el ejecutable externo con el que se construye detrás de escena _no_ se adhiere a la convención más utilizada para citar argumentos , como se detalla en el comando Parsing C ++ de la documentación del compilador de Microsoft C / C ++. Sección de

    • Lo que sucede actualmente ni siquiera es que se use una convención _diferente_: presumiblemente debido a un descuido, las líneas de comando que se construyen están situacionalmente _sintácticamente fundamentalmente rotas_, dependiendo de los detalles de los argumentos, relacionados con una combinación de comillas dobles y espacios incrustados así como argumentos de cadena vacía.

    • En última instancia, el problema es la arquitectura fundamental de la creación de procesos en Windows: se ve obligado a codificar los argumentos para pasarlos a un proceso _ como una línea de comando_ - una sola cadena que representa _todos_ los_ argumentos - en lugar de pasarlos como un _array_ de argumentos (que así es como lo hacen las plataformas tipo Unix). La necesidad de pasar una línea de comando requiere que se implementen _comillas y reglas de escape_, y en última instancia, depende de cada programa_ cómo interpretar la línea de comando que se le da. En efecto, esto equivale a forzar innecesariamente a los programas a ser una especie de mini-shell: están obligados a _repetir_ la tarea que el shell ya ha realizado, que es una tarea que debería ser competencia de un _shell solamente_ (como es el caso de Unix), es decir, analizar una línea de comando en argumentos individuales. En pocas palabras, esta es la anarquía que está transmitiendo argumentos en Windows .

    • En la práctica, la anarquía se mitiga con la mayoría de los programas que se adhieren a la convención antes mencionada, y es muy probable que los nuevos programas que se están desarrollando se adhieran a esa convención, principalmente porque los tiempos de ejecución ampliamente utilizados que sustentan las aplicaciones de consola implementan estas convenciones (como Microsoft C / C ++ / Tiempos de ejecución de .NET). Por tanto, la solución sensata es:

      • Haga que PowerShell se adhiera a esta convención al crear la línea de comandos entre bastidores.
      • Para los programas "deshonestos" que _no_ se adhieren a esta convención, que incluye en particular cmd.exe , archivos por lotes y utilidades de Microsoft como msiexec.exe y msdeploy.exe - proporcione un mecanismo para explícitamente controlar la línea de comando pasada al ejecutable de destino; esto es lo que proporciona --% , el símbolo de detener el análisis , aunque de forma bastante incómoda .
    • En _Unix_, el problema es que _ se está construyendo una línea de comandos en absoluto_; en cambio, la matriz de argumentos textuales debe pasarse _ como está_ , que ahora admite .NET Core (desde v2.1, a través de ProcessStartInfo.ArgumentList ; debería _siempre_ haber apoyado esto, dado que - sensiblemente - _no hay líneas de comando_, solo matrices de argumentos, cuando se crea un proceso en plataformas tipo Unix).

    • Una vez que usamos ProcessStartInfo.ArgumentList , todos los problemas en Unix desaparecen.

Solucionar estos problemas es de lo que se trata https://github.com/PowerShell/PowerShell-RFC/pull/90 de @TSlivede .

En https://github.com/PowerShell/PowerShell-RFC/pull/90#issuecomment -650242411, he propuesto una compensación automática adicional por la "picardía" de los archivos por lotes, dado su uso todavía muy extendido como puntos de entrada CLI para -software de perfil como Azure (CLI az se implementa como un archivo por lotes, az.cmd ).
De manera similar, deberíamos considerar hacer lo mismo para msiexec.exe y msdeploy.exe y quizás otras CLI de Microsoft "deshonestas" de alto perfil.


Acabo de publicar un módulo, Native , ( Install-Module Native -Scope CurrentUser ) que aborda todo lo anterior a través de su función ie (abreviatura de i nvoke (external) e xecutable; it es una implementación más completa de la función iep presentada anteriormente ).

También incluye ins ( Invoke-NativeShell ) , que se dirige a # 13068, y dbea ( Debug-ExecutableArguments ) para diagnosticar el paso de argumentos; consulte https: // github. com / PowerShell / PowerShell / issues / 13068 # issuecomment -671572939 para obtener más detalles.

En otras palabras: ie puede servir como un recurso provisional discreto mientras esperamos que se solucione este problema, simplemente prefijando las invocaciones con ie como comando:

En vez de:

# This command is currently broken, because the '{ "name": "foo" }' argument isn't properly passed.
curl.exe -u jdoe  'https://api.github.com/user/repos' -d '{ "name": "foo" }'

usarías lo siguiente:

# OK, thanks to `ie`
ie curl.exe -u jdoe  'https://api.github.com/user/repos' -d '{ "name": "foo" }'

En cuanto al ejemplo CMD /CSTART="WINDOW TITLE" (cuya forma más idiomática es cmd /c start "WINDOW TITLE" , que ya funciona):

En esencia, es el mismo problema que con los argumentos prop="<value with spaces>" para msiexec / msdeploy : PowerShell - justificadamente - transforma /CSTART="WINDOW TITLE" en "/CSTART=WINDOW TITLE" , que, sin embargo, rompe la invocación cmd.exe .

Hay dos formas de resolver esto:

  • Delegar en ins / Invoke-NativeShell (tenga en cuenta que el uso de cmd.exe /c está implícito):

    • ins 'START="WINDOW TITLE"'
    • Si usa una cadena _expandable_, puede incrustar valores de PowerShell en la cadena de comando.

      • $title = 'window title'; ins "START=`"$title`""

  • Alternativamente, use la implementación actual --% , pero tenga cuidado con sus limitaciones :

    • cmd --% /CSTART="WINDOW TITLE"
    • Como se discutió, una limitación problemática de --% es que la única forma de incrustar valores _PowerShell_ es usar un _aux. variable de entorno_ y haga referencia a ella con la sintaxis %...% :

      • $env:_title = 'window title'; cmd --% /CSTART="%_title%"

      • Para evitar esta limitación, --% siempre debería haberse implementado con un argumento de cadena _single_, por ejemplo,

        cmd --% '/CSTART="WINDOW TITLE"' o cmd --% "/CSTART=`"$title`"" - pero esto no se puede cambiar sin romper la compatibilidad con versiones anteriores, por lo que tendría que introducirse un símbolo _nuevo_ - personalmente, no veo la necesidad de uno.

  • están obligados a _re-realizar_ la tarea que el shell ya ha realizado

No creo que CMD.EXE divida las líneas de comando en argumentos, lo único que se necesita es averiguar a qué ejecutable llamar y el resto es solo la línea de comando escrita por el usuario (después de las sustituciones de variables de entorno, que se realizan sin tener en cuenta los límites de los argumentos). Por supuesto, los comandos de shell internos son una excepción aquí.

En esencia, es el mismo problema que con los argumentos prop="<value with spaces>" para msiexec / msdeploy

No soy un usuario seguro de ninguno de los dos, así que preferí mencionar algo con lo que estoy más familiarizado.

Para ser claros: lo siguiente no tiene ningún impacto en los puntos expuestos en mi comentario anterior.

No creo que CMD.EXE divida las líneas de comando en argumentos

  • Puede escaparse sin _explicit_ splitting al llamar _external ejecutables_ (comandos ejecutados por otro ejecutable en un proceso hijo), pero tiene que hacerlo para _batch files_.

  • Incluso cuando se llaman ejecutables externos, debe ser _consciente_ de los límites de los argumentos, para determinar si un metacarácter dado (por ejemplo, & ) tiene una función _sintáctica_ o si es parte de un argumento entre comillas dobles y, por lo tanto, debe ser tratado como literal:

:: OK - the "..." around & tells cmd.exe to use it verbatim
C:\>echoArgs.exe one "two & three"
Arg 0 is <one>
Arg 1 is <two & three>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" one "two & three"

Además, cmd.exe reconoce _embedded_ " caracteres. en "..." cadenas se reconocen si se escapan como "" :

:: OK - the "" is recognized as an escaped "
C:\>echoArgs.exe "3"" of rain & such."
Arg 0 is <3" of rain & such.>

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "3"" of rain & such."

Desafortunadamente, cmd.exe _only_ admite (solo para Windows) "" y no también el más utilizado \" (que es lo que usan _shells_ tipo POSIX en Unix _exclusivamente_ - nota: _shells_, no _programs_, porque los programas solo ven la matriz de argumentos textuales que resultan del análisis del shell)

Si bien la mayoría de las CLI en Windows admiten _tanto_ "" como \" , algunas _sólo_ entienden \" (en particular, Perl y Ruby), y luego estás en problemas:

:: !! BROKEN: cmd.exe misinterprets the & as *unquoted*, thinks it's the statement-sequencing operator, 
:: !! and tries to execute `such`:
C:\>echoArgs.exe "3\" of rain & such."
Arg 0 is <3" of rain >

Command line:
"C:\ProgramData\chocolatey\lib\echoargs\tools\EchoArgs.exe" "3\" of rain

'such."' is not recognized as an internal or external command,
operable program or batch file.

Por lo tanto:

  • Evite llamar directamente a cmd.exe , si es posible.

    • Llame a ejecutables externos directamente (una vez que se solucione este problema) o mediante ie (por ahora), utilizando la sintaxis de _PowerShell_.
  • Si tiene que llamar a cmd.exe , use ins / Invoke-NativeShell por simplicidad general y, específicamente, por lo fácil que es incrustar valores de expresión y variable de PowerShell en la línea de comando .

    • Una razón legítima para seguir llamando a cmd.exe directamente es compensar la falta de soporte de PowerShell para datos de bytes sin procesar en la canalización; consulte esta respuesta SO para ver un ejemplo.

Sé que voy a recibir muchas críticas aquí, y realmente aprecio la profundidad de la discusión que está ocurriendo, pero ... patos ... ¿alguien tiene un ejemplo de que algo de esto realmente importe en un escenario del mundo real? ?

En mi opinión, no estamos autorizados en PowerShell para resolver la "anarquía" que existe actualmente con el análisis de argumentos de Windows. Y por muchas de las mismas razones por las que no podemos resolver el problema, existe una buena razón por la que Windows y los compiladores de VC ++ han optado por no romper este comportamiento. Es desenfrenado, y solo vamos a crear una larga lista de problemas nuevos (y en gran parte indescifrables) si cambiamos las cosas.

Para aquellas utilidades que ya son multiplataforma y de uso intensivo entre Windows y Linux (por ejemplo, Docker, k8s, Git, etc.), no veo que este problema se manifieste en el mundo real.

Y para aquellas aplicaciones "fraudulentas" que hacen un mal trabajo: son en gran parte utilidades heredadas solo para Windows.

Estoy de acuerdo en que lo que ha descrito @ mklement0 es en gran medida una solución "correcta". No sé cómo llegar sin arruinar las cosas.

Los usos bastante básicos se rompen:

❯ git commit --allow-empty -m 'this is what we call a "commit message" which contains arbitrary text, often with punctuation'
error: pathspec 'message which contains arbitrary text, often with punctuation' did not match any file(s) known to git
❯ $a = 'this is what we call a "commit message" which contains arbitrary text, often with punctuation'
❯ git commit --allow-empty -m "$a"
error: pathspec 'message which contains arbitrary text, often with punctuation' did not match any file(s) known to git
❯ $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.0.3
PSEdition                      Core
GitCommitId                    7.0.3
OS                             Microsoft Windows 10.0.19042
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

git.exe es una aplicación que se comporta bien, por lo que la solución en PowerShell será sencilla, aunque requerirá que todos los scripters reviertan sus soluciones inteligentes. cmd.exe es más difícil de adaptar y requiere un enfoque mucho más considerado que puede resolver algunos problemas, pero probablemente no todos. Lo que es realmente espantoso, considerando que PowerShell comenzó como una herramienta de Windows NT. Entiendo esta pregunta como _si hay un escenario de la vida real en el que una utilidad heredada de mal comportamiento como cmd.exe será llamada desde PowerShell de una manera que cause problemas en la interfaz_. PowerShell intentó abordar este problema duplicando la mayor parte de la funcionalidad en cmd.exe , de modo que cmd.exe redundante. Esto también es posible para otras herramientas, por ejemplo, MSI se puede operar a través de ActiveX, aunque hacerlo requiere un conocimiento considerable. Entonces, ¿hay algo esencial que no esté cubierto?

@ PowerShell / powershell-Committee discutió esto. Apreciamos el ejemplo de git que muestra claramente un ejemplo convincente del mundo real. Acordamos que deberíamos tener una función experimental a principios de 7.2 para validar el impacto de tomar un cambio tan importante. Un ejemplo de prueba adicional muestra que incluso --% tiene un problema aunque debería haber sido descomprimido:

PS> testexe --% -echoargs 'a b c "d e f " g h'
Arg 0 is <'a>
Arg 1 is <b>
Arg 2 is <c>
Arg 3 is <d e f >
Arg 4 is <g>
Arg 5 is <h'>

Esto parece ser un problema en el enlazador de parámetros de comando nativo.

Sí, gracias @cspotcode. Ese ejemplo fue definitivamente un momento ajá para mí (especialmente teniendo en cuenta que en realidad lo acerté en el mundo real).

Todavía estoy preocupado por el aspecto del cambio radical, y creo que este es un candidato potencial para una función experimental que puede seguir siendo experimental en múltiples versiones de PowerShell, y eso no es algo que estamos seguros de que eventualmente lo lograremos.

También necesito profundizar más para comprender el aspecto de la lista de permitidos / "aplicación de rouge" de su RFC,

@joeyaiello y @ SteveL-MSFT, permítanme hacer una meta observación primero:

Si bien es bueno ver que el ejemplo de @cspotcode le dio un _ vistazo_ del problema, sus respuestas aún revelan una falta fundamental de comprensión y apreciación de la (magnitud del) problema subyacente (discutiré este punto en un comentario posterior) .

Este no es un juicio _personal_: reconozco plenamente lo difícil que debe ser estar muy delgado y tener que tomar decisiones sobre una amplia gama de temas en un corto período de tiempo.

Sin embargo, esto apunta a un problema _estructural_: para mí, parece que las decisiones las toma de forma rutinaria el comité @ PowerShell / powershell sobre la base de una comprensión superficial de los problemas que se están discutiendo, en detrimento de la comunidad en general.

Para mí, la respuesta del comité al tema que se está discutiendo aquí es el ejemplo más importante de este problema estructural hasta la fecha.

Por lo tanto, les pido que consideren esto:

¿Qué tal nombrar subcomités de temas específicos con los que el comité consulte y que tengan la comprensión requerida de los temas involucrados?

¿Puedes compartir el contenido de testexe SteveL-MSFT? ¡Solo quiero asegurarte!

@TSlivede resumió el problema adecuadamente en https://github.com/PowerShell/PowerShell/issues/13068#issuecomment -665125375:

PowerShell, por otro lado, afirma ser un shell (hasta que se resuelva # 1995, no diré que _es_ un shell)

Como se dijo muchas veces antes, un mandato central de un shell es llamar a ejecutables externos con argumentos.

PowerShell actualmente no cumple con este mandato, dado que los argumentos con comillas dobles incrustadas y argumentos de cadena vacía no se pasan correctamente.

Como se indicó anteriormente, esto puede haber sido un problema menor en los días de solo Windows, donde la falta de CLI externas capaces rara vez surgió este problema, pero estos días se han ido, y si PowerShell quiere establecerse como una plataforma cruzada creíble. shell, debe solucionar este problema.

El ejemplo de git @cspotcode es bueno; cualquier ejecutable al que desee pasar una cadena JSON, por ejemplo, curl , es otro:

# On Unix; on Windows, 
#   echoArgs.exe '{ "foo": "bar" }' 
# would show the same problem.
PS> /bin/echo '{ "foo": "bar" }'
{ foo: bar }  # !! Argument was incorrectly passed.

Dejando a un lado la compatibilidad con versiones anteriores:

  • En Unix, el problema se resuelve trivial y _completamente_ usando ProcessStartInfo.ArgumentList detrás de escena.

  • En Windows, el problema se resuelve de manera trivial y _ en su mayor parte_ usando ProcessStartInfo.ArgumentList detrás de escena.

    • Para casos extremos (CLI "deshonestas"), existe el ( mal implementado ) --%
    • Como _cortesía_, podemos compensar ciertos casos extremos bien conocidos para disminuir la necesidad de --% - ver más abajo.

Por lo tanto, _ lo antes posible_, se debe realizar una de las siguientes elecciones:

  • Reconozca la importancia de hacer que el paso de argumentos funcione correctamente y _ corríjalo a expensas de la compatibilidad con versiones anteriores_.

  • Si la compatibilidad con versiones anteriores es realmente primordial, proporcione un nuevo operador o una función como la función ie del módulo Native que soluciona el problema y publicítelo ampliamente como la única forma confiable de invocar ejecutables.

Proponer una característica _experimental_ para abordar una característica fundamental muy rota es totalmente inadecuado.


@ SteveL-MSFT

Incluso considerar el uso de --% como solución a este problema es fundamentalmente equivocado:

Es una característica _Sólo para Windows_ que solo conoce "..." comillas y %...% referencias de variables de entorno de estilo.

En Unix, el concepto de "detener el análisis" fundamentalmente no se aplica: _no hay línea de comando_ para pasar a los procesos secundarios, solo matrices de argumentos.

Por lo tanto, _alguien_ tiene que analizar la línea de comando en argumentos _antes_ de la invocación, que se delega implícitamente a la clase ProcessStartInfo , a través de su propiedad .Arguments , que en Unix usa las convenciones de _Windows_ para analizar una línea de comando - y por lo tanto reconoce "..." comillas (con escape de " incrustados como "" o \" ) únicamente.

--% es una característica exclusiva de Windows cuyo único propósito legítimo es llamar a CLI "no autorizadas".


@joeyaiello

que Windows y los compiladores de VC ++ han optado por no romper este comportamiento.

El compilador de VC ++ _impone una convención sensata y ampliamente observada_ para poner orden en la anarquía.

Es precisamente la adhesión a esta convención lo que se aboga aquí , que el uso de ProcessStartInfo.ArgumentList nos daría automáticamente.

_ Esto solo cubrirá la gran mayoría de las llamadas. Cubrir TODAS las llamadas es imposible y, de hecho, no es responsabilidad de PowerShell.

Como se indicó, para las CLI "deshonestas" que requieren formas no convencionales de cotización, se debe usar --% (o ins / Invoke-NativeShell del módulo Native ).

_Como cortesía_, podemos compensar automáticamente los escenarios "fraudulentos" conocidos, es decir, llamar a archivos por lotes y ciertas CLI de Microsoft de alto perfil:

  • El caso de archivo por lotes es genérico, y se explica y conceptualiza fácilmente (por ejemplo, pasar a&b como "a&b" , aunque no debería requerir una cita); evitará la necesidad de usar de --% con todas las CLI que usan archivos por lotes como punto de entrada (lo cual es bastante común), como az.cmd Azure

  • La alternativa a las excepciones de codificación rígida para CLI específicas, que ciertamente pueden resultar confusas, es detectar el siguiente _patrón_ en los argumentos que resultan del análisis de PowerShell - <word>=<value with spaces> - y, en lugar de pasar "<word>=<value with spaces>" , como sucede actualmente, pasar <word>="<value with spaces>" ; este último satisface las CLI "deshonestas", mientras que también es aceptado por las CLI que se adhieren a las convenciones; por ejemplo, echoArgs "foo=bar baz" finalmente ve el mismo primer argumento que echoArgs --% foo="bar baz"

@musm Puede encontrar el código fuente de TestExe en https://github.com/PowerShell/PowerShell/blob/master/test/tools/TestExe/TestExe.cs.

GitHub
¡PowerShell para todos los sistemas! Contribuya al desarrollo de PowerShell / PowerShell creando una cuenta en GitHub.

Creo que acomodar las excepciones por defecto solo conducirá a una situación similar a la actual, donde la gente necesita revertir la "utilidad" de PowerShell. Si hay excepciones, debería ser obvio que se están aplicando.

Tal vez algo como:

# Arguments passed correctly, without regard for the program's ability to handle them
& $program a "" 'c "d e" f'
# Try to pass the arguments intelligently based on the program being called
&[] $program a "" 'c "d e" f'
# Escape the arguments for a batch file, eg) " -> ""
&[bat] $program a "" 'c "d e" f'

Realmente estoy luchando por encontrar una sintaxis para esto que no esté rota. Al menos, esto tiene sentido si lo considera como convertir el programa, pero convertir la variable real que contiene el programa requeriría encerrar paréntesis.

Eso, además de permitir que las personas agreguen excepciones para cualquier comportamiento incorrecto que deseen, debería eliminar la necesidad de --% . La clase de excepción que registran tendría un método para determinar si es aplicable al programa (para la invocación inteligente), y un método de escape en el que simplemente le arroja el árbol de sintaxis abstracta de comandos y devuelve el argumento matriz / cadena.

  • en lugar de pasar "<word>=<value with spaces>" , como sucede actualmente, pasar <word>="<value with spaces>"

Las comillas utilizadas para construir la línea de comandos deben seguir la forma en que se cita la llamada en PowerShell. Por lo tanto:

  1. Una llamada que no contenga comillas dobles en su texto debe poner comillas dobles alrededor de todo el valor.
  2. Una llamada que contiene comillas dobles debe conservarlas tal como están escritas en el script _si es posible_.

En particular:
| argumento del guión | línea de comando |
| ----------------- | ---------------- |
| p=l of v | "p = l de v" |
| p=l` of` v | "p = l de v" |
| p="l of v" | p = "l de v" |
| p="l of v"'a s m' | p = "l de v" a "sm" |
| p="l of v"' s m' | p = "l de vsm" |

La última fila muestra un ejemplo en el que no será posible conservar las comillas dobles originales.

Sé que voy a recibir muchas críticas aquí, y realmente aprecio la profundidad de la discusión que está sucediendo, pero ..._ patos _... ¿alguien tiene un ejemplo de algo de esto que realmente importe en un escenario del mundo real? ?

Ya lo mencioné en este hilo hace un año (ahora está oculto ...) que solía tener muchos momentos WFT cuando usaba ripgrep en Powershell. No podía entender por qué no podía buscar cadenas entre comillas. Ignoró mis citas:

rg '"quoted"'

y en git bash no lo hizo.

Ahora obtengo menos estos momentos WTF porque, lamentablemente, encontré este largo problema de github y descubrí que pasar " a Powershel está totalmente roto. El ejemplo reciente de "git.exe" también es genial.

Para ser honesto, ahora ni siquiera me atrevo a usar Powershell para llamar al comando nativo cuando sé que podría estar pasando " en una cadena como parámetro. Sé que podría obtener un resultado incorrecto o un error.

Realmente, @ mklement0 lo resumió genial (esto debería estar grabado en piedra en alguna parte)

Como se dijo muchas veces antes, un mandato central de un shell es llamar a ejecutables externos con argumentos .
PowerShell actualmente no cumple con este mandato, dado que los argumentos con comillas dobles incrustadas y argumentos de cadena vacía no se pasan correctamente.
Como se mencionó anteriormente, esto puede haber sido un problema menor en los días de solo Windows, donde la falta de CLI externas capaces rara vez surgió este problema, pero estos días se han ido, y si PowerShell quiere establecerse como una plataforma cruzada creíble. shell, debe solucionar este problema .

Y sobre cambios importantes.
Recientemente, un compañero de trabajo me escribió que mi guión no funcionaba en su máquina. Lo estaba ejecutando solo en Powershel Core y él lo estaba ejecutando en Windows Powershell. Resulta que el archivo codificado Out-File -Encoding utf8 con "BOM" en Windows Powershell y sin BOM en Powershel Core. De alguna manera, este es un ejemplo inédito, pero muestra que ya hay cambios sutiles en Powershel y esto es bueno porque estamos eliminando las peculiaridades y el comportamiento intuitivo de un lenguaje que es famoso por ello. Sería genial si el equipo de Powershel fuera un poco más indulgente cuando se trata de cambios importantes ahora que tenemos Powershell multiplataforma que se envía fuera de Windows y que sabemos que Windows Powershell está en modo de "mantenimiento" y se podrá usar para siempre si realmente desea que ejecute algún script antiguo que se rompió en la versión más reciente de Powershell.

RE: ese último punto de cambios rotos, estoy totalmente de acuerdo. Hay _muchos_ cambios importantes que hemos tolerado por varias razones. Sin embargo, cada vez con más frecuencia parece ser el caso de que algunos cambios importantes simplemente se desaprueban por razones de preferencia y no se les da la debida consideración por su valor real.

Hay algunos cambios como este que _masivamente_ mejorarían la experiencia de shell general para cualquiera que necesite llegar fuera de PowerShell para hacer las cosas, lo que sucede todo el tiempo. Se ha acordado una y otra vez que el comportamiento actual es insostenible y ya está roto en gran medida para cualquier cosa menos para los usos más simples. Y, sin embargo, todavía nos enfrentamos a esta reticencia a los cambios importantes, incluso cuando hay decenas de cambios importantes ya aceptados, algunos de los cuales tienen un impacto similar.

Para aquellos que piden ejemplos, tómese un minuto para visitar Stack Overflow por una vez. Estoy seguro de que @ mklement0 tiene una letanía de ejemplos en los que se requiere ayuda de la comunidad para ayudar a explicar un cambio radical en las versiones más nuevas. Pasa todo el tiempo_. No tenemos excusa para no hacer cambios importantes y útiles.

Siempre que el equipo de MSFT repite lo mismo una y otra vez, podemos estar seguros de que saben más de lo que pueden decir públicamente. Debemos respetar su disciplina interior y no presionarlos. _Tal vez podamos encontrar un compromiso_. Espero tener tiempo hoy para describir una ruta alternativa con migración perezosa.

Lo reconozco, y es por eso que rara vez me esfuerzo por cuestionarlo.

Sin embargo, este es un proyecto de código abierto; si no hay visibilidad posible de esas decisiones, la gente, inevitablemente, acabará frustrada. No hay que echarle la culpa a ninguno de los lados de esa moneda, esa es solo la realidad de la situación aquí, en mi opinión. Así que sí, tener una ruta de migración puede aliviar un poco ese dolor, pero necesitamos políticas claras definidas sobre cómo tiene que funcionar eso hará que las cosas funcionen para la mayor cantidad de personas posible. Sin embargo, es difícil llegar a un compromiso cuando se carece de información.

Espero ver lo que tienes bajo la manga. 😉

@ mklement0 tienes toda la razón, y tanto es así que solo puedo responder a tu meta-punto ahora mismo. Desafortunadamente, en los casos en los que no podemos alcanzar el nivel de profundidad requerido para responder una pregunta como esta, el enfoque más seguro es aplazar o rechazar el cambio fundamental hasta que tengamos más tiempo para

Sin embargo, quiero hacer otro meta-punto sobre cambios importantes: nuestra telemetría implica que la mayoría de los usuarios de PowerShell 7 no están administrando sus propias versiones. Están ejecutando scripts automatizados en un entorno administrado que es cómodo, por ejemplo, actualizando a sus usuarios de 6.2 a 7.0 (vea el salto de 2 días en 6.2 usuarios convirtiéndose en 7.0 usuarios a partir del 8/3; este no es nuestro único punto de datos aquí, pero es conveniente en este momento que hace el punto). Para estos usuarios, un cambio radical que convierta un script que funciona perfectamente en un script que no funciona es inaceptable.

También le debo a la comunidad un blog sobre cómo pienso sobre el impacto de los cambios de ruptura: es decir, el intercambio de la prevalencia del uso existente y la gravedad de la ruptura con la facilidad de identificar y corregir la ruptura. Este es extremadamente frecuente en los scripts existentes, confuso para identificar y corregir, y el comportamiento de ruptura va desde el éxito total al fracaso total, de ahí mi extrema reticencia a hacer algo aquí.

Creo que es justo decir que no vamos a hacer nada aquí en 7.1, pero definitivamente estoy abierto a hacer de esto una prioridad de investigación para 7.2 (es decir, dedicamos más tiempo que nuestro Comité a discutir esto).

¿Qué tal nombrar subcomités de temas específicos con los que el comité consulte y que tengan la comprensión requerida de los temas involucrados?

Estamos trabajando en esto. Sé que he dicho eso antes, pero estamos muy cerca (como en, estoy elaborando el blog y probablemente verán aparecer algunas etiquetas nuevas pronto con las que jugaremos).

Agradezco la paciencia de todos y reconozco que es molesto obtener una respuesta concisa del Comité cada dos semanas cuando la gente está poniendo una inmensa cantidad de pensamiento y consideración en la discusión. Sé que parece que eso significa que no estamos pensando profundamente en las cosas, pero creo que simplemente no estamos expresando la profundidad de nuestras discusiones con tanto detalle como la gente aquí. En mi propio trabajo pendiente, tengo un conjunto completo de temas de blog como el cambio radical sobre cómo pienso acerca de la toma de decisiones dentro del Comité, pero nunca tuve la oportunidad de sentarme y expresarlas. Pero puedo ver aquí que tal vez la gente encontraría mucho valor en eso.

Espero no haberme descarrilado demasiado en esta discusión. No quiero que este problema se convierta por completo en un metaproblema sobre la gestión del proyecto, pero sí quería abordar algunas de las comprensibles frustraciones que veo aquí. Le imploro a cualquiera que quiera hablar sobre esto con más detalle con nosotros que se una a la Llamada de la aquí y me aseguraré de abordarlos en la llamada).

Solo una nota rápida sobre el meta-punto: agradezco la respuesta reflexiva, @joeyaiello.

En cuanto a la gravedad del cambio radical: las siguientes declaraciones parecen estar en desacuerdo:

¿Alguien tiene un ejemplo de algo de esto que realmente importe en un escenario del mundo real?

vs.

Este es extremadamente frecuente en los scripts existentes, confuso para identificar y corregir

Si ya es frecuente, la incomodidad y la oscuridad de las soluciones necesarias son una razón más para solucionarlo finalmente, especialmente dado que deberíamos esperar que aumente el número de casos.

Me doy cuenta de que todas las soluciones alternativas existentes se romperán.

Si evitar eso es primordial, este enfoque sugerido anteriormente es el camino a seguir:

proporcione un _nuevo operador o una función_ como la función ie del módulo Native que solucione el problema y publicítelo ampliamente como la única forma confiable de invocar ejecutables externos.

Una _función_ como ie permitiría a las personas optar por el comportamiento correcto con _ mínimo alboroto_, _ como una solución provisional_, sin sobrecargar el _lenguaje_ con un nuevo elemento sintáctico (un operador), cuya única razón de ser sería para solucionar un error heredado que se considera demasiado grave para solucionarlo:

  • El recurso provisional proporcionaría acceso _ oficialmente autorizado_ al comportamiento correcto (sin depender de las características _experimentales_).
  • Mientras sea necesario el recurso provisional, deberá ser ampliamente publicitado y debidamente documentado.

Si / cuando se corrige el comportamiento predeterminado:

  • la función se puede modificar para ceder a ella, para no romper el código que la usa.
  • se puede escribir nuevo código sin necesidad de la función.

Una función como ie permitiría a las personas optar por el comportamiento correcto con un mínimo de alboroto, como una solución provisional

Podemos simplificar la adopción por medio de # 13428. Podemos inyectar esto con las investigaciones de @ mklement0 en Engine de forma transparente.

@Dabombber

Creo que acomodar las excepciones por defecto solo conducirá a una situación similar a la actual, donde la gente necesita revertir la "utilidad" de PowerShell. Si hay excepciones, debería ser obvio que se están aplicando.

Las adaptaciones que propongo hacen que la gran mayoría de las llamadas "simplemente funcionen": son abstracciones útiles de la anarquía de la línea de comandos de Windows de las que debemos hacer todo lo posible para proteger a los usuarios.

La suposición justificada es que este blindaje es un esfuerzo único para los ejecutables _legacy_, y que los recién creados se adherirán a las convenciones de Microsoft C / C ++.

Sin embargo, es imposible hacer eso en _todos_ los casos; para aquellos que no se pueden acomodar automáticamente, hay --% .

Personalmente, no quiero tener que pensar si una determinada utilidad foo se implementa como foo.bat o foo.cmd , o si requiere foo="bar none" argumentos específicamente, sin aceptar también "foo=bar none" , que para los ejecutables que cumplen con las convenciones son equivalentes.

Y ciertamente no quiero una forma de sintaxis separada para varias excepciones, como &[bat]
En cambio, --% es la herramienta general (solo para Windows) para formular la línea de comando exactamente como desea que se transmita, independientemente de los requisitos específicos y no convencionales del programa de destino.

Específicamente, las adaptaciones propuestas son las siguientes:

Nota:

  • Como se indicó, se requieren _en Windows_ solamente; en Unix, delegar en ProcessStartInfo.ArgumentList es suficiente para resolver todos los problemas.

  • Al menos en un nivel alto, estas adaptaciones son fáciles de conceptualizar y documentar.

  • Tenga en cuenta que se aplicarán _después_ del análisis habitual de PowerShell, en el paso de traducción al proceso de la línea de comandos (solo para Windows). Es decir, el propio análisis de parámetros de PowerShell no estará involucrado, y _no debería_ estarlo , @ yecril71pl.

  • Cualquier caso realmente exótico no cubierto por estas adaptaciones tendría que ser manejado por los propios usuarios, con
    --% - o, con el módulo Native instalado, con ins / Invoke-NativeShell , lo que facilita la inserción de valores de expresión y variable de PowerShell en la llamada.

    • El comando dbea ( Debug-ExecutableArguments ) del módulo Native puede ayudar a diagnosticar y comprender qué línea de comando de proceso se utiliza en última instancia; consulte la sección de ejemplo a continuación.

Lista de adaptaciones:

  • Para los archivos por az.cmd para Azure).

    • Las comillas dobles incrustadas, si las hay, se escapan como "" (en lugar de \" ) en todos los argumentos.
    • Cualquier argumento que no contenga cmd.exe metacaracteres como & se incluye entre comillas dobles (mientras que PowerShell de forma predeterminada solo incluye argumentos con espacios entre comillas dobles); por ejemplo, un argumento literal visto por PowerShell como a&b se coloca como "a&b" en la línea de comandos que se pasa a un archivo por lotes.
  • Para CLI de alto perfil como msiexec.exe / msdeploy.exe y cmdkey.exe (sin excepciones de codificación rígida para ellos):

    • Cualquier invocación que contenga al menos un argumento de las siguientes formas desencadena el comportamiento que se describe a continuación; <word> puede estar compuesto por letras, dígitos y guiones bajos:

      • <word>=<value with spaces>
      • /<word>:<value with spaces>
      • -<word>:<value with spaces>
    • Si tal argumento está presente:

      • Las comillas dobles incrustadas, si las hay, se escapan como "" (en lugar de \" ) en todos los argumentos. - consulte https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167 para saber por qué _no_ deberíamos hacer esto; esto significa que en el raro caso de que <value with spaces> tenga _embebidos_ " caracteres., --% debe usarse; p.ej,
        msiexec ... --% PROP="Nat ""King"" Cole"
      • Solo la parte <value with spaces> está encerrada entre comillas dobles, no el argumento en su totalidad (este último es lo que PowerShell, justificadamente, hace de forma predeterminada); por ejemplo, un argumento literal visto por PowerShell como foo=bar none se coloca como foo="bar none" en la línea de comando del proceso (en lugar de como "foo=bar none" ).
    • Nota:

      • Si el ejecutable de destino _no_ es una CLI de estilo msiexec , no se hace daño, porque las CLI que se adhieren a las convenciones consideran razonablemente <word>="<value with spaces>" y "<word>=<value with spaces>" _equivalente_, ambos representando textualmente <word>=<value with spaces> .

      • De manera similar, la gran mayoría de los ejecutables aceptan "" indistintamente con \" para escapar de los caracteres " incrustados, con la notable excepción de la CLI, Ruby y Perl de PowerShell (_no_ funcionan la adaptación vale la pena al menos si se llama a la CLI de PowerShell, pero creo que codificar Ruby y Perl también tendría sentido). https://github.com/PowerShell/PowerShell/pull/13482#issuecomment -677813167 muestra que todas las aplicaciones que usan la función CommandLineToArgvW WinAPI _no_ admiten "" -escaping.

Todos los demás casos en Windows también se pueden manejar con ProcessStartInfo.ArgumentList , que aplica implícitamente la convención de Microsoft C / C ++ (lo que significa en particular \" por " -escaping).


La función ie de la versión actual ( 1.0.7 ) del módulo Native implementa estas adaptaciones (además de corregir el análisis de argumentos roto) , para las versiones 3 y posteriores de PowerShell ( Install-Module Native ).

Los invito a usted y a todos los aquí presentes a ponerlo a prueba para probar la afirmación de que "simplemente funciona" para la gran mayoría de llamadas ejecutables externas.

Limitaciones actualmente inevitables:

  • Nota: Estas limitaciones técnicas provienen de que ie se implementa como una _ función_ (una solución adecuada en el motor en sí _no_ tendría estos problemas):

    • Si bien $LASTEXITCODE está configurado correctamente en el código de salida del proceso, $? siempre termina $true - el código de usuario actualmente no puede establecer $? explícitamente, aunque se agrega esta capacidad ha recibido luz verde; consulte https://github.com/PowerShell/PowerShell/issues/10917#issuecomment -550550490. Desafortunadamente, esto significa que actualmente no puede usar ie significativa con && y || , los && , los operadores de cadena de canalización .
      Sin embargo, si se desea _abortar_ una secuencia de comandos al detectar un código de salida distinto de cero, se puede utilizar la función contenedora iee .

    • -- como argumento es invariablemente "devorado" por el enlazador de parámetros de PowerShell; simplemente páselo _dos veces_ para pasar -- al ejecutable de destino ( foo -- -- lugar de foo -- ).

    • Un token sin comillas con , debe estar entre comillas para que no se interprete como una matriz y se pase como múltiples argumentos; por ejemplo, pase 'a,b' lugar de a,b ; de manera similar, pase -foo:bar (algo que parece un argumento de PowerShell con nombre) como '-foo:bar' (esto no debería ser necesario, pero se debe a un error: # 6360); de manera similar '-foo.bar must be passed as ' -foo.bar'` (otro error, que también afecta a las llamadas directas a ejecutables externos: # 6291)

  • Espero que la función funcione de manera sólida en PowerShell _Core_. Debido a los cambios en _Windows PowerShell_ a lo largo del tiempo, puede haber casos extremos que no se manejan correctamente, aunque solo conozco dos:

    • La acomodación de cotización parcial para CLI de estilo msiexec no se puede aplicar en la versión 3 y 4, porque estas versiones envuelven todo el argumento en un conjunto adicional de comillas dobles; Sin embargo, funciona en v5.1.

    • "" -escaping se usa de forma predeterminada, para solucionar problemas, pero en los casos en que \" es necesario (PowerShell CLI, Perl, Ruby), se utiliza un token como 3" of snow se pasó por error como argumentos _3_, porque todas las versiones de Windows PowerShell no incluyen dicho argumento entre comillas dobles; esto parece suceder para argumentos con caracteres " no iniciales que no están precedidos por un carácter de espacio.


Ejemplos, con salida de PowerShell Core 7.1.0-preview.5 en Windows 10:

Nota: La función dbea ( Debug-ExecutableArguments ) se utiliza para ilustrar cómo los archivos ejecutables / por lotes externos recibirían los argumentos.

Paso de argumento actual, roto:

  • Llamar a una aplicación compatible con la convención (una aplicación de consola .NET utilizada por dbea detrás de escena de forma predeterminada):
# Note the missing 2nd argument and the effective loss of embedded double quotes,
# due to the embedded " chars. not having been escaped.
PS> dbea -- 'a&b' '' '{ "foo": "bar" }'

2 argument(s) received (enclosed in <...> for delineation):

  <a&b>
  <{ foo: bar }>

Command line (helper executable omitted):

  a&b  "{ "foo": "bar" }"
  • Llamar a un archivo por lotes:

Tenga en cuenta el uso de -UseBatchFile para hacer que dbea pase los argumentos a un archivo auxiliar _batch file_ en su lugar.

# Note that only *part of the first argument* is passed and that the `&` is interpreted as cmd.exe's
# statement separator, causing `b` to be run as a command (which fails).
PS> dbea -UseBatchFile -- 'a&b' '' '{ "foo": "bar" }'

1 argument(s) received (enclosed in <...> for delineation):

  <a>

'b' is not recognized as an internal or external command,
operable program or batch file.
  • Llamar a msiexec -style CLI, cmdkey.exe :
# The call fails, because `cmdkey.exe` requires the password argument to 
# to be quoted exactly as `/password:"bar none"` (double-quoting of the option value only), 
# whereas PowerShell - justifiably - passes `"/password:bar none"` (double-quoting of the whole argument).
PS> cmdkey.exe /generic:foo /user:foo /password:'bar none'

The command line parameters are incorrect.

Solucionando el problema con ie :

Tenga en cuenta el uso de -ie en las llamadas dbea , que provoca el uso de ie para las invocaciones.

  • Llamar a una aplicación compatible con la convención (una aplicación de consola .NET utilizada por dbea detrás de escena de forma predeterminada):
# OK
# Note that the empty 2nd argument is correctly passed, and that \" is used for embedded "-escaping.
PS> dbea -ie -- 'a&b' '' '{ "foo": "bar" }'

3 argument(s) received (enclosed in <...> for delineation):

  <a&b>
  <>
  <{ "foo": "bar" }>

Command line (helper executable omitted):

  a&b "" "{ \"foo\": \"bar\" }"
  • Llamar a un archivo por lotes:
# OK
# - `a&b` was enclosed in "...", due to the presence of metacharacter `&`
# - "" is used for escaping of embedded " chars.
# Note that `echo %1`, for instance, prints the argument exactly as passed on the command line, including quoting.
# `echo %~1` strips the surrounding double quotes, but embedded escaped ones still print as "".
# However, if you pass these arguments (`%*`) through to convention-compliant CLIs, they are parsed correctly.
PS> dbea -ie -UseBatchFile -- 'a&b' '' '{ "foo": "bar" }'

3 argument(s) received (enclosed in <...> for delineation):

  <"a&b">
  <"">
  <"{ ""foo"": ""bar"" }">
  • Llamar a una CLI de estilo msiexec , cmdkey.exe :
# The call now succeeds, because `ie` ensure the value-only double-quoting that cmdkey.exe requires.
# (Use `cmdkey /del:foo` to remove the credentials again.)
PS> ie cmdkey.exe /generic:foo /user:foo /password:'bar none'

CMDKEY: Credential added successfully.

Para mostrar que se aplicó la doble cotización de solo valor en la línea de comando real, a través de dbea :

PS> dbea -ie -- cmdkey.exe /generic:foo /user:foo /password:'bar none'

  <cmdkey.exe>
  </generic:foo>
  </user:foo>
  </password:bar none>

Command line (helper executable omitted):

  cmdkey.exe /generic:foo /user:foo /password:"bar none"

El siguiente código provoca una pérdida de datos:

{ PARAM($A) $A } | OUT-FILE A.PS1
PWSH A.PS1 -A:(1,2)

1

@JamesWTruher tiene una solución propuesta y está validando si aborda las preocupaciones planteadas en este problema

¿Existe una solicitud de extracción de esa solución propuesta? Sería bueno si pudiéramos comentar sobre las relaciones públicas. Porque arreglar esto nunca fue en mi opinión la parte complicada. La parte complicada fue cómo manejar la compatibilidad hacia atrás. Y sería bueno, si pudiéramos ver cómo la solución propuesta maneja eso ...

Es bueno escuchar eso, @ SteveL-MSFT, ¿una solución para todos los problemas que se analizan aquí? ¿Para v7.1, después de todo? Y segundo la solicitud de @TSlivede .

@ yecril71pl , ese es un buen hallazgo, aunque este realmente se relaciona con el propio análisis de PowerShell (que tiene _algunos_ caracteres especiales para ejecutables externos), no con cómo se construye la línea de comandos nativa _después_ del análisis (que es donde vienen los problemas discutidos anteriormente desde).

Una reproducción más sucinta del problema, en Unix:

PS> printf '<%s>\n' -a:(1,2,3)
<-a:1>
<2>
<3>

Es decir, solo el elemento de matriz _first_ se adjuntó directamente a -a: , los demás se pasaron como argumentos separados.

Hay problemas relacionados con argumentos que se parecen a los parámetros de PowerShell, pero que no lo son:

Existe un problema relacionado que solo afecta a las llamadas a los comandos _PowerShell_ que usan $args / @args : # 6360

  • & { $args.Count; $args } -foo:bar rendimientos 2, '-foo:', 'bar'

También hay # 6291, que afecta tanto a los comandos de PowerShell como a los ejecutables externos (tenga en cuenta el . ).

  • & { $args.Count; $args } -foo.bar rendimientos 2, '-foo', '.bar'

Una cosa a tener en cuenta es que (...) como parte de una palabra básica normalmente da como resultado que la salida de (...) como un todo se convierta en un argumento _separado_, por lo que el hecho de que el primer elemento _ esté_ adjunto en el printf comando
& { $args.Count; $args.ForEach({ "$_" }) } foo('bar', 'baz') produce 2, 'foo', 'bar baz' , siendo el segundo argumento la cadena de caracteres de la matriz 'bar', 'baz' .

Cuando PowerShell tiene que pasar -A:(1,2) para un ejecutable externo, se da cuenta de que -A: es una cadena y (1,2) es una matriz que debe calcularse como '1 2'. PowerShell intenta preservar la sintaxis original de la invocación, por lo que, al juntarlo todo, obtenemos '-A: 1 2', mientras que el resultado correcto sería '-A: "1 2"'. Me parece una omisión trivial en el código de clasificación.

No diría que el problema específico de @ yecril71pl está relacionado con el análisis sintáctico (aunque estoy de acuerdo, no tiene nada que ver con el problema de "conversión de matriz en línea de comandos", que se analiza en este número).

Cuando PowerShell tiene que pasar -A: (1,2) para un ejecutable externo, se da cuenta de que -A: es una cadena y (1,2) es una matriz

Casi: -A: es un parámetro con nombre y la matriz es el valor de ese parámetro (Para probar eso: elimine el - al frente y verá que se cita de manera diferente). Pero el problema no es que la matriz se convierta incorrectamente en una cadena; el problema es que para los ejecutables nativos los argumentos están (casi) siempre divididos, incluso cuando se usa $ y no @ e incluso si la matriz se origina en una expresión como (1,2) .

Pruebe, por ejemplo, printf '<%s>\n' -a:('a b',2) : Como la cadena a b contiene un espacio, se cita correctamente, pero como 2 está en el siguiente elemento de la matriz y la matriz está salpicada, 2 no forma parte del primer argumento.


La magia ocurre en NativeCommandParameterBinder.cs

En la línea 170, powershell intenta obtener un enumerador para el valor actual del argumento.

IEnumerator list = LanguagePrimitives.GetEnumerator(obj);

Si list no es null , powershell agrega cada elemento de la lista (posiblemente entre comillas si contiene espacios) a lpCommandLine.

Los elementos están separados por espacios ( línea 449 ) por defecto. La única excepción es si la matriz era un literal
(como en printf '<%s>\n' -a:1,2 ).
Luego, powershell intenta usar el mismo separador en lpCommandLine, que se usó en la línea de script.

Espero un PR cuando esté listo. Si llega a 7.1, lo aceptaremos; de lo contrario, será 7.2. La compatibilidad con versiones anteriores es algo que está abordando. Quizás lo que ayudaría es algo de ayuda para escribir pruebas de Pester (usando testexe -echoargs que se puede construir usando publish-pstesttools de build.psm1).

Espero un PR cuando esté listo

Eso es exactamente lo que quería evitar: muestre el código que no está listo (marque PR como trabajo en progreso).

O al menos comentar lo que quiere hacer.

Sería bueno si pudiéramos discutir cómo quiere manejar la compatibilidad con versiones anteriores.

@TSlivede , tenga en cuenta que dado que se llama a PowerShell _CLI_ - un ejecutable externo - -A:(1,2) se analiza _antes_ sabiendo que este token eventualmente se vinculará al parámetro _named_ -A - que tal parámetro _ eventualmente_ entra en juego es incidental al problema.

@ yecril71pl :

Se da cuenta de que -A: es una cadena

No, se escribe en mayúsculas y minúsculas durante el análisis, porque se parece a un parámetro de PowerShell.

Este caso especial ocurre para las llamadas a los comandos de PowerShell que usan $args también (en lugar de tener parámetros declarados reales que están vinculados), pero ocurre _diferente_ para los ejecutables externos (lo que normalmente es un argumento separado permanece adjunto, pero en el caso de una colección solo su _primer_ elemento).

De hecho, puede optar por no participar en esta carcasa especial si pasa -- antemano, pero, por supuesto, eso también pasará -- , que solo se elimina para las llamadas a los comandos _PowerShell_:

PS> printf '<%s>\n' -- -a:(1,2,3)
<-->   # !! not removed
<-a:>
<1>    # array elements are *all* now passed as indiv. arguments, because (...) output is separate (splatted) argument
<2>
<3>

Si el argumento _no_ parece un parámetro de PowerShell, el comportamiento habitual (la salida de (...) convierte en un argumento separado) se activa incluso para los ejecutables externos (con el comportamiento habitual de un _array_ salpicado, es decir, convertido en argumentos individuales en el caso ejecutable externo).

# Note: No "-" before "a:" -> output from `(...)` becomes separate argument(s)
PS> printf '<%s>\n' a:(1,2,3)
<a:>
<1>
<2>
<3>

Aplicar este comportamiento _consistentemente_ tendría sentido - una expresión (...) como parte de una palabra desnuda debería _siempre_ convertirse en un argumento separado - ver # 13488.

Para pasar un solo argumento '-A:1 2 3' , con la matriz _stringified_, use una (n implícita) _cadena expandible_, en cuyo caso necesita $(...) lugar de (...) _and_ - sorprendentemente - actualmente también "..." :

PS> printf '<%s>\n' "-a:$(1,2,3)"  # quotes shouldn't be needed; `-a:"$(1,2,3)"` would work too.
<a:1 2 3> # SINGLE argument with stringified array.

También _no_ debería_ necesitar "..." en este caso - nuevamente es necesario debido a las anomalías relacionadas con los tokens que _ parecen _ parámetros similares (que en general se aplican a _tanto_ PowerShell como a las llamadas ejecutables externas - ver # 13489); si no es así, no es necesario citar:

# Anomaly due to looking like a parameter: $(...) output becomes separate argument
PS> Write-Output -- -a:$(1,2,3)
-a:
1
2
3

# Otherwise (note the absence of "-"): no quoting needed; treated implicitly like 
# "a:$(1,2,3)"
PS> Write-Output -- a:$(1,2,3)
a:1 2 3  # SINGLE argument with stringified array.

El mundo de los tokens compuestos en modo argumento es complejo, con varias inconsistencias - vea el # 6467.

@ SteveL-MSFT

En su forma actual, testexe -echoArgs solo imprime los argumentos individuales que el ejecutable de .NET Core analizó desde la línea de comandos sin procesar (en Windows), no la línea de comandos sin procesar en sí.

Por lo tanto, no puede probar adaptaciones con citas selectivas para archivos por lotes y CLI de estilo msiexec , asumiendo que se implementarán dichas adaptaciones, lo cual recomiendo encarecidamente; por ejemplo, no podrá verificar que PROP='foo bar' se haya pasado como PROP="foo bar" , con comillas dobles alrededor de la parte del valor.

Sin embargo, para imprimir la línea de comando sin formato, testexe no debe ser un ejecutable de .NET _Core_, porque .NET Core _recrea una línea de comando hipotética_ que siempre usa \" -escaping para " incrustados "" , y generalmente no refleja fielmente qué argumentos se citaron dos veces y cuáles no; para obtener información de fondo, consulte https://github.com/ dotnet / runtime / issues / 11305 # issuecomment -674554010.

Solo un ejecutable compilado por .NET _Framework_ muestra la línea de comando verdadera en Environment.CommandLine , por lo que testexe tendría que compilarse de esa manera (y modificarse para imprimir (opcionalmente) la línea de comando sin procesar).

Para probar las adaptaciones para archivos por lotes, se necesita un archivo _batch_ de prueba por separado, para verificar que 'a&b' se pasa como "a&b" y 'a"b' como "a""b" , para ejemplo.

La compilación de

Alternativamente, ¿podemos confiar en un script bash simple para Linux / macOS para emitir los argumentos?

#!/bin/bash
for i; do
   echo $i
done

Y en Windows algo similar con un archivo por lotes.

¿Qué tal usar node con un script .js?

console.log(process.execArgv.join('\n') o cualquier cadena que te maneje
¿Quieres hacer para que la salida se vea bien?

@cspotcode , para obtener la línea de comando sin

@ SteveL-MSFT:

En Windows , puede delegar la compilación a _Windows PowerShell_ a través de su CLI, que es lo que hago en dbea ; aquí hay un ejemplo simple que produce un ejecutable .NET _Framework_ que se hace eco de la línea de comando sin formato (solo), ./rawcmdline.exe :

powershell.exe -noprofile -args ./rawcmdline.exe -c {

  param([string] $exePath)

  Add-Type -ErrorAction Stop -OutputType ConsoleApplication -OutputAssembly $exePath -TypeDefinition @'
using System;
static class ConsoleApp {
  static void Main(string[] args) {
    Console.WriteLine(Environment.CommandLine);
  }
}
'@

}

Llamada de muestra:

PS> ./rawcmdline.exe --% "a&b" PROP="foo bar"
"C:\Users\jdoe\rawcmdline.exe"  "a&b" PROP="foo bar"

En cuanto a un _archivo por lotes_ que repite sus argumentos, dbea también crea uno a pedido .

En Unix , un script de shell simple, como se muestra en su comentario, es de hecho suficiente, e incluso puede

@ PowerShell / powershell-Committee discutió esto hoy, le pedimos a @JamesWTruher que actualice su PR para incluir también como parte de su función experimental para omitir el paso en el procesador de comandos nativo que reconstruye la matriz de argumentos en una cadena y simplemente pasa eso a los nuevos argumentos de matriz en ProcessStartInfo (hay un poco de código para asegurarse de que los nombres y valores de los parámetros coincidan adecuadamente). Además, aceptamos que es posible que necesitemos una lista de permisos para comandos conocidos de casos especiales que aún fallan con el cambio propuesto y eso es algo que se puede agregar más adelante.

Para aquellos que quizás no lo hayan notado: el PR se ha publicado (como un WIP) y ya se está discutiendo: https://github.com/PowerShell/PowerShell/pull/13482

PD, @ SteveL-MSFT, con respecto a la obtención de la línea de comandos sin procesar en Windows: por supuesto, una alternativa a delegar la compilación a Windows PowerShell / .NET _Framework_ es mejorar la aplicación de consola .NET _Core_ existente para hacer un (condicional de plataforma) P / Invoque la función GetCommandLine() WinAPI, como se muestra a continuación.

using System;
using System.Runtime.InteropServices;

namespace demo
{
  static class ConsoleApp
  {
    [DllImport("kernel32.dll")]
    private static extern System.IntPtr GetCommandLineW();

    static void Main(string[] args)
    {
      Console.WriteLine("\n{0} argument(s) received (enclosed in <...> for delineation):\n", args.Length);
      for (int i = 0; i < args.Length; ++i)
      {
        Console.WriteLine("  <{0}>", args[i]);
      }

      // Windows only: print the raw command line.
      if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
      {
        Console.WriteLine("\nCommand line:\n\n  {0}\n", Marshal.PtrToStringUni(GetCommandLineW()));
      }

    }

  }

}

@ SteveL-MSFT

La compatibilidad con versiones anteriores es algo que está abordando.

Creo que está implícito en su aclaración posterior que ProcessStartInfo.ArgumentList (una _ colección_ de argumentos _verbatim_ para usarse tal cual en Unix y traducidos a una línea de comandos compatible con la convención de MS C / C ++ en Windows por .NET Core ) debería usarse, pero permítanme decirlo explícitamente:

  • Arreglar este problema adecuadamente, de una vez por todas, _ excluye cualquier concesión a la compatibilidad con versiones anteriores_.

  • El PR de @JamesWTruher está en el camino correcto al momento de escribir este artículo, el único problema parece ser que los argumentos vacíos aún no se pasan .

    • Una vez que se soluciona, la solución está completa en _Unix_, pero carece de las adaptaciones importantes para las CLI en _Windows_ (ver más abajo).

es posible que necesitemos una lista de permisos para comandos conocidos de casos especiales que aún fallan con el cambio propuesto y eso es algo que se puede agregar más adelante.

Le insto a que no posponga esto para más tarde .

En lugar de una _allowlist_ (mayúsculas y minúsculas especiales para ejecutables específicos), las reglas generales simples pueden regir las adaptaciones , refinadas de las anteriores después de una discusión más con @TSlivede .

Estas adaptaciones, que son _necesarias solo en Windows_:

Específicamente, estos son:

  • Al igual que en Unix, ProcessStartInfo.ArgumentList se usa de forma predeterminada, excepto si se cumple _una o ambas_ de las siguientes condiciones, _en cuyo caso la línea de comando del proceso debe construirse manualmente_ (y asignarse a ProcessStartInfo.Arguments como actualmente):

    • Un archivo por lotes ( .cmd , .bat ) o cmd.exe directamente se llama:
    • En ese caso, _embedded_ " se escapan como "" (en lugar de como \" ), y argumentos sin espacio que contienen cualquiera de los siguientes cmd.exe metacaracteres también están entre comillas dobles (normalmente, solo los argumentos _con espacios_ aparecen entre comillas dobles): " & | < > ^ , ; - esto hará que las llamadas a los archivos por lotes funcionen de manera sólida, lo cual es importante, porque muchas CLI de alto perfil usan archivos por lotes como _entrada puntos_.
    • Independientemente (y posiblemente además), si al menos un argumento que coincide con la expresión regular
      '^([/-]\w+[=:]|\w+=)(.*? .*)$' está presente, todos estos argumentos deben aplicarse _parcial_ entre comillas dobles alrededor de la _ parte de valor solamente_ (lo que sigue a : o = )

      • Por ejemplo, argumentos de estilo msiexec.exe / msdeploy.exe y cmdkey.exe PowerShell ve literalmente como
        FOO=bar baz y /foo:bar baz / -foo:bar baz se colocarían en la línea de comando del proceso como
        foo="bar baz" o /foo:"bar baz" / -foo:"bar baz" haciendo felices a _cualquier_ CLI que requiera este estilo de citación.
    • Los caracteres \ textuales en los argumentos deben manejarse de acuerdo con las convenciones de MS C / C ++.

Qué _no_ cubren estas adaptaciones:

  • msiexec.exe (y presumiblemente msdeploye.exe también) admite _only_ "" -escaping de _embedded_ " caracteres. , que las reglas anteriores _no_ cubrirían, excepto si llama a través de un archivo por lotes o cmd /c .

    • Esto debería ser lo suficientemente raro para empezar (p. Ej.,
      msiexec.exe /i example.msi PROPERTY="Nat ""King"" Cole" ), pero probablemente se haga aún más raro debido al hecho de que las invocaciones misexec generalmente se _hacen sincronizadas_ para esperar el final de una instalación, en cuyo caso puede evitar el problema en uno de dos caminos:
    • cmd /c start /wait msiexec /i example.msi PROPERTY='Nat "King' Cole' - confíe en llamadas a cmd.exe (luego) activando "" -escaping
    • Start-Process -Wait msiexec '/i example.msi PROPERTY="Nat ""King"" Cole"' - confíe en el parámetro -ArgumentList ( -Args ) pasando un argumento de una sola cadena literalmente como la línea de comando del proceso (aunque no es así como se supone que funciona este parámetro - ver # 5576).
  • Cualquier otra CLI no convencional para la que las adaptaciones anteriores no sean suficientes; personalmente no conozco ninguna.

Al final del día, siempre hay una solución alternativa: llame a través de cmd /c , o, para aplicaciones que no sean de consola, a través de Start-Process , o use --% ; si y cuando proporcionamos un cmdlet ins ( Invoke-NativeShell ), es otra opción; un cmdlet dbea ( Debug-ExecutableArguments con capacidades similares a echoArgs.exe , pero a pedido también para archivos por lotes, también ayudaría a _diagnosticar_ problemas.


En cuanto al camino hacia un cambio radical frente a la suscripción voluntaria:

  • ¿Implementar esto como una característica experimental significa que si se muestra suficiente interés, se convertirá en el comportamiento _predeterminado_ y, por lo tanto, supondrá un cambio rotundo (no trivial)?

  • ¿Puede asegurarse de que esta función experimental se publicite ampliamente, dada su importancia?

    • Una preocupación general que tengo sobre las funciones experimentales es que su uso a menudo puede ser _inconsciente_ en las versiones de vista previa, dado que _todas_ las funciones experimentales están activadas de forma predeterminada. Definitivamente queremos que la gente conozca y ejerza esta función deliberadamente.
¿Fue útil esta página
0 / 5 - 0 calificaciones