Escapar citas dobles en escritura por lotes

¿Cómo voy a reemplazar todas las comillas dobles en los parámetros de mi archivo por lotes con comillas dobles escapadas? Este es mi archivo por lotes actual, que expande todos sus parámetros de línea de comando dentro de la cadena:

@echo off call bash --verbose -c "g++-linux-4.1 %*" 

Luego usa esa cadena para hacer una llamada al bash de Cygwin, ejecutando un comstackdor cruzado de Linux. Lamentablemente, obtengo parámetros como estos pasados ​​a mi archivo por lotes:

 "launch-linux-g++.bat" -ftemplate-depth-128 -O3 -finline-functions -Wno-inline -Wall -DNDEBUG -c -o "C:\Users\Me\Documents\Testing\SparseLib\bin\Win32\LinuxRelease\hello.o" "c:\Users\Me\Documents\Testing\SparseLib\SparseLib\hello.cpp" 

Donde la primera cita alrededor de la primera ruta pasó prematuramente terminando la cadena pasando a GCC, y pasando el rest de los parámetros directamente a bash (que falla espectacularmente).

Imagino que si puedo concatenar los parámetros en una sola cadena y luego escapar de las comillas, debería funcionar bien, pero tengo dificultades para determinar cómo hacerlo. ¿Alguien sabe?

El carácter de escape en las secuencias de comandos por lotes es ^ . Pero para cadenas de comillas dobles, duplica las comillas:

 "string with an embedded "" character" 

La propia respuesta de eplawless resuelve simple y efectivamente su problema específico: reemplaza todas las " instancias en toda la lista de argumentos con \" , que es cómo Bash requiere que se representen las comillas dobles dentro de una cadena de comillas dobles.

Para responder en general a la pregunta de cómo escapar de las comillas dobles dentro de una cadena de comillas dobles utilizando cmd.exe , el intérprete de línea de comandos de Windows (ya sea en la línea de comandos, a menudo aún erróneamente llamado “indicador de DOS”) o en un lote archivo): vea la parte inferior para ver PowerShell .

tl; dr :

  • Debe usar "" al pasar una cadena a un archivo por lotes (más) y puede usar "" con aplicaciones creadas con los comstackdores C / C ++ / .NET de Microsoft , que también aceptan \" :

    • Ejemplo: foo.bat "We had 3"" of rain."

    • Lo siguiente se aplica solo a los archivos por lotes:

      • "" es la única forma de hacer que el intérprete de comandos ( cmd.exe ) trate la cadena doble entre comillas como un único argumento.

      • Tristemente, sin embargo, no solo se conservan las comillas dobles adjuntas (como es habitual), sino que también se duplican las escapadas, por lo que obtener la cadena deseada es un proceso de dos pasos; por ejemplo, suponiendo que la secuencia de comillas dobles se pasa como el primer argumento, %1 :

      • set "str=%~1" quita las comillas dobles adjuntas; set "str=%str:""="%" luego convierta las comillas dobles dobladas en simples.
        Asegúrese de utilizar las comillas dobles adjuntas alrededor de las partes de la tarea para evitar la interpretación no deseada de los valores.

  • \" es obligatorio , como única opción, por muchos otros progtwigs (por ejemplo, Perl, Python, Ruby e incluso el propio PowerShell de Microsoft (!)), pero SU USO NO ES SEGURO :

    • \" es lo que requieren muchos ejecutables e intérpretes, incluida la propia PowerShell de Microsoft cuando pasa cadenas desde el exterior – o, en el caso de los comstackdores de Microsoft, soporte como alternativa a "" – en última instancia, sin embargo, depende del progtwig de destino analizar la lista de argumentos.
    • Ejemplo: foo.exe "We had 3\" of rain."
    • SIN EMBARGO, EL USO DE \" PUEDE RESULTAR EN LA EJECUCIÓN ARBITRARIA NO DESEADA, y / o en las REDIRECCIONES DE ENTRADA / SALIDA :
      • Los siguientes caracteres presentan este riesgo: & | < > & | < >
      • Por ejemplo, los siguientes resultados en la ejecución involuntaria del comando ver ; vea más abajo para una explicación y el siguiente punto para una solución alternativa:
        • foo.exe "3\" of snow" "& ver."
  • Si debe usar \" , solo hay 3 enfoques seguros , que son, sin embargo, bastante engorrosos : Consejo del sombrero a TS por su ayuda.

    • Usando la expansión variable retrasada (posiblemente selectiva ) en su archivo por lotes, puede almacenar literal \" en una variable y hacer referencia a esa variable dentro de una cadena "..." usando la syntax de !var! – vea la útil respuesta de TS .

      • El enfoque anterior, a pesar de ser engorroso, tiene la ventaja de que puede aplicarlo metódicamente y de que funciona de forma robusta , con cualquier aportación.
    • Solo con cadenas LITERALES – que NO involucren VARIABLES – obtienes un enfoque similarmente metódico: descarta categóricamente todos los metacaracteres cmd.exe : " & | < > y – si también quieres suprimir la expansión variable – % :
      foo.exe ^"3\^" of snow^" ^"^& ver.^"

    • De lo contrario, debe formular su cadena de caracteres en función de reconocer qué partes de la cadena cmd.exe considera no citadas debido a la mala interpretación \" como delimitadores de cierre:

      • en porciones literales que contienen metacaracteres de shell: ^ -scape them; usando el ejemplo anterior, es & eso debe ser ^ -escaped:
        foo.exe "3\" of snow" "^& ver."

      • en porciones con %...% -style variable references : asegúrese de que cmd.exe considera parte de una cadena "..." y que los valores de las variables no tienen incrustados, comillas no balanceadas, lo que no siempre es posible .

Para información de fondo, sigue leyendo.


Fondo

Nota: Esto se basa en mis propios experimentos. Hazme saber si estoy equivocado.

Conchas similares a POSIX como Bash en sistemas tipo Unix tokenize la lista de argumentos (cadena) antes de pasar argumentos individualmente al progtwig de destino: entre otras expansiones, dividen la lista de argumentos en palabras individuales (división de palabras) y eliminan los caracteres de cotización del palabras resultantes (eliminación de citas). Lo que el progtwig objective se entrega es conceptualmente una matriz de argumentos individuales con las citas (requeridas por la syntax) eliminadas.

Por el contrario, el intérprete de comandos de Windows aparentemente no tokenize la lista de argumentos y simplemente pasa la única cadena que comprende todos los argumentos, incluyendo la cotización de caracteres. – al progtwig objective
Sin embargo, se lleva a cabo algún preprocesamiento antes de pasar la cadena única al progtwig de destino: ^ escape chars. fuera de las cadenas de comillas dobles se eliminan (se escapan de la siguiente char.), y las referencias de variables (por ejemplo, %USERNAME% ) se interpolan primero.

Por lo tanto, a diferencia de Unix, es responsabilidad del progtwig objective analizar el análisis de la cadena de argumentos y descomponerlo en argumentos individuales sin las comillas. Por lo tanto, diferentes progtwigs pueden hipotéticamente requerir diferentes métodos de escape y no hay un solo mecanismo de escape que garantice que funcione con todos los progtwigs . https://stackoverflow.com/a/4094897/45375 contiene excelentes antecedentes sobre la anarquía que es la línea de comandos de Windows. analizando.

En la práctica, \" es muy común, pero NO SEGURO , como se mencionó anteriormente:

Dado que cmd.exe sí mismo no reconoce \" como una comilla doble escapada , puede malinterpretar tokens posteriores en la línea de comandos como no citadas y potencialmente interpretarlos como comandos y / o redirecciones de entrada / salida .
En pocas palabras: el problema emerge, si alguno de los siguientes caracteres sigue una apertura o un desequilibrado \" : & | < > ; por ejemplo:

 foo.exe "3\" of snow" "& ver." 

cmd.exe ve los siguientes tokens, como resultado de malinterpretar \" como una comilla doble normal:

  • "3\"
  • of
  • snow" "
  • descanso: & ver.

Dado que cmd.exe piensa que & ver. está sin comillas , lo interpreta como & (el operador de secuencia de comandos), seguido por el nombre de un comando para ejecutar ( ver. – el . se ignora; cmd.exe la información de la versión de cmd.exe ).
El efecto general es:

  • En primer lugar, foo.exe se invoca con los primeros 3 tokens solamente.
  • Luego, se ejecuta el comando ver .

Incluso en los casos en que el comando accidental no daña, su comando general no funcionará según lo diseñado, dado que no se le pasan todos los argumentos.

Muchos comstackdores / intérpretes solo reconocen \" – por ejemplo, el comstackdor GNU C / C ++, Python, Perl, Ruby, incluso el propio PowerShell de Microsoft cuando se invoca desde cmd.exe – y para ellos no hay una solución simple a este problema.
Esencialmente, usted debería saber de antemano qué partes de su línea de comando se malinterpretan como no citadas, y selectivamente ^ -cape todas las instancias de & | < > & | < > en esas porciones.

Por el contrario, el uso de "" es SEGURO , pero lamentablemente solo es compatible con los ejecutables basados ​​en el comstackdor de Microsoft y los archivos por lotes (en el caso de los archivos por lotes, con los caprichos descritos anteriormente).

Por el contrario, los scripts de PowerShell invocados desde el exterior -por ejemplo, desde cmd.exe , ya sea desde la línea de comandos o un archivo por lotes- reconocen \" solo \" , aunque internamente PowerShell usa ` como el carácter de escape en cadenas de comillas dobles y también acepta "" .
Del mismo modo, pasar una cadena de comando a powershell.exe -c requiere \" ; por ejemplo,
powershell -c " \"ab c\".length" funciona (salidas 4 ), pero
powershell -c " ""ab c"".length" rompe.


Información relacionada

  • ^ solo se puede usar como el carácter de escape en cadenas sin comillas – dentro de las comillas dobles, ^ no es especial y se trata como un literal.

    • CAVEAT : El uso de ^ en los parámetros pasados ​​a la statement de call está quebrado (esto se aplica a ambos usos de call : invocando otro archivo por lotes o binario, y llamando a una subrutina en el mismo archivo por lotes):
      • ^ instancias en valores doblemente citados se doblan inexplicablemente , alterando el valor que se pasa: por ejemplo, si la variable %v% contiene el valor literal a^b , call :foo "%v%" asigna "a^^b" (!) a %1 (el primer parámetro) en la subrutina :foo .
      • El uso sin comillas de ^ con call se rompe por completo en el sentido de que ^ ya no se puede usar para escapar de caracteres especiales : por ejemplo, call foo.cmd a^&b quiebra silenciosamente (en lugar de pasar literalmente a&b también foo.cmd , como sería el caso sin call ) – foo.cmd nunca se invoca (!), al menos en Windows 7.
  • Escapar un % literal es un caso especial , desafortunadamente, que requiere una syntax distinta dependiendo de si una cadena se especifica en la línea de comando o dentro de un archivo por lotes ; ver https://stackoverflow.com/a/31420292/45375

    • En resumen: dentro de un archivo por lotes, use %% . En la línea de comando, no se puede escapar % , pero si coloca un ^ al principio, final o dentro de un nombre de variable en una cadena sin comillas (por ejemplo, echo %^foo% ), puede evitar la expansión variable (interpolación); % instancias en la línea de comandos que no son parte de una referencia de variable se tratan como literales (por ejemplo, 100% ).
  • En general, para trabajar de forma segura con valores variables que pueden contener espacios y caracteres especiales :

    • Asignación : incluya tanto el nombre de la variable como el valor en un solo par de comillas dobles ; por ejemplo, set "v=a & b" asigna el valor literal a & b a la variable %v% (por el contrario, set v="a & b" haría que las comillas dobles sean parte del valor). Escape % instancias % como %% (funciona solo en archivos de proceso por lotes, consulte más arriba).
    • Referencia : referencias de comillas dobles para asegurarse de que su valor no esté interpolado; por ejemplo, echo "%v%" no somete el valor de %v% a la interpolación e imprime "a & b" (pero tenga en cuenta que las comillas dobles se imprimen invariablemente también). Por el contrario, echo %v% pasa literal a a echo , interpreta & como el operador de secuencia de comandos, y por lo tanto intenta ejecutar un comando llamado b .
      También tenga en cuenta la anterior advertencia de uso de ^ con el enunciado de call .
    • Los progtwigs externos normalmente se encargan de eliminar las comillas dobles que rodean a los parámetros, pero, como se señaló, en los archivos por lotes, debe hacerlo usted mismo (por ejemplo, %~1 para eliminar las comillas dobles del primer parámetro) y, lamentablemente, hay No conozco ninguna forma directa de obtener echo para imprimir fielmente un valor variable sin las comillas dobles adjuntas .
      • Neil ofrece una solución alternativa que funciona siempre que el valor no tenga comillas dobles incrustadas ; p.ej:
        set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
  • cmd.exe no reconoce las comillas simples como delimitadores de cadena; se tratan como literales y, en general, no se pueden usar para delimitar cadenas con espacios en blanco incrustados; también, se deduce que los tokens que colindan con las comillas simples y cualquier token en el medio se tratan como no citado por cmd.exe y se interpretan en consecuencia.

    • Sin embargo, dado que los progtwigs de destino finalmente realizan su propio análisis de argumentos, algunos progtwigs como Ruby reconocen cadenas de comillas simples incluso en Windows; por el contrario, los ejecutables C / C ++, Perl y Python no los reconocen.
      Sin embargo, incluso si es compatible con el progtwig de destino, no es aconsejable utilizar cadenas de comillas simples, dado que sus contenidos no están protegidos de una interpretación potencialmente no deseada por cmd.exe .

Potencia Shell

Windows PowerShell es un shell mucho más avanzado que cmd.exe , y ha sido parte de Windows durante muchos años (y PowerShell Core también aportó la experiencia de PowerShell a macOS y Linux).

PowerShell funciona de forma consistente internamente con respecto a las cotizaciones:

  • dentro de las cadenas de comillas dobles, use `" o "" para escapar de las comillas dobles
  • dentro de cadenas simples, use '' para escapar de comillas simples

Esto funciona en la línea de comandos de PowerShell y al pasar parámetros a scripts o funciones de PowerShell desde PowerShell.

(Como se discutió anteriormente, pasar una doble cita escapada a PowerShell desde el exterior requiere \" – nada más funciona).

Lamentablemente, al invocar progtwigs externos , se enfrenta a la necesidad de acomodar las reglas de cotización de PowerShell y de escapar para el progtwig de destino :

Las dobles- comillas dentro de las cadenas dobles- citadas :

Considere la cadena "3`" of rain" , que PowerShell "3`" of rain" internamente a literalmente 3" of rain .

Si desea pasar esta cadena a un progtwig externo, debe aplicar el escape del progtwig objective además de PowerShell ; supongamos que quiere pasar la cadena a un progtwig C, que espera que las comillas dobles incrustadas se escapen como \" :

 foo.exe "3\`" of rain" 

Tenga en cuenta que tanto `" – para hacer feliz a PowerShell – y el \ – para hacer que el progtwig objective sea feliz – debe estar presente.

La misma lógica se aplica a la invocación de un archivo por lotes, donde debe usarse "" :

 foo.bat "3`"`" of rain" 

Por el contrario, la incrustación de comillas simples en una cadena con las dobles comillas no requiere escaparse en absoluto.

Las comillas simples dentro de las cadenas de una sola cita no requieren un escape adicional ; considere '2'' of snow' , que es la representación de 2' of snow PowerShell”.

 foo.exe '2'' of snow' foo.bat '2'' of snow' 

PowerShell traduce cadenas de comillas simples a dobles comillas antes de pasarlas al progtwig de destino.

Sin embargo, las comillas dobles dentro de las cadenas de una sola cita , que no necesitan escaparse para PowerShell , todavía tienen que ser escapadas para el progtwig de destino :

 foo.exe '3\" of rain' foo.bat '3"" of rain' 

PowerShell v3 introdujo la opción magic --% , llamada stop-parsing symbol , que alivia parte del dolor, al pasar cualquier cosa después de que no se haya interpretado en el progtwig objective, guardar referencias de variable de entorno estilo cmd.exe (por ejemplo, %USERNAME% ), que se expanden; p.ej:

 foo.exe --% "3\" of rain" -u %USERNAME% 

Tenga en cuenta que basta con escaparse del " como " incrustado para el progtwig de destino (y no también para PowerShell como \`" ).

Sin embargo, este enfoque:

  • no permite el escape de caracteres % para evitar expansiones de variable de entorno.
  • impide el uso directo de variables y expresiones de PowerShell; en su lugar, la línea de comando debe construirse en una variable de cadena en un primer paso, y luego invocarse con Invoke-Expression en un segundo.

Por lo tanto, a pesar de sus muchos avances, PowerShell no ha hecho que escaparse sea mucho más fácil cuando se llaman progtwigs externos. Sin embargo, ha introducido soporte para cadenas de comillas simples.

Me pregunto si, en el mundo de Windows, es fundamentalmente posible cambiar al modelo de Unix de permitir que el shell realice toda la tokenización y la eliminación de cotizaciones predeciblemente , por adelantado , independientemente del progtwig objective , y luego invocar el progtwig objective pasando los tokens resultantes .

Google finalmente tuvo la respuesta. La syntax para el reemplazo de cadenas en lote es esta:

 set v_myvar=replace me set v_myvar=%v_myvar:ace=icate% 

Lo que produce “replicarme”. Mi script ahora se ve así:

 @echo off set v_params=%* set v_params=%v_params:"=\"% call bash -c "g++-linux-4.1 %v_params%" 

Que reemplaza todas las instancias de " con \" , escapó correctamente para bash.

Como una adición a la excelente respuesta de mklement0 :

Casi todos los ejecutables aceptan \" como un escape " . Sin embargo, el uso seguro en cmd solo es posible con DELAYEDEXPANSION.
Para enviar explícitamente un literal " a algún proceso, asignar \" a una variable de entorno, y luego usar esa variable, cada vez que necesite pasar una cita. Ejemplo:

 SETLOCAL ENABLEDELAYEDEXPANSION set q=\" child "malicious argument!q!&whoami" 

Nota SETLOCAL ENABLEDELAYEDEXPANSION parece funcionar solo dentro de los archivos por lotes. Para obtener DELAYEDEXPANSION en una sesión interactiva, inicie cmd /V:ON .

Si su archivo por lotes no funciona con DELAYEDEXPANSION, puede habilitarlo temporalmente:

 ::region without DELAYEDEXPANSION SETLOCAL ENABLEDELAYEDEXPANSION ::region with DELAYEDEXPANSION set q=\" echoarg.exe "ab !q! & echo danger" ENDLOCAL ::region without DELAYEDEXPANSION 

Si desea pasar contenido dynamic de una variable que contiene comillas que se han escapado como "" , puede reemplazar "" con \" en la expansión:

 SETLOCAL ENABLEDELAYEDEXPANSION foo.exe "danger & bar=region with !dynamic_content:""=\"! & danger" ENDLOCAL 

¡Este reemplazo no es seguro con %...% expansión de estilo!

En el caso de OP bash -c "g++-linux-4.1 !v_params:"=\"!" es la versión segura


Si por algún motivo, incluso temporalmente, permite que DELAYEDEXPANSION no sea una opción, siga leyendo:

Usar \" desde dentro de cmd es un poco más seguro si uno siempre necesita escapar caracteres especiales, en lugar de solo algunas veces. (Es menos probable que olvide un símbolo de intercalación, si es consistente …)

Para lograr esto, uno precede a cualquier cita con un símbolo de intercalación ( ^" ), las comillas que deben llegar al proceso hijo ya que los literales deben además escapar con una reacción ( \^" ). TODOS los metacaracteres del shell deben ser escapados con ^ también, por ejemplo & => ^& ; | => ^| ; > => ^> ; etc.

Ejemplo:

 child ^"malicious argument\^"^&whoami^" 

Fuente: Todo el mundo cita los argumentos de la línea de comando de forma incorrecta , consulte “Un método mejor para citar”.


Para pasar contenido dynamic, uno debe asegurarse de lo siguiente:
La parte del comando que contiene la variable debe considerarse “citada” por cmd.exe (Esto es imposible si la variable puede contener comillas – no escriba %var:""=\"% ). Para lograr esto, el el último " antes de la variable y el primero " después de la variable no son ^ -escaped. cmd-metacharacters entre esos dos " no se deben escapar”. Ejemplo:

 foo.exe ^"danger ^& bar=\"region with %dynamic_content% & danger\"^" 

Esto no es seguro, si %dynamic_content% puede contener comillas sin %dynamic_content% .