sirve redes que para hackear comandos basicos windows parsing batch-file cmd variable-expansion

redes - ¿Cómo funciona el intérprete de comandos de Windows(CMD.EXE) para analizar scripts?



comandos de cmd para hackear (6)

Al invocar un comando desde una ventana de comando, Token no hace tokenización de los argumentos de la línea de comando cmd.exe (también conocido como "el shell"). Muy a menudo la tokenización se realiza mediante el tiempo de ejecución C / C ++ de los procesos recién formados, pero esto no es necesariamente así, por ejemplo, si el nuevo proceso no se escribió en C / C ++, o si el nuevo proceso elige ignorar argv y procesar la línea de comandos en bruto por sí mismo (por ejemplo, con GetCommandLine() ). En el nivel del sistema operativo, Windows pasa las líneas de comando desaconsejadas como una única cadena a los nuevos procesos. This is in contrast to most *nix shells, where the shell tokenizes arguments in a consistent, predictable way before passing them to the newly formed process. All this means that you may experience wildly divergent argument tokenization behavior across different programs on Windows, as individual programs often take argument tokenization into their own hands.

If it sounds like anarchy, it kind of is. However, since a large number of Windows programs do utilize the Microsoft C/C++ runtime''s argv , it may be generally useful to understand how the MSVCRT tokenizes arguments. Here is an excerpt:

  • Arguments are delimited by white space, which is either a space or a tab.
  • A string surrounded by double quotation marks is interpreted as a single argument, regardless of white space contained within. A quoted string can be embedded in an argument. Note that the caret (^) is not recognized as an escape character or delimiter.
  • A double quotation mark preceded by a backslash, /", is interpreted as a literal double quotation mark (").
  • Backslashes are interpreted literally, unless they immediately precede a double quotation mark.
  • If an even number of backslashes is followed by a double quotation mark, then one backslash () is placed in the argv array for every pair of backslashes (/), and the double quotation mark (") is interpreted as a string delimiter.
  • If an odd number of backslashes is followed by a double quotation mark, then one backslash () is placed in the argv array for every pair of backslashes (/) and the double quotation mark is interpreted as an escape sequence by the remaining backslash, causing a literal double quotation mark (") to be placed in argv.

The Microsoft "batch language" ( .bat ) is no exception to this anarchic environment, and it has developed its own unique rules for tokenization and escaping. It also looks like cmd.exe''s command prompt does do some preprocessing of the command line argument (mostly for variable substitution and escaping) before passing the argument off to the newly executing process. You can read more about the low-level details of the batch language and cmd escaping in the excellent answers by jeb and dbenham on this page.

Let''s build a simple command line utility in C and see what it says about your test cases:

int main(int argc, char* argv[]) { int i; for (i = 0; i < argc; i++) { printf("argv[%d][%s]/n", i, argv[i]); } return 0; }

(Notes: argv[0] is always the name of the executable, and is omitted below for brevity. Tested on Windows XP SP3. Compiled with Visual Studio 2005.)

> test.exe "a ""b"" c" argv[1][a "b" c] > test.exe """a b c""" argv[1]["a b c"] > test.exe "a"" b c argv[1][a" b c]

And a few of my own tests:

> test.exe a "b" c argv[1][a] argv[2][b] argv[3][c] > test.exe a "b c" "d e argv[1][a] argv[2][b c] argv[3][d e] > test.exe a /"b/" c argv[1][a] argv[2]["b"] argv[3][c]

Me encontré con ss64.com que proporciona una buena ayuda con respecto a cómo escribir secuencias de comandos por lotes que ejecutará el intérprete de comandos de Windows.

Sin embargo, no he podido encontrar una buena explicación de la gramática de los guiones por lotes, cómo se expanden o no, y cómo escapar de las cosas.

Aquí hay ejemplos de preguntas que no he podido resolver:

  • ¿Cómo se maneja el sistema de cotizaciones? Hice un script de TinyPerl
    ( foreach $i (@ARGV) { print ''*'' . $i ; } ), lo compiló y lo llamó de esta manera:
    • my_script.exe "a ""b"" c" → la salida es *a "b*c
    • my_script.exe """abc"""my_script.exe """abc""" *"a*b*c"
  • ¿Cómo funciona el comando echo interno? ¿Qué se expande dentro de ese comando?
  • ¿Por qué tengo que usar for [...] %%I en scripts de archivos, pero for [...] %I en sesiones interactivas?
  • ¿Cuáles son los personajes de escape y en qué contexto? ¿Cómo escapar de un signo de porcentaje? Por ejemplo, ¿cómo puedo hacer un eco de %PROCESSOR_ARCHITECTURE% literalmente? Descubrí que echo.exe %""PROCESSOR_ARCHITECTURE% funciona, ¿hay una mejor solución?
  • ¿Cómo coinciden los pares de % ? Ejemplo:
    • set b=a , echo %a %b% c%%aac%
    • set a =b , echo %a %b% c%bb c%
  • ¿Cómo me aseguro de que una variable pase a un comando como un único argumento si alguna vez esta variable contiene comillas dobles?
  • ¿Cómo se almacenan las variables cuando se utiliza el comando set ? Por ejemplo, si set a=a" b y luego echo.%a% , obtengo a" b . Sin embargo, si uso echo.exe de UnxUtils, obtengo ab . ¿Cómo se expande %a% de una manera diferente?

Gracias por tus luces


Realicé muchos experimentos para investigar la gramática de los guiones por lotes. También investigué las diferencias entre la gramática de las secuencias de comandos por lotes y el modo de línea de comandos.

El analizador de línea por lotes:

Procesar una línea de código en un archivo por lotes implica múltiples fases.

Aquí hay una breve descripción de las diversas fases:

Fase 0) Línea de lectura:

Fase 1) Expansión porcentual:

Fase 1.5) Eliminar <CR> : eliminar todos los caracteres de retorno de carro (0x0D)

Fase 2) Procesar caracteres especiales, tokenizar y construir un bloque de comandos en caché: este es un proceso complejo que se ve afectado por elementos tales como comillas, caracteres especiales, delimitadores de tokens y escapes de intercalaciones.

Fase 3) Echo los comandos analizados solo si el bloque de comandos no comenzó con @ , y ECHO estaba ENCENDIDO al inicio del paso anterior.

Fase 4) FOR %X variable expansion: solo si un comando FOR está activo y los comandos después de DO están siendo procesados.

Fase 5) Expansión retrasada: solo si la expansión retrasada está habilitada

Fase 5.3) Procesamiento de tuberías: solo si los comandos están a ambos lados de un tubo

Fase 5.5) Ejecutar redirección:

Fase 6) CALL processing / Caret double: solo si el token de comando es CALL

Fase 7) Ejecutar: el comando se ejecuta

Y aquí hay detalles para cada fase:

Tenga en cuenta que las fases que se describen a continuación son solo un modelo de cómo funciona el analizador de lotes. Las partes internas reales de cmd.exe pueden no reflejar estas fases. Pero este modelo es extremadamente eficaz para predecir el comportamiento de los scripts por lotes.

Fase 0) Leer línea: Leer línea de entrada.

  • Al leer una línea para analizar como un comando, <Ctrl-Z> (0x1A) se lee como <LF> (LineFeed 0x0A)
  • Cuando GOTO o CALL lee líneas mientras escanea una etiqueta:, <Ctrl-Z> , se trata como sí mismo, no se convierte a <LF>

Fase 1) Expansión porcentual:

  • Un doble %% es reemplazado por un solo %
  • Expansión de variables de argumento ( %1 , %2 , etc.)
  • Expansión de %var% , si var no existe sustitúyalo por nada
  • Para una explicación completa, lea la primera mitad de esto de dbenham Mismo hilo: Fase de porcentaje

Fase 1.5) Eliminar <CR> : eliminar todas las devoluciones de carro (0x0D) de la línea

Fase 2) Procesar caracteres especiales, tokenizar y construir un bloque de comandos en caché: este es un proceso complejo que se ve afectado por elementos tales como comillas, caracteres especiales, delimitadores de tokens y escapes de intercalaciones. Lo que sigue es una aproximación de este proceso.

Hay algunos conceptos que son importantes a lo largo de esta fase.

  • Un token es simplemente una cadena de caracteres que se trata como una unidad.
  • Los tokens están separados por delimitadores de tokens. Los delimitadores de token estándar son <space> <tab> ; , = <0x0B> <0x0C> y <0xFF>
    Los delimitadores de tokens consecutivos se tratan como uno: no hay tokens vacíos entre los delimitadores de tokens.
  • No hay delimitadores de tokens dentro de una cadena entre comillas. Toda la cadena entrecomillada siempre se trata como parte de un solo token. Un único token puede consistir en una combinación de cadenas entre comillas y caracteres sin comillas.

Los siguientes caracteres pueden tener un significado especial en esta fase, según el contexto: ^ ( @ & | < > <LF> <space> <tab> ; = <0x0B> <0x0C> <0xFF>

Mire cada personaje de izquierda a derecha:

  • Si es un símbolo de intercalación ( ^ ), se escapa el siguiente carácter y se elimina el símbolo de intercalación de escape. Los personajes escapados pierden todo significado especial (excepto para <LF> ).
  • Si es una comilla ( " ), alternar la bandera de la cita. Si la bandera de la cita está activa, entonces solo " y <LF> son especiales. Todos los demás personajes pierden su significado especial hasta que la siguiente cita desactiva la bandera de cita. No es posible escapar de la cita de cierre. Todos los caracteres entrecomillados están siempre dentro del mismo token.
  • <LF> siempre apaga la bandera de cotización. Otros comportamientos varían según el contexto, pero las citas nunca alteran el comportamiento de <LF> .
    • Escapó <LF>
      • <LF> se quita
      • El siguiente personaje se escapó. Si está al final del buffer de línea, entonces la siguiente línea es leída y anexada a la actual antes de escapar del siguiente caracter. Si el siguiente carácter es <LF> , entonces se trata como un literal, lo que significa que este proceso no es recursivo.
    • Unescaped <LF> no entre paréntesis
      • <LF> se elimina y el análisis de la línea actual finaliza.
      • Cualquier carácter restante en el buffer de línea simplemente se ignora.
    • Unescaped <LF> dentro de un bloque PAR entre paréntesis
      • <LF> se convierte en un <space>
      • Si está al final del buffer de línea, entonces la siguiente línea se lee y se agrega a la actual.
    • Unescaped <LF> dentro de un bloque de comando entre paréntesis
      • <LF> se convierte en <LF><space> y el <space> se trata como parte de la siguiente línea del bloque de comandos.
      • Si está al final del buffer de línea, entonces la siguiente línea es leída y anexada al espacio.
  • Si es uno de los personajes especiales & | < o > , divida la línea en este punto para manejar las tuberías, la concatenación de comandos y la redirección.
    • En el caso de una tubería ( | ), cada lado es un comando separado (o bloque de comandos) que obtiene un manejo especial en la fase 5.3
    • En el caso de & , && , o || Concatenación de comandos, cada lado de la concatenación se trata como un comando separado.
    • En el caso de la redirección < , << , > o >> , la cláusula de redirección se analiza, se elimina temporalmente y luego se agrega al final del comando actual. Una cláusula de redirección consiste en un dígito de identificador de archivo opcional, el operador de redirección y el token de destino de redirección.
      • Si el token que precede al operador de redirección es un único dígito, entonces el dígito especifica el manejador de archivo que se redirigirá. Si no se encuentra el token del controlador, la redirección de salida se establece de manera predeterminada en 1 (stdout), y la redirección de entrada se establece de manera predeterminada en 0 (stdin).
  • Si el primer token para este comando (antes de mover la redirección al final) comienza con @ , entonces el @ tiene un significado especial. ( @ no es especial en ningún otro contexto)
    • El @ especial se elimina.
    • Si ECHO está en ON, este comando, junto con los siguientes comandos concatenados en esta línea, se excluyen del eco de fase 3. Si el @ está antes de una apertura ( , entonces todo el bloque entre paréntesis se excluye del eco de fase 3.
  • Paréntesis de proceso (proporciona declaraciones compuestas en múltiples líneas):
    • Si el analizador no está buscando un token de comando, entonces ( no es especial.
    • Si el analizador busca un token de comando y encuentra ( , entonces comience una nueva instrucción compuesta e incremente el contador de paréntesis
    • Si el contador de paréntesis es> 0, entonces ) termina la instrucción compuesta y decrementa el contador de paréntesis.
    • Si se llega al final de la línea y el contador de paréntesis es> 0, la siguiente línea se agregará a la declaración compuesta (comienza de nuevo con la fase 0)
    • Si el contador de paréntesis es 0 y el analizador busca un comando, entonces ) funciona de forma similar a una instrucción REM siempre que vaya seguida de un delimitador de token, carácter especial, nueva línea o fin de archivo
      • Todos los caracteres especiales pierden su significado excepto ^ (la concatenación de línea es posible)
      • Una vez que se llega al final de la línea lógica, se descarta todo el "comando".
  • Cada comando se analiza en una serie de tokens. El primer token siempre se trata como un token de comando (después de que @ se ha eliminado y la redirección se ha movido hasta el final).
    • Los delimitadores de token iniciales antes del token de comando se eliminan
    • Al analizar el token de comando, ( funciona como un delimitador de token de comando, además de los delimitadores de token estándar)
    • El manejo de los tokens posteriores depende del comando.
  • La mayoría de los comandos simplemente concatenan todos los argumentos después del token de comando en un solo token de argumento. Todos los delimitadores de token de argumento se conservan. Las opciones de argumento normalmente no se analizan hasta la fase 7.
  • Tres comandos obtienen un manejo especial: IF, FOR y REM
    • IF se divide en dos o tres partes distintas que se procesan de forma independiente. Un error de sintaxis en la construcción IF provocará un error de sintaxis fatal.
      • La operación de comparación es el comando real que fluye a través de la fase 7
        • Todas las opciones de IF se analizan por completo en la fase 2.
        • Los delimitadores de tokens consecutivos colapsan en un solo espacio.
        • Según el operador de comparación, habrá uno o dos tokens de valor identificados.
      • El bloque de comandos True es el conjunto de comandos después de la condición y se analiza como cualquier otro bloque de comandos. Si se va a utilizar ELSE, entonces el bloque True debe estar entre paréntesis.
      • El bloque de comando falso opcional es el conjunto de comandos después de ELSE. De nuevo, este bloque de comandos se analiza normalmente.
      • Los bloques de comandos True y False no fluyen automáticamente a las fases posteriores. Su procesamiento posterior está controlado por la fase 7.
    • FOR se divide en dos después del DO. Un error de sintaxis en la construcción FOR dará como resultado un error de sintaxis fatal.
      • La porción a través de DO es el comando de iteración FOR real que fluye a través de la fase 7
        • Todas las opciones FOR se analizan por completo en la fase 2.
        • La cláusula entre paréntesis IN trata <LF> como <space> . Después de analizar la cláusula IN, todos los tokens se combinan para formar un solo token.
        • Los delimitadores de tokens consecutivos colapsan en un único espacio a través del comando FOR a través de DO.
      • La porción después de DO es un bloque de comandos que se analiza normalmente. El procesamiento posterior del bloque de comando DO se controla mediante la iteración en la fase 7.
    • REM detectado en la fase 2 se trata de manera dramáticamente diferente a todos los demás comandos.
      • Solo se analiza un token de argumento: el analizador ignora los caracteres después del token del primer argumento.
      • Si solo hay un token de argumento que termina con un sinescabado ^ que finaliza la línea, entonces el token de argumento se descarta, y la línea subsiguiente se analiza y se agrega al REM. Esto se repite hasta que haya más de un token, o el último carácter no sea ^ .
      • El comando REM puede aparecer en la salida de la fase 3, pero el comando nunca se ejecuta y el texto del argumento original se repite: las referencias de escape no se eliminan.
  • Si el token de comando comienza con : y esta es la primera ronda de la fase 2 (no un reinicio debido a CALL en la fase 6), entonces
    • El token normalmente se trata como una etiqueta sin errores .
      • El resto de la línea se analiza, sin embargo ) , < , > , & y | ya no tiene un significado especial. El resto de la línea se considera parte de la etiqueta "comando".
      • El ^ sigue siendo especial, lo que significa que la continuación de línea se puede usar para agregar la línea siguiente a la etiqueta.
      • Una etiqueta no ejecutada dentro de un bloque entre paréntesis dará como resultado un error de sintaxis fatal a menos que sea seguida inmediatamente por un comando o etiqueta ejecutada en la siguiente línea.
        • Tenga en cuenta que ( ya no tiene un significado especial para el primer comando que sigue a la Etiqueta sin errores en este contexto.
      • El comando se cancela después de que se completa el análisis de etiqueta. Las fases posteriores no tienen lugar para la etiqueta
    • Hay tres excepciones que pueden hacer que una etiqueta encontrada en la fase 2 se trate como una etiqueta ejecutada que continúa analizando a través de la fase 7.
      • Hay una redirección que precede al token de etiqueta, y hay un | pipe o & , && , o || comando concatenación en la línea.
      • Hay una redirección que precede al token de etiqueta, y el comando está dentro de un bloque entre paréntesis.
      • El token de etiqueta es el primer comando en una línea dentro de un bloque entre paréntesis, y la línea de arriba terminó con una etiqueta sin errores .
    • Lo siguiente ocurre cuando se descubre una etiqueta ejecutada en la fase 2
      • La etiqueta, sus argumentos y su redirección están excluidos de cualquier salida de eco en la fase 3
      • Cualquier comando concatenado posterior en la línea se analiza y se ejecuta por completo.
    • Para obtener más información acerca de las etiquetas ejecutadas frente a las etiquetas no explicitadas , consulte https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Fase 3) Echo los comandos analizados solo si el bloque de comandos no comenzó con @ , y ECHO estaba ENCENDIDO al inicio del paso anterior.

Fase 4) FOR %X variable expansion: solo si un comando FOR está activo y los comandos después de DO están siendo procesados.

  • En este punto, la fase 1 del procesamiento por lotes ya habrá convertido una variable FOR como %%X en %X La línea de comando tiene diferentes reglas de porcentaje de expansión para la fase 1. Esta es la razón por la que las líneas de comando usan %X pero los archivos por lotes usan %%X para las variables FOR.
  • PARA los nombres de las variables son sensibles a mayúsculas y minúsculas, pero los ~modifiers no distinguen entre mayúsculas y minús
  • ~modifiers tienen prioridad sobre los nombres de las variables. Si un carácter que sigue ~ es a la vez un modificador y un nombre de variable FOR válido, y existe un carácter subsiguiente que es un nombre de variable FOR activo, entonces el carácter se interpreta como un modificador.
  • PARA los nombres de variable son globales, pero solo dentro del contexto de una cláusula DO. Si una rutina se LLAMA desde dentro de una cláusula FOR DO, las variables FOR no se expanden dentro de la rutina CALLed. Pero si la rutina tiene su propio comando FOR, todas las variables FOR definidas actualmente son accesibles para los comandos DO internos.
  • PARA nombres de variables se pueden reutilizar dentro de FOR anidados. El valor for interno tiene prioridad, pero una vez que INNER FOR se cierra, se restablece el valor for externo.
  • Si ECHO estaba ENCENDIDO al inicio de esta fase, entonces la fase 3) se repite para mostrar los comandos DO procesados ​​después de que las variables FOR hayan sido expandidas.

---- A partir de este momento, cada comando identificado en la fase 2 se procesa por separado.
---- Las fases 5 a 7 se completan para un comando antes de pasar al siguiente.

Fase 5) Expansión retrasada: solo si la expansión retrasada está activada

  • Si el comando está dentro de un bloque entre paréntesis en cualquier lado de un tubo, omita este paso.
  • Cada token de un comando se analiza para la expansión retrasada de forma independiente.
    • La mayoría de los comandos analizan dos o más tokens: el token de comando, el token de los argumentos y cada token de destino de redirección.
    • El comando FOR analiza el token de cláusula IN solamente.
    • El comando IF solo analiza los valores de comparación, ya sea uno o dos, dependiendo del operador de comparación.
  • Para cada token analizado, primero verifique si contiene alguno ! . De lo contrario, el token no se analiza, lo que es importante para los caracteres. Si el token contiene ! , luego escanea cada personaje de izquierda a derecha:
    • Si es un símbolo de intercalación ( ^ ), el siguiente carácter no tiene ningún significado especial, el símbolo de intercalación se elimina
    • Si se trata de un signo de exclamación, busque el siguiente signo de admiración (no se observan las referencias), expanda al valor de la variable.
      • Apertura consecutiva ! están colapsados ​​en un solo !
      • Cualquier restante ! que no se puede emparejar se quita
    • Importante: en esta fase, se ignoran las comillas y otros caracteres especiales
    • La expansión de vars en esta etapa es "segura", porque los caracteres especiales ya no se detectan (incluso <CR> o <LF> )
    • Para una explicación más completa, lea la segunda mitad de este dbenham mismo hilo - Fase de Exclamación
    • Hay algunos casos extremos donde estas reglas parecen fallar:
      Ver La expansión retrasada falla en algunos casos

Fase 5.3) Procesamiento de tuberías: solo si los comandos están a ambos lados de un tubo
Cada lado de la tubería se procesa de manera independiente.

  • Si se trata de un bloque de comando entre paréntesis, todos los <LF> con un comando anterior y posterior se convierten a <space>& . Otros <LF> son despojados.
  • El comando (o bloque de comandos) se ejecuta de forma asíncrona en un nuevo hilo cmd.exe a través de
    %comspec% /S /D /c" commandBlock" . Esto significa que el bloque de comando obtiene un reinicio de fase, pero esta vez en modo línea de comando.
  • Este es el final del proceso para los comandos de tubería.
  • Para obtener más información sobre cómo se procesan y procesan las tuberías, observe esta pregunta y sus respuestas: ¿Por qué la expansión demorada falla cuando se encuentra dentro de un bloque de código procesado ?

Fase 5.5) Ejecutar redirección: Cualquier redirección que se descubrió en la fase 2 ahora se ejecuta.

  • Los resultados de las fases 4 y 5 pueden afectar la redirección que se descubrió en la fase 2.
  • Si la redirección falla, entonces el resto del comando se cancela. Tenga en cuenta que la redirección fallida no establece ERRORLEVEL en 1 a menos que || es usado .

Fase 6) Procesamiento de LLAMADA / Duplicación de Caret: solo si el token de comando es CALL, o si el texto antes del primer delimitador de token estándar que se produce es CALL. Si CALL se analiza desde un token de comando más grande, la porción no utilizada se antepone al token de argumentos antes de continuar.

  • Escanee el token de argumentos para un /? comillas . Si se encuentra en cualquier lugar dentro de los tokens, anule la fase 6 y proceda a la Fase 7, donde se imprimirá HELP for CALL.
  • Elimine la primera CALL , de modo que se puedan apilar varias LLAMADAS
  • Doblar todas las carátulas
  • Reinicie las fases 1, 1.5 y 2, pero no continúe con la fase 3
    • Las referencias dobles se reducen a una intercalación siempre que no se coticen. Pero desafortunadamente, los careos cotizados siguen duplicados.
    • El bloque de comando en caché ya se ha preparado en la ronda original de la fase 2. Muchas de las tareas de la fase 2 están alteradas
      • Se detecta cualquier nueva redirección no esquivada sin cita que no se detectó en la primera ronda de la fase 2, pero se elimina (incluido el nombre del archivo) sin realizar la redirección.
      • Cualquier intercalación que no aparezca en la lista y que aparezca al final de la línea se eliminará sin realizar la continuación de línea.
      • La llamada se cancela sin error si se detecta cualquiera de los siguientes
        • Apareciendo sin comillas, sin escapada & |
        • El token de comando resultante comienza con sin comillas, sin guardar (
        • La primera ficha después de la llamada eliminada comenzó con @
      • Si el comando resultante es un IF o FOR aparentemente válido, la ejecución fallará posteriormente con un error que indica que IF o FOR no se reconocen como un comando interno o externo.
      • Por supuesto, la LLAMADA no se cancela en esta segunda ronda de la fase 2 si la ficha de comando resultante es una etiqueta que comienza con :
    • Hay algunos casos extremos donde estas reglas fallan:
      Consulte Examen de alimentaciones de línea con LLAMADA
  • Si el token de comando resultante es CALL, reinicie la Fase 6 (se repite hasta que deje de LLAMAR)
  • Si el token de comando resultante es un script de proceso por lotes o una etiqueta:, la ejecución de la llamada se maneja completamente con el resto de la fase 6.
    • Empuje la posición del archivo de secuencia de proceso actual en la pila de llamadas para que la ejecución pueda reanudarse desde la posición correcta cuando se completa la LLAMADA.
    • Configure los tokens de argumento% 0,% 1,% 2, ...% N y% * para CALL, utilizando todos los tokens resultantes
    • Si el token de comando es una etiqueta que comienza con : entonces
      • Reinicie la Fase 5. Esto puede afectar lo que: etiqueta se LLAMA. Pero como los tokens% 0 etc. ya se han configurado, no alterará los argumentos que se pasan a la rutina CALLed.
      • Ejecute la etiqueta GOTO para colocar el puntero del archivo al principio de la subrutina (ignore cualquier otro token que pueda seguir la etiqueta:) Consulte la Fase 7 para ver las reglas sobre cómo funciona GOTO.
        • Si falta el token de la etiqueta: o no se encuentra la etiqueta:, la pila de llamadas se hace saltar inmediatamente para restaurar la posición del archivo guardado, y la llamada se interrumpe.
        • Si la etiqueta: contiene /?, Se imprime la ayuda GOTO en lugar de buscar la etiqueta: El puntero del archivo no se mueve, de modo que el código después de la LLAMADA se ejecuta dos veces, una vez en el contexto de LLAMADA y luego otra vez después del retorno LLAMADA. Consulte ¿Por qué CALL imprime el mensaje de ayuda GOTO en este script? ¿Y por qué el comando después de eso se ejecuta dos veces? para más información.
    • Else otro control de transferencia al script por lotes especificado.
    • La ejecución de la etiqueta CALLed: o la secuencia de comandos continúa hasta que se llega a EXIT / B o fin de archivo, momento en el que la pila CALL se abre y la ejecución se reanuda desde la posición del archivo guardado.
      La fase 7 no se ejecuta para scripts LLAMADOS o: etiquetas.
  • De lo contrario, el resultado de la fase 6 cae en la fase 7 para su ejecución.

Fase 7) Ejecutar: el comando se ejecuta

  • 7.1 - Ejecutar comando interno - Si el token de comando está entre comillas, omita este paso. De lo contrario, intente analizar un comando interno y ejecutarlo.
    • Las siguientes pruebas se realizan para determinar si un token de comando sin comillas representa un comando interno:
      • Si el token de comando coincide exactamente con un comando interno, ejecútelo.
      • De lo contrario, romper el token de comando antes de la primera aparición de + / [ ] <space> <tab> ; o =
        Si el texto anterior es un comando interno, recuerda ese comando
        • Si está en el modo de línea de comandos, o si el comando es de un bloque entre paréntesis, si el bloque de comandos es verdadero o falso, para el bloque de comandos DO DO, o involucrado con la concatenación de comandos, entonces ejecute el comando interno
        • Else (debe ser un comando autónomo en modo por lotes) escanee la carpeta actual y la RUTA de un archivo .COM, .EXE, .BAT o .CMD cuyo nombre base coincida con el token de comando original
          • Si el primer archivo coincidente es un .BAT o .CMD, goto 7.3.exec y ejecuta ese script
          • Else (coincidencia no encontrada o primera coincidencia es .EXE o .COM) ejecute el comando interno recordado
      • De lo contrario, romper el token de comando antes de la primera aparición de . / o :
        Si el texto anterior no es un comando interno, entonces goto 7.2
        De lo contrario, el texto anterior puede ser un comando interno. Recuerda este comando.
      • Divida el token de comando antes de la primera aparición de + / [ ] <space> <tab> ; o =
        Si el texto anterior es una ruta a un archivo existente, entonces pase a 7.2
        De lo contrario, ejecute el comando interno que se recuerda.
    • Si un comando interno se analiza desde un token de comando más grande, la porción no utilizada del token de comando se incluye en la lista de argumentos
    • El hecho de que un token de comando se analice como un comando interno no significa que se ejecutará correctamente. Cada comando interno tiene sus propias reglas sobre cómo se analizan los argumentos y las opciones, y qué sintaxis se permite.
    • Todos los comandos internos imprimirán ayuda en lugar de realizar su función si /? es detectado. La mayoría reconoce /? si aparece en cualquier parte de los argumentos. Pero algunos comandos como ECHO y SET solo imprimen ayuda si el primer token de argumento comienza con /? .
    • SET tiene una semántica interesante:
      • Si un comando SET tiene una cita antes del nombre de la variable
        set "name=content" ignored -> valor = content
        luego, el texto entre el primer signo igual y la última cita se utiliza como contenido (se excluyen la primera y la última cita). El texto después de la última cita se ignora. Si no hay cotización después del signo igual, entonces el resto de la línea se usa como contenido.
      • Si un comando SET no tiene una cita antes del nombre
        set name="content" not ignored -> value = "content" not ignored
        a continuación, el resto de la línea después de la igualdad se utiliza como contenido, incluidas todas las citas que pueden estar presentes.
    • Se evalúa una comparación IF, y dependiendo de si la condición es verdadera o falsa, se procesa el bloque de comando dependiente apropiado ya analizado, comenzando con la fase 5.
    • La cláusula IN de un comando FOR se itera apropiadamente.
      • Si esto es un FOR / F que itera la salida de un bloque de comando, entonces:
        • La cláusula IN se ejecuta en un nuevo proceso cmd.exe a través de CMD / C.
        • El bloque de comandos debe pasar por todo el proceso de análisis una segunda vez, pero esta vez en un contexto de línea de comando
        • ECHO se iniciará en ON, y la expansión demorada generalmente comenzará desactivada (dependiendo de la configuración del registro)
        • Todos los cambios de entorno realizados por el bloque de comandos de la cláusula IN se perderán una vez que finalice el proceso de cmd.exe secundario.
      • Para cada iteración:
        • Los valores de la variable FOR están definidos
        • El bloque de comando DO ya procesado se procesa, comenzando con la fase 4.
    • GOTO usa la siguiente lógica para ubicar la etiqueta:
      • La etiqueta se analiza desde el primer token de argumento
      • El script se escanea en busca de la siguiente aparición de la etiqueta
        • El escaneo comienza desde la posición actual del archivo
        • Si se llega al final del archivo, el escaneo vuelve al principio del archivo y continúa hasta el punto de inicio original.
      • El escaneo se detiene en la primera aparición de la etiqueta que encuentra, y el puntero del archivo se establece en la línea inmediatamente después de la etiqueta. La ejecución del script se reanuda desde ese punto. Tenga en cuenta que un verdadero GOTO exitoso abortará inmediatamente cualquier bloque de código analizado, incluidos los bucles FOR.
      • Si no se encuentra la etiqueta, o si falta el token de la etiqueta, entonces el GOTO falla, se imprime un mensaje de error y aparece la pila de llamadas. Esto funciona efectivamente como EXIT / B, excepto que todos los comandos ya procesados ​​en el bloque de comandos actual que siguen a GOTO se siguen ejecutando, pero en el contexto de CALLer (el contexto que existe después de EXIT / B)
      • Consulte https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 para obtener una descripción más precisa de las reglas utilizadas para analizar las etiquetas.
    • RENAME y COPY ambos aceptan comodines para las rutas de origen y destino. Pero Microsoft hace un trabajo terrible documentando cómo funcionan los comodines, especialmente para la ruta de destino. Se puede encontrar un conjunto útil de reglas de comodines en ¿Cómo interpreta el comando RENOMBRE de Windows los comodines?
  • 7.2 - Ejecutar cambio de volumen - Else si el token de comando no comienza con una cita, tiene exactamente dos caracteres de longitud, y el segundo carácter es dos puntos, luego cambia el volumen
    • Todos los tokens de argumento son ignorados
    • Si el volumen especificado por el primer carácter no se puede encontrar, entonces cancela con un error
    • Un token de comando de :: siempre dará como resultado un error a menos que se use SUBST para definir un volumen para ::
      Si SUBST se usa para definir un volumen para :: , entonces el volumen se cambiará, no se tratará como una etiqueta.
  • 7.3 - Ejecutar comando externo - De lo contrario, intente tratar el comando como un comando externo.
    • Si el segundo carácter del token de comando es dos puntos, entonces verifique que se encuentre el volumen especificado por el primer carácter.
      Si no se puede encontrar el volumen, interrumpa con un error.
    • Si está en modo batch y el token de comando comienza con : luego ir a 7.4
      Tenga en cuenta que si el token de etiqueta comienza con :: , entonces no se alcanzará porque el paso anterior habrá abortado con un error a menos que se utilice SUBST para definir un volumen para :: .
    • Identifica el comando externo para ejecutar.
      • Este es un proceso complejo que puede involucrar el volumen actual, el directorio actual, la variable PATH, la variable PATHEXT y / o las asociaciones de archivos.
      • Si no se puede identificar un comando externo válido, interrumpa con un error.
    • Si está en el modo de línea de comando y el token de comando comienza con : luego ir a 7.4
      Tenga en cuenta que esto rara vez se alcanza porque el paso anterior se abortará con un error a menos que el token comience con :: , y SUBST se usa para definir un volumen para :: , y el token de comando completo es una ruta válida para un comando externo .
    • 7.3.exec - Ejecuta el comando externo.
  • 7.4 - Ignorar una etiqueta - Ignora el comando y todos sus argumentos si el token de comando comienza con :
    Las reglas en 7.2 y 7.3 pueden evitar que una etiqueta llegue a este punto.

Analizador de línea de comandos:

Funciona como el BatchLine-Parser, excepto:

Fase 1) Expansión porcentual:

  • %var% sigue siendo reemplazado por el contenido de var, pero si var no está definido, entonces la expresión no cambiará.
  • Sin manejo especial de %% . Si var = content, entonces %%var%% expande a %content% .

Fase 3) Echo los comandos analizados

  • Esto no se realiza después de la fase 2. Solo se realiza después de la fase 4 para el bloque de comandos FOR DO.

Fase 5) Expansión retrasada: solo si la demora retrasada está habilitada

  • !var! aún se reemplaza por el contenido de var, pero si var no está definido, la expresión no cambiará.

Fase 7) Ejecutar comando

  • Los intentos de LLAMAR o GOLPEAR a: etiqueta dan como resultado un error.
  • Aunque no se pueden llamar las etiquetas, una línea válida puede contener una etiqueta. Como ya se documentó en la fase 7, una etiqueta ejecutada puede generar un error en diferentes escenarios.
    • Las etiquetas ejecutadas por lotes solo pueden causar un error si comienzan con ::
    • Las etiquetas ejecutadas en la línea de comando casi siempre dan como resultado un error

Análisis de valores enteros

Hay muchos contextos diferentes donde cmd.exe analiza valores enteros de cadenas y las reglas son inconsistentes:

  • SET /A
  • IF
  • %var:~n,m% (expansión de subcadena variable)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Los detalles de estas reglas se pueden encontrar en Reglas para ver cómo CMD.EXE analiza los números

Para cualquiera que desee mejorar estas reglas, hay un tema de discusión en el foro DosTips donde se pueden informar problemas y hacer sugerencias.

Espero eso ayude
Jan Erik (jeb) - Autor original y descubridor de las diversas fases
Dave Benham (dbenham) - Mucho contenido adicional y edición


As pointed out, commands are passed the entire argument string in μSoft land, and it is up to them to parse this into separate arguments for their own use. There is no consistencty in this between different programs, and therefore there is no one set of rules to describe this process. You really need to check each corner case for whatever C library your program uses.

As far as the system .bat files go, here is that test:

c> type args.cmd @echo off echo cmdcmdline:[%cmdcmdline%] echo 0:[%0] echo *:[%*] set allargs=%* if not defined allargs goto :eof setlocal @rem Wot about a nice for loop? @rem Then we are in the land of delayedexpansion, !n!, call, etc. @rem Plays havoc with args like %t%, a"b etc. ugh! set n=1 :loop echo %n%:[%1] set /a n+=1 shift set param=%1 if defined param goto :loop endlocal

Now we can run some tests. See if you can figure out just what μSoft are trying to do:

C>args a b c cmdcmdline:[cmd.exe ] 0:[args] *:[a b c] 1:[a] 2:[b] 3:[c]

Fine so far. (I''ll leave out the uninteresting %cmdcmdline% and %0 from now on.)

C>args *.* *:[*.*] 1:[*.*]

No filename expansion.

C>args "a b" c *:["a b" c] 1:["a b"] 2:[c]

No quote stripping, though quotes do prevent argument splitting.

c>args ""a b" c *:[""a b" c] 1:[""a] 2:[b" c]

Consecutive double quotes causes them to lose any special parsing abilities they may have had. @Beniot''s example:

C>args "a """ b "" c""" *:["a """ b "" c"""] 1:["a """] 2:[b] 3:[""] 4:[c"""]

Quiz: How do you pass the value of any environment var as a single argument (ie, as %1 ) to a bat file?

c>set t=a "b c c>set t t=a "b c c>args %t% 1:[a] 2:["b c] c>args "%t%" 1:["a "b] 2:[c"] c>Aaaaaargh!

Sane parsing seems forever broken.

For your entertainment, try adding miscellaneous ^ , / , '' , & (&c.) characters to these examples.


Here is an expanded explanation of Phase 1 in jeb''s answer (Valid for both batch mode and command line mode).

Phase 1) Percent Expansion Starting from left, scan each character for % . If found then

  • 1.1 (escape % ) skipped if command line mode
    • If batch mode and followed by another % then
      Replace %% with single % and continue scan
  • 1.2 (expand argument) skipped if command line mode
    • Else if batch mode then
      • If followed by * and command extensions are enabled then
        Replace %* with the text of all command line arguments (Replace with nothing if there are no arguments) and continue scan.
      • Else if followed by <digit> then
        Replace %<digit> with argument value (replace with nothing if undefined) and continue scan.
      • Else if followed by ~ and command extensions are enabled then
        • If followed by optional valid list of argument modifiers followed by required <digit> then
          Replace %~[modifiers]<digit> with modified argument value (replace with nothing if not defined or if specified $PATH: modifier is not defined) and continue scan.
          Note: modifiers are case insensitive and can appear multiple times in any order, except $PATH: modifier can only appear once and must be the last modifier before the <digit>
        • Else invalid modified argument syntax raises fatal error: All parsed commands are aborted, and batch processing aborts if in batch mode!
  • 1.3 (expand variable)
    • Else if command extensions are disabled then
      Look at next string of characters, breaking before % or <LF> , and call them VAR (may be an empty list)
      • If next character is % then
        • If VAR is defined then
          Replace %VAR% with value of VAR and continue scan
        • Else if batch mode then
          Remove %VAR% and continue scan
        • Else goto 1.4
      • Else goto 1.4
    • Else if command extensions are enabled then
      Look at next string of characters, breaking before % : or <LF> , and call them VAR (may be an empty list). If VAR breaks before : and the subsequent character is % then include : as the last character in VAR and break before % .
      • If next character is % then
        • If VAR is defined then
          Replace %VAR% with value of VAR and continue scan
        • Else if batch mode then
          Remove %VAR% and continue scan
        • Else goto 1.4
      • Else if next character is : then
        • If VAR is undefined then
          • If batch mode then
            Remove %VAR: and continue scan.
          • Else goto 1.4
        • Else if next character is ~ then
          • If next string of characters matches pattern of [integer][,[integer]]% then
            Replace %VAR:~[integer][,[integer]]% with substring of value of VAR (possibly resulting in empty string) and continue scan.
          • Else goto 1.4
        • Else if followed by = or *= then
          Invalid variable search and replace syntax raises fatal error: All parsed commands are aborted, and batch processing aborts if in batch mode!
        • Else if next string of characters matches pattern of [*]search=[replace]% , where search may include any set of characters except = and <LF> , and replace may include any set of characters except % and <LF> , then replace
          %VAR:[*]search=[replace]% with value of VAR after performing search and replace (possibly resulting in empty string) and continue scan
        • Else goto 1.4
  • 1.4 (strip %)
    • Else If batch mode then
      Remove % and continue with scan
    • Else preserve % and continue scan

The above helps explain why this batch

@echo off setlocal enableDelayedExpansion set "1var=varA" set "~f1var=varB" call :test "arg1" exit /b :: :test "arg1" echo %%1var%% = %1var% echo ^^^!1var^^^! = !1var! echo -------- echo %%~f1var%% = %~f1var% echo ^^^!~f1var^^^! = !~f1var! exit /b

Gives these results:

%1var% = "arg1"var !1var! = varA -------- %~f1var% = P:/arg1var !~f1var! = varB

Note 1 - Phase 1 occurs prior to the recognition of REM statements. This is very important because it means even a remark can generate a fatal error if it has invalid argument expansion syntax or invalid variable search and replace syntax!

@echo off rem %~x This generates a fatal argument expansion error echo this line is never reached

Note 2 - Another interesting consequence of the % parsing rules: Variables containing : in the name can be defined, but they cannot be expanded unless command extensions are disabled. There is one exception - a variable name containing a single colon at the end can be expanded while command extensions are enabled. However, you cannot perform substring or search and replace operations on variable names ending with a colon. The batch file below (courtesy of jeb) demonstrates this behavior

@echo off setlocal set var=content set var:=Special set var::=double colon set var:~0,2=tricky set var::~0,2=unfortunate echo %var% echo %var:% echo %var::% echo %var:~0,2% echo %var::~0,2% echo Now with DisableExtensions setlocal DisableExtensions echo %var% echo %var:% echo %var::% echo %var:~0,2% echo %var::~0,2%

Note 3 - An interesting outcome of the order of the parsing rules that jeb lays out in his post: When performing search and replace with normal expansion, special characters should NOT be escaped (though they may be quoted). But when performing search and replace with delayed expansion, special characters MUST be escaped (unless they are quoted).

@echo off setlocal enableDelayedExpansion set "var=this & that" echo %var:&=and% echo "%var:&=and%" echo !var:^&=and! echo "!var:&=and!"

Here is an expanded, and more accurate explanation of phase 5 in jeb''s answer (Valid for both batch mode and command line mode)

Note there are some edge cases where these rules fail:
See Examination of Linefeeds with CALL

Phase 5) Delayed Expansion Only if delayed expansion is enabled, and the line contains at least one ! , then Starting from left, scan each character for ^ or ! , and if found, then

  • 5.1 (caret escape) Needed for ! or ^ literals
    • If character is a caret ^ then
      • Remove the ^
      • Scan the next character and preserve it as a literal
      • Continue the scan
  • 5.2 (expand variable)
    • If character is ! , entonces
      • If command extensions are disabled then
        Look at next string of characters, breaking before ! or <LF> , and call them VAR (may be an empty list)
        • If next character is ! entonces
          • If VAR is defined, then
            Replace !VAR! with value of VAR and continue scan
          • Else if batch mode then
            Remove !VAR! and continue scan
          • Else goto 5.2.1
        • Else goto 5.2.1
      • Else if command extensions are enabled then
        Look at next string of characters, breaking before ! , : , or <LF> , and call them VAR (may be an empty list). If VAR breaks before : and the subsequent character is ! then include : as the last character in VAR and break before !
        • If next character is ! entonces
          • If VAR exists, then
            Replace !VAR! with value of VAR and continue scan
          • Else if batch mode then
            Remove !VAR! and continue scan
          • Else goto 5.2.1
        • Else if next character is : then
          • If VAR is undefined then
            • If batch mode then
              Remove !VAR: and continue scan
            • Else goto 5.2.1
          • Else if next string of characters matches pattern of
            ~[integer][,[integer]]! entonces
            Replace !VAR:~[integer][,[integer]]! with substring of value of VAR (possibly resulting in an empty string) and continue scan
          • Else if next string of characters matches pattern of [*]search=[replace]! , where search may include any set of characters except = and <LF> , and replace may include any set of characters except ! and <LF> , then
            Replace !VAR:[*]search=[replace]! with value of VAR after performing search and replace (possibly resulting in an empty string) and continue scan
          • Else goto 5.2.1
        • Else goto 5.2.1
      • 5.2.1
        • If batch mode then remove the !
          Else preserve the !
        • Continue the scan starting with the next character after the !

You have some great answers above already, but to answer one part of your question:

set a =b, echo %a %b% c% → bb c%

What is happening there is that because you have a space before the =, a variable is created called %a<space>% so when you echo %a % that is evaluated correctly as b .

The remaining part b% c% is then evaluated as plain text + an undefined variable % c% , which should be echoed as typed, for me echo %a %b% c% returns bb% c%

I suspect that the ability to include spaces in variable names is more of an oversight than a planned ''feature''


edit: see accepted answer, what follows is wrong and explains only how to pass a command line to TinyPerl.

Regarding quotes, I have the feeling that the behaviour is the following:

  • when a " is found, string globbing begins
  • when string globbing occurs:
    • every character that is not a " is globbed
    • when a " is found:
      • if it is followed by "" (thus a triple " ) then a double quote is added to the string
      • if it is followed by " (thus a double " ) then a double quote is added to the string and string globbing ends
      • if the next character is not " , string globbing ends
    • when line ends, string globbing ends.

En breve:

"a """ b "" c""" consists of two strings: a " b " and c"

"a"" , "a""" and "a"""" are all the same string if at the end of a line