2012-09-29 45 views
5

suponga que ejecuta test.bat "blabla,blabla,^>blabla", "blaby"¿Comportamiento extraño con caracteres especiales en los argumentos de las funciones de proceso por lotes?

aplicación test.bat:

@SETLOCAL 
@ECHO OFF 
SET list=%~1 
ECHO "LIST: %list%" 
ECHO "ARG 1: %~1" 
ECHO "ARG 2: %~2" 
@ENDLOCAL 
@GOTO :EOF 

de salida es el esperado:

"LIST: blabla,blabla,>blabla" 
"ARG 1: blabla,blabla,^>blabla" 
"ARG 2: blaby" 

Pero lo que si haces test.bat una función dentro de un archivo por lotes:

@SETLOCAL 
CALL :TEST "blabla,blabla,^>blabla", "blaby" 
@ENDLOCAL 
@GOTO :EOF 

:TEST 
@SETLOCAL 
@ECHO OFF 
SET list=%~1 
ECHO "LIST: %list%" 
ECHO "ARG 1: %~1" 
ECHO "ARG 2: %~2" 
@ENDLOCAL 
@GOTO :EOF 

Después de ejecutar it th La salida e es:

"LIST: blabla,blabla,^" 
"ARG 1: blabla,blabla,^^>blabla" 
"ARG 2: blaby" 

¿Huh?

  1. ¿Dónde entró blabla en la lista?
  2. ARG 1 tiene ^^? ¿Por qué?

¿Alguien puede explicar cómo los caracteres especiales se comportan de manera diferente en los argumentos de función en comparación con los argumentos de línea de comando?

Respuesta

9

, usted puede obtener el mismo resultado con su primera escritura de la hornada simplemente usando:

call test.bat "blabla,blabla,^>blabla", "blaby" 

Sus problemas se derivan de un aspecto lamentable de cómo el procesamiento por lotes analiza las sentencias CALL. Se describe en la fase 6 en How does the Windows Command Interpreter (CMD.EXE) parse scripts?.

Ouch - Creí haber entendido antes el doblaje, pero obviamente no. He editado en gran medida la siguiente discusión en respuesta a los comentarios de jeb.

Los diseñadores de CMD.EXE quieren una declaración como call echo ^^ para dar el mismo resultado que echo ^^. Ambas sentencias reducen ^^ a ^ en la fase 2, donde se manejan los caracteres especiales. Pero la declaración CALL debe pasar por la fase 1 y la fase 2 por segunda vez. Así que detrás de las escenas, cuando CMD.EXE reconoce la instrucción CALL en la fase 6, dobla el cursor restante a ^^ y luego la segunda ronda de la fase 2 lo reduce a ^. Ambas sentencias hacen eco de un solo cursor en la pantalla.

Desafortunadamente, CMD.EXE dobla a ciegas todos los carec, incluso si están entre comillas. Pero un caret citado no se trata como un escape, es literal. Los caretos ya no se consumen. Muy desafortunado.

Corriendo call test.bat "blabla,blabla,^>blabla", "blaby" se convierte en call test.bat "blabla,blabla,^^>blabla" "blaby" en la fase 6 del analizador sintáctico.

Eso explica fácilmente por qué ARG 1 se ve como lo hace en su salida.

¿Hasta dónde llegó blabla ?, eso es un poco más complicado.

Cuando el script se ejecuta SET list=%~1, las cotizaciones se eliminan, el ^^ es tratado como un símbolo de intercalación escapado que se reduce a ^, y el > ya no se escapó.Por lo tanto, el resultado de su instrucción SET se redirige a un archivo "blabla". Por supuesto, SET no tiene salida, por lo que debe tener un archivo "blabla" de longitud cero en su disco duro.

EDITAR - Cómo pasar correctamente los argumentos deseados usando "expansión tardía"

En su respuesta, Davor trató de revertir el efecto de duplicar el cursor dentro de la rutina de llamada. Pero eso no es confiable porque no se puede saber con certeza cuántas veces se han duplicado los cuidados. Es mejor si permite que la persona que llama ajuste la llamada para compensarlo. Es complicado: debe usar lo que se denomina "expansión tardía"

Dentro de una secuencia de comandos por lotes puede definir una variable que contenga la cadena de argumentos deseada y luego retrasar la expansión hasta que las llaves se dupliquen escapando el% con otro%. Necesita duplicar los porcentajes para cada llamada en la declaración.

@echo off 
setlocal 
set arg1="blabla,blabla,^>blabla" 
call :TEST %%arg1%% "blaby" 
echo(
call call :TEST %%%%arg1%%%% "blaby" 
::unquoted test 
exit /b 

:TEST 
setlocal 
set list=%~1 
echo "LIST: %list%" 
echo "ARG 1: %~1" 
echo "ARG 2: %~2" 
exit /b 

El anterior produce el resultado deseado:

"LIST: blabla,blabla,>blabla" 
"ARG 1: blabla,blabla,^>blabla" 
"ARG 2: blaby" 

"LIST: blabla,blabla,>blabla" 
"ARG 1: blabla,blabla,^>blabla" 
"ARG 2: blaby" 

Las reglas de expansión son diferentes cuando se ejecuta desde la línea de comandos. Es imposible escapar un% de la línea de comando. En su lugar, debe agregar un símbolo de intercalación dentro de los porcentajes que evite que la fase de expansión reconozca el nombre en la 1ra pasada, y luego cuando se quita la intercalación en la fase 2, el 2 ° paso de expansión expande adecuadamente la variable.

TEST.BAT original de los siguientes usos de Davor

C:\test>test.bat "blabla,blabla,^>blabla" "blaby" 
"LIST: blabla,blabla,>blabla" 
"ARG 1: blabla,blabla,^>blabla" 
"ARG 2: blaby" 

C:\test>set arg1="blabla,blabla,^>blabla" 

C:\test>test.bat %arg1% "blaby" 
"LIST: blabla,blabla,>blabla" 
"ARG 1: blabla,blabla,^>blabla" 
"ARG 2: blaby" 

C:\test>call test.bat %^arg1% "blaby" 
"LIST: blabla,blabla,>blabla" 
"ARG 1: blabla,blabla,^>blabla" 
"ARG 2: blaby" 

C:\test>set arg2=%^arg1% 

C:\test>call call test.bat %^arg2% "blaby" 
"LIST: blabla,blabla,>blabla" 
"ARG 1: blabla,blabla,^>blabla" 
"ARG 2: blaby" 

Una alternativa para escapar - pasar valores de referencia!

En general, las reglas de escape son ridículamente complicadas. Es por eso que las secuencias de comandos por lotes avanzadas a menudo pasan los valores de cadena por referencia en lugar de como literales. La cadena deseada se coloca en una variable y luego el nombre de la variable se pasa como un argumento. La expansión retrasada se utiliza para obtener la cadena exacta sin temor a la corrupción debido a caracteres especiales o duplicación de atención CALL o porcentaje de despojo.

Aquí es una test.bat sencillo que muestra el concepto

@echo off 
setlocal enableDelayedExpansion 
set "var1=!%~1!" 
echo var1=!var1! 

call :test var1 
exit /b 

:test 
set "var2=!%~1!" 
echo var2=!var2! 

Y aquí es una demostración de cómo funciona.

C:\test>set complicatedString="This & that ^" ^& the other thing ^^ is 100% difficult to escape 

C:\test>set complicatedString 
complicatedString="This & that ^" & the other thing^is 100% difficult to escape 

C:\test>test.bat complicatedString 
var1="This & that ^" & the other thing^is 100% difficult to escape 
var2="This & that ^" & the other thing^is 100% difficult to escape 

C:\test>call test.bat complicatedString 
var1="This & that ^" & the other thing^is 100% difficult to escape 
var2="This & that ^" & the other thing^is 100% difficult to escape 

C:\test>call call test.bat complicatedString 
var1="This & that ^" & the other thing^is 100% difficult to escape 
var2="This & that ^" & the other thing^is 100% difficult to escape 
+0

+1 incluso la parte de 'call echo this^& that' está completamente mal :-) – jeb

+0

@jeb - ??? No sé cómo interpretar tu comentario? ¿Qué me he equivocado? – dbenham

+2

No hace eco de nada, ya que en la fase 2, se consume el símbolo de intercalación.Entonces, después, no hay ningún símbolo de intercalación que pueda duplicarse, por lo que solo hay un símbolo nake y el "eco de llamada" completo falla. No es posible escapar de algo literalmente en un "eco de llamada" solo con la expansión tardía. – jeb

0

Después de algunas pruebas y la respuesta de dbenham, que apears habría que anticipar el doble cursor y vuelva a colocar de nuevo con un solo símbolo de intercalación:

@SETLOCAL 
CALL :TEST "blabla,blabla,^>blabla", "blaby" 
@ENDLOCAL 
@GOTO :EOF 

:TEST 
@SETLOCAL 
@ECHO OFF 
SET "list=%~1" & REM CHANGED 
SET "list=%list:^^=^%" & REM ADDED 
ECHO "LIST: %list%" 
ECHO "ARG 1: %~1" 
ECHO "ARG 2: %~2" 
@ENDLOCAL 
@GOTO :EOF 

continuación, la salida es:

"LIST: blabla,blabla,^>blabla" 
"ARG 1: blabla,blabla,^^>blabla" 
"ARG 2: blaby" 

Tenga en cuenta también lo extraño: en SET "list=%list:^^=^%"^^ entre %% se toma como dos caracteres, no como el escapado ^.

+1

Eso funciona siempre que sepa cómo se llamará a su rutina. Se romperá si se llama usando 'call call: test ...'. Poco probable, pero existen circunstancias excepcionales en las que se necesita una doble llamada. Más problemático es tu "TEST.BAT" original. Se puede ejecutar usando 'TEST ...' o 'CALL TEST ...'. Su secuencia de comandos no tiene manera de saber si las interconexiones deben corregirse. Actualizaré mi respuesta para mostrar cómo obtener el resultado deseado al modificar la declaración CALL utilizando la técnica de "expansión tardía" que se hace referencia críptica. – dbenham

Cuestiones relacionadas