¿Cómo analiza el intérprete de comandos de Windows (CMD.EXE) los scripts?

142

Me encontré con ss64.com que proporciona una buena ayuda sobre cómo escribir scripts 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 scripts por lotes, cómo las cosas se expanden o no, y cómo escapar de las cosas.

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

  • ¿Cómo se gestiona el sistema de cotizaciones? Hice un script 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 """a b c""" → salida *"a*b*c"
  • ¿Cómo funciona el echocomando interno ? ¿Qué se expande dentro de ese comando?
  • ¿Por qué tengo que usar for [...] %%Ien scripts de archivo, pero for [...] %Ien 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 eco %PROCESSOR_ARCHITECTURE%literalmente? Encontré que echo.exe %""PROCESSOR_ARCHITECTURE%funciona, ¿hay una mejor solución?
  • ¿Cómo se %combinan los pares ? Ejemplo:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • ¿Cómo me aseguro de que una variable pase a un comando como argumento único si alguna vez esta variable contiene comillas dobles?
  • ¿Cómo se almacenan las variables cuando se usa el setcomando? Por ejemplo, si lo hago set a=a" by luego echo.%a%obtengo a" b. Sin embargo, si uso echo.exede UnxUtils, obtengo a b. ¿Cómo se %a%expande de una manera diferente?

Gracias por tus luces.

Benoit
fuente
Rob van der Woude tiene una increíble referencia de scripting por lotes y comandos de Windows en su sitio.
JBRWilkinson el

Respuestas:

200

Realizamos experimentos para investigar la gramática de los scripts por lotes. También investigamos las diferencias entre el modo de línea de comandos y el lote.

Analizador de línea de lote:

Aquí hay una breve descripción de las fases en el analizador de línea de archivo por lotes:

Fase 0) Leer línea:

Fase 1) Porcentaje de expansión:

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

Fase 3) Haga eco de los comandos analizados solo si el bloque de comandos no comenzó @y ECHO estaba ENCENDIDO al comienzo del paso anterior.

Fase 4) %XExpansión variable FOR : 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ía: solo si los comandos están a ambos lados de una tubería

Fase 5.5) Ejecutar redireccionamiento:

Fase 6) Procesamiento de LLAMADAS / Duplicación de Caret: solo si el token de comando es LLAMADA

Fase 7) Ejecutar: el comando se ejecuta


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 por lotes. Las partes internas reales de cmd.exe pueden no reflejar estas fases. Pero este modelo es efectivo para predecir el comportamiento de los scripts por lotes.

Fase 0) Leer línea: lea la línea de entrada hasta el primero <LF>.

  • Al leer una línea para analizarla como un comando, <Ctrl-Z>(0x1A) se lee como <LF>(LineFeed 0x0A)
  • Cuando GOTO o llamada se leen las líneas durante la exploración para un: etiqueta, <Ctrl-Z>, se trata como en sí - se no se convirtió al<LF>

Fase 1) Porcentaje de expansión:

  • Un doble %%es reemplazado por un sencillo%
  • La expansión de argumentos ( %*, %1, %2, etc.)
  • Expansión de %var%, si var no existe, reemplácelo con nada
  • La línea se trunca al principio <LF>no dentro de la %var%expansión
  • Para una explicación completa, lea la primera mitad de esto de dbenham Mismo hilo: Fase porcentual

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

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

  • Una ficha es simplemente una cadena de caracteres que se trata como una unidad.
  • Las fichas están separadas por delimitadores de fichas. Los delimitadores de token estándar son <space> <tab> ; , = <0x0B> <0x0C>y los <0xFF>
    delimitadores de token consecutivos se tratan como uno: no hay tokens vacíos entre los delimitadores de token
  • No hay delimitadores de token dentro de una cadena entre comillas. La cadena completa entre comillas siempre se trata como parte de un token único. Un solo 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: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

Mira cada personaje de izquierda a derecha:

  • Si <CR>luego, elimínelo, como si nunca hubiera estado allí (excepto por un extraño comportamiento de redireccionamiento )
  • Si un símbolo de intercalación ( ^), se escapa el siguiente carácter y se elimina el símbolo de interferencia de escape. Los personajes escapados pierden todo significado especial (excepto <LF>).
  • Si se trata de una cita ( "), alterna el indicador de cita. Si la bandera de cotización está activo, entonces solamente "y <LF>son especiales. Todos los demás personajes pierden su significado especial hasta que la próxima cita desactiva la bandera de la cita. No es posible escapar de la cita de cierre. Todos los caracteres entre comillas siempre están 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>.
    • Escapado <LF>
      • <LF> es despojado
      • El siguiente personaje se escapa. Si al final del búfer de línea, la siguiente línea se lee y procesa en las fases 1 y 1.5 y se agrega a la actual antes de escapar del siguiente carácter. Si el siguiente carácter es <LF>, entonces se trata como un literal, lo que significa que este proceso no es recursivo.
    • Sin escapes <LF>no entre paréntesis
      • <LF> se elimina y se termina el análisis de la línea actual.
      • Los caracteres restantes en el búfer de línea simplemente se ignoran.
    • Sin escapes <LF>dentro de un bloque entre paréntesis FOR IN
      • <LF> se convierte en un <space>
      • Si al final del búfer de línea, la siguiente línea se lee y se agrega a la actual.
    • Sin escapes <LF>dentro de un bloque de comandos entre paréntesis
      • <LF>se convierte <LF><space>y <space>se trata como parte de la siguiente línea del bloque de comandos.
      • Si al final del búfer de línea, la siguiente línea se lee y se agrega al espacio.
  • Si es uno de los caracteres especiales & | <o >, divida la línea en este punto para manejar tuberías, concatenación de comandos y redirección.
    • En el caso de una tubería ( |), cada lado es un comando separado (o bloque de comandos) que recibe un manejo especial en la fase 5.3
    • En el caso de &, &&o ||concatenación de comando, cada lado de la concatenación se trata como un comando separado.
    • En el caso de <, <<, >, o >>redirección, la cláusula de redirección se analiza, elimina temporalmente, y luego añade al final de la consigna de corriente. Una cláusula de redirección consta de 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 sin escape, entonces el dígito especifica el identificador de archivo que se debe redirigir. Si no se encuentra el token del identificador, la redirección de salida predeterminada es 1 (stdout) y la redirección de entrada predeterminada es 0 (stdin).
  • Si el primer token para este comando (antes de mover la redirección al final) comienza @, entonces @tiene un significado especial. ( @no es especial en ningún otro contexto)
    • El especial @se elimina.
    • Si ECHO está activado, este comando, junto con los siguientes comandos concatenados en esta línea, se excluyen del eco de la fase 3. Si @está antes de una apertura (, se excluye todo el bloque entre paréntesis del eco de fase 3.
  • Paréntesis de proceso (proporciona enunciados compuestos en varias 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 lo encuentra (, inicie una nueva declaración compuesta e incremente el contador de paréntesis
    • Si el contador de paréntesis es> 0, )termina la declaración compuesta y disminuye el contador de paréntesis.
    • Si se alcanza el 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 está buscando un comando, entonces )funciona de manera similar a una REMdeclaración siempre que sea seguido inmediatamente por un delimitador de token, un carácter especial, una nueva línea o el final del archivo
      • Todos los caracteres especiales pierden su significado excepto ^(es posible la concatenación de líneas)
      • Una vez que se alcanza el 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 los especiales @se hayan eliminado y la redirección se haya movido al 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 generalmente 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 dará como resultado un error de sintaxis fatal.
      • La operación de comparación es el comando real que fluye hasta la fase 7
        • Todas las opciones de IF se analizan completamente en la fase 2.
        • Los delimitadores de tokens consecutivos colapsan en un solo espacio.
        • Dependiendo del operador de comparación, habrá uno o dos tokens de valor que se identificarán.
      • 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 False opcional es el conjunto de comandos después de ELSE. De nuevo, este bloque de comando se analiza normalmente.
      • Los bloques de comandos Verdadero y Falso no fluyen automáticamente a las fases posteriores. Su procesamiento posterior es controlado por la fase 7.
    • FOR se divide en dos después de la 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 completamente en la fase 2.
        • La cláusula IN entre paréntesis trata <LF>como <space>. Después de analizar la cláusula IN, todos los tokens se concatenan juntos para formar un solo token.
        • Los delimitadores de tokens consecutivos sin escape / sin comillas colapsan en un solo espacio a lo largo del comando FOR a través de DO.
      • La porción después de DO es un bloque de comando 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 dramáticamente diferente que todos los otros comandos.
      • Solo se analiza un token de argumento: el analizador ignora los caracteres después del primer token de argumento.
      • El comando REM puede aparecer en la salida de la fase 3, pero el comando nunca se ejecuta y se repite el texto del argumento original: no se eliminan los escapes, excepto ...
        • Si solo hay un token de argumento que termina con un no escapado ^que termina la línea, entonces el token de argumento se descarta y la línea posterior se analiza y se agrega al REM. Esto se repite hasta que haya más de una ficha o el último carácter no ^.
  • 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 no ejecutada .
      • El resto de la línea se analiza, sin embargo ), <, >, &y |ya no tienen un significado especial. Todo el resto de la línea se considera parte de la etiqueta "comando".
      • El ^sigue siendo especial, lo que significa que de continuación de línea se puede utilizar para añadir una línea posterior 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 seguido inmediatamente por un comando o una etiqueta ejecutada en la siguiente línea.
        • (ya no tiene un significado especial para el primer comando que sigue a la etiqueta no ejecutada .
      • El comando se cancela después de completar el análisis de etiquetas. Las fases posteriores no tienen lugar para la etiqueta
    • Existen tres excepciones que pueden hacer que una etiqueta que se encuentra en la fase 2 se trate como una etiqueta ejecutada que continúa analizando a través de la fase 7.
      • No hay cambio de dirección que precede a la etiqueta de la señal, y hay una |tubería o &, &&o ||concatenación de comando en la línea.
      • Hay una redirección que precede al token de la 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 no ejecutada .
    • 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 ejecuta por completo.
    • Para obtener más información sobre las etiquetas Ejecutado vs. etiquetas no ejecutados , ver https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Fase 3) Haga eco de los comandos analizados solo si el bloque de comandos no comenzó @y ECHO estaba ENCENDIDO al comienzo del paso anterior.

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

  • En este punto, la fase 1 de procesamiento por lotes ya se habrá convertido una para la variable como %%Xen %X. La línea de comando tiene diferentes reglas de expansión porcentual para la fase 1. Esta es la razón por la que las líneas de comando usan %Xpero los archivos %%Xpor lotes usan las variables FOR.
  • Los nombres de las variables FOR distinguen entre mayúsculas y minúsculas, pero ~modifiersno distinguen entre mayúsculas y minúsculas.
  • ~modifierstienen prioridad sobre los nombres de variables. Si el siguiente carácter ~es un modificador y un nombre de variable FOR válido, y existe un carácter posterior que es un nombre de variable FOR activo, entonces el carácter se interpreta como un modificador.
  • Los nombres de las variables FOR son globales, pero solo dentro del contexto de una cláusula DO. Si una rutina se llama desde 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, entonces todas las variables FOR definidas actualmente son accesibles para los comandos DO internos.
  • Los nombres de variables FOR se pueden reutilizar dentro de FOR anidados. El valor FOR interno tiene prioridad, pero una vez que se cierra el FOR interno, se restaura el valor FOR externo.
  • Si ECHO estaba ENCENDIDO al comienzo de esta fase, entonces se repite la fase 3) para mostrar los comandos de OD analizados después de que las variables FOR se hayan expandido.

---- A partir de este punto, 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, el comando no está en un bloque entre paréntesis a cada lado de una tubería , y el comando no es un script por lotes "desnudo" (nombre del script sin paréntesis, CALL, concatenación de comandos, o tubería).

  • Cada token para un comando se analiza para una 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 argumentos y cada token de destino de redirección.
    • El comando FOR analiza solo el token de la cláusula IN.
    • El comando IF solo analiza los valores de comparación, ya sea uno o dos, dependiendo del operador de comparación.
  • Para cada ficha analizada, primero verifique si contiene alguna !. Si no, entonces el token no se analiza, importante para los ^personajes. Si el token contiene !, entonces escanea cada personaje de izquierda a derecha:
    • Si se trata de un símbolo de intercalación ( ^), el siguiente carácter no tiene un significado especial, el símbolo de intercalación en sí mismo se elimina
    • Si se trata de un signo de exclamación, busque el siguiente signo de exclamación (ya no se observan los signos de interrogación), amplíe el valor de la variable.
      • Las aperturas consecutivas !se colapsan en una sola!
      • Cualquier resto no emparejado !se elimina
    • La expansión de vars en esta etapa es "segura", ya que los caracteres especiales ya no se detectan (incluso <CR>o <LF>)
    • Para una explicación más completa, lea la segunda mitad de esto del mismo hilo de dbenham - Fase del punto de exclamación

Fase 5.3) Procesamiento de tubería: solo si los comandos están a cada lado de una tubería
Cada lado de la tubería se procesa de forma independiente y asíncrona.

  • Si el comando es interno a cmd.exe, o es un archivo por lotes, o si es un bloque de comando entre paréntesis, entonces se ejecuta en un nuevo hilo cmd.exe a través de %comspec% /S /D /c" commandBlock", por lo que el bloque de comando obtiene un reinicio de fase, pero esta vez en modo de línea de comando.
    • Si se <LF>trata de un bloque de comandos entre paréntesis, todos los que tienen un comando antes y después se convierten a <space>&. Otros <LF>son despojados.
  • Este es el final del procesamiento de los comandos de tubería.
  • Consulte ¿Por qué falla la expansión retrasada cuando se encuentra dentro de un bloque de código canalizado? para más información sobre el análisis y procesamiento de tuberías

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

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 es CALL. Si CALL se analiza desde un token de comando más grande, la parte no utilizada se antepone al token de argumentos antes de continuar.

  • Escanee el token de argumentos para encontrar uno sin comillas /?. Si se encuentra en algún lugar dentro de los tokens, entonces cancele la fase 6 y continúe con la Fase 7, donde se imprimirá la AYUDA para la LLAMADA.
  • Elimine el primero CALL, para que se puedan apilar varias LLAMADAS
  • Doblar todos los cuidados
  • Reinicie las fases 1, 1.5 y 2, pero no continúe con la fase 3
    • Cualquier cuidado duplicado se reduce de nuevo a un cuidado, siempre que no se cite. Pero desafortunadamente, las citas citadas siguen duplicadas.
    • La fase 1 cambia un poco
      • Los errores de expansión en el paso 1.2 o 1.3 anulan la LLAMADA, pero el error no es fatal: el procesamiento por lotes continúa.
    • Las tareas de la fase 2 se alteran un poco
      • Se detecta cualquier nueva redirección sin comillas y sin escape que no se detectó en la primera ronda de la fase 2, pero se elimina (incluido el nombre del archivo) sin realizar realmente la redirección
      • Se elimina cualquier caret sin comillas y sin escape que aparece recientemente al final de la línea sin realizar la continuación de la línea
      • La LLAMADA se cancela sin error si se detecta alguno de los siguientes
        • Recién aparecido sin comillas, sin escapes &o|
        • El token de comando resultante comienza con sin comillas, sin escape (
        • La primera ficha después de que la LLAMADA eliminada comenzó con @
      • Si el comando resultante es un IF o FOR aparentemente válido, entonces la ejecución fallará posteriormente con un error que indica que IFo FORno se reconoce como un comando interno o externo.
      • Por supuesto, la LLAMADA no se cancela en esta segunda ronda de la fase 2 si el token de comando resultante es una etiqueta que comienza con :.
  • Si el token de comando resultante es CALL, reinicie la Fase 6 (se repite hasta que no haya más CALL)
  • Si el token de comando resultante es un script por lotes o una etiqueta:, el resto de la Fase 6 maneja completamente la ejecución de la LLAMADA.
    • Empuje la posición actual del archivo de script por lotes en la pila de llamadas para que la ejecución pueda reanudarse desde la posición correcta cuando se complete 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: la etiqueta está LLAMADA. Pero dado que 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 comienzo de la subrutina (ignore cualquier otra ficha que pueda seguir a la etiqueta:) Consulte la Fase 7 para conocer las reglas sobre cómo funciona GOTO.
    • De lo contrario, transfiera el control al script por lotes especificado.
    • La ejecución de CALLed: etiqueta o secuencia de comandos continúa hasta que se alcanza EXIT / B o fin de archivo, momento en el que se abre la pila CALL y la ejecución se reanuda desde la posición guardada del archivo.
      La fase 7 no se ejecuta para scripts CALLed 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 se cita el token de comando, omita este paso. De lo contrario, intente analizar un comando interno y ejecutar.
    • 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, rompa el token de comando antes de la primera aparición de + / [ ] <space> <tab> , ;o =
        Si el texto anterior es un comando interno, recuerde ese comando
        • Si está en modo de línea de comando, o si el comando proviene de un bloque entre paréntesis, SI es un bloque de comando verdadero o falso, un bloque de comando FOR DO o está involucrado con la concatenación de comandos, ejecute el comando interno
        • De lo contrario (debe ser un comando independiente en modo por lotes) escanee la carpeta actual y la RUTA en busca de un archivo .COM, .EXE, .BAT o .CMD cuyo nombre base coincida con el token de comando original
          • Si el primer archivo coincidente es .BAT o .CMD, vaya a 7.3.exec y ejecute ese script
          • De lo contrario (coincidencia no encontrada o primera coincidencia es .EXE o .COM) ejecute el comando interno recordado
      • De lo contrario, rompa el token de comando antes de la primera aparición de . \o :
        Si el texto anterior no es un comando interno, vaya a 7.2. De lo
        contrario, el texto anterior puede ser un comando interno. Recuerda este comando.
      • Rompa 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, vaya a 7.2 De lo
        contrario, ejecute el comando interno recordado.
    • Si un comando interno se analiza desde un token de comando más grande, la parte 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á con éxito. Cada comando interno tiene sus propias reglas sobre cómo se analizan los argumentos y las opciones, y qué sintaxis está permitida.
    • Todos los comandos internos imprimirán ayuda en lugar de realizar su función si /?se detecta. La mayoría reconoce /?si aparece en alguna 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 comilla antes de habilitar el nombre de la variable y las extensiones
        set "name=content" ignored -> valor = content
        entonces el texto entre el primer signo igual y la última comilla se usa como contenido (se excluyen la primera comilla y la última comilla). El texto después de la última cita se ignora. Si no hay comillas después del signo igual, entonces el resto de la línea se usa como contenido.
      • Si un comando SET no tiene una comilla antes del nombre
        set name="content" not ignored -> valor = "content" not ignored
        entonces el resto completo de la línea después del igual se usa como contenido, incluidas todas y cada una de las comillas que puedan 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 ya analizado, comenzando con la fase 5.
    • La cláusula IN de un comando FOR se repite adecuadamente.
      • Si este es un FOR / F que itera la salida de un bloque de comandos, entonces:
        • La cláusula IN se ejecuta en un nuevo proceso cmd.exe a través de CMD / C.
        • El bloque de comando debe pasar por todo el proceso de análisis por segunda vez, pero esta vez en un contexto de línea de comando
        • ECHO comenzará ON, y la expansión demorada generalmente comenzará deshabilitada (dependiendo de la configuración del registro)
        • Todos los cambios de entorno realizados por el bloque de comando de la cláusula IN se perderán una vez que finalice el proceso hijo cmd.exe
      • Para cada iteración:
        • Los valores de la variable FOR están definidos
        • El bloque de comando DO ya analizado se procesa, comenzando con la fase 4.
    • GOTO utiliza la siguiente lógica para ubicar la etiqueta:
      • La etiqueta se analiza desde el primer token de argumento
      • El script se escanea para la próxima aparición de la etiqueta
        • El escaneo comienza desde la posición actual del archivo
        • Si se alcanza el final del archivo, el escaneo vuelve al principio del archivo y continúa hasta el punto inicial 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 que sigue inmediatamente a la etiqueta. La ejecución del guión se reanuda desde ese punto. Tenga en cuenta que un verdadero GOTO exitoso anulará inmediatamente cualquier bloque de código analizado, incluidos los bucles FOR.
      • Si no se encuentra la etiqueta o falta el token de la etiqueta, GOTO falla, se imprime un mensaje de error y se abre la pila de llamadas. Esto funciona efectivamente como EXIT / B, excepto que los comandos ya analizados en el bloque de comandos actual que siguen al GOTO todavía se ejecutan, 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 aceptan comodines para las rutas de origen y destino. Pero Microsoft hace un trabajo terrible al documentar cómo funcionan los comodines, especialmente para la ruta de destino. Puede encontrar un conjunto útil de reglas comodín en ¿Cómo interpreta el comando RENAME de Windows los comodines?
  • 7.2 - Ejecutar cambio de volumen - De lo contrario, si el token de comando no comienza con una cita, tiene exactamente dos caracteres y el segundo carácter es un signo de dos puntos, entonces cambie el volumen
    • Todos los tokens de argumento son ignorados
    • Si no se puede encontrar el volumen especificado por el primer carácter, entonces cancele con un error
    • Una señal de comando de ::siempre dará como resultado un error a menos que se use SUBST para definir un volumen para. ::
      Si se usa SUBST para definir un volumen ::, entonces el volumen se cambiará, no se tratará como una etiqueta.
  • 7.3 - Ejecutar comando externo : en caso contrario, trate el comando como un comando externo.
    • Si en el modo de línea de comandos y el comando no es citado y no comienza con una especificación de volumen, espacio en blanco, ,, ;, =o +luego romper el comando de token en la primera aparición de <space> , ;o =y anteponer el resto al argumento token (s).
    • Si el segundo carácter del token de comando es dos puntos, verifique que se pueda encontrar el volumen especificado por el primer carácter.
      Si no puede encontrar el volumen, anule con un error.
    • Si está en modo por lotes y el token de comando comienza con :, entonces vaya a 7.4
      Tenga en cuenta que si el token de etiqueta comienza con ::, entonces esto no se alcanzará porque el paso anterior habrá abortado con un error a menos que se use SUBST para definir un volumen ::.
    • Identifique el comando externo a 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, anule con un error.
    • Si en el modo de línea de comandos y el comando símbolo comienza con :y, a continuación Goto 7.4
      Tenga en cuenta que esto rara vez se alcanza debido a que el paso anterior se habrá abortado con un error a menos que el símbolo de comandos comienza con ::, y SUBST se utiliza para definir un volumen para ::, y el el token de comando completo es una ruta válida a un comando externo.
    • 7.3.exec : ejecuta el comando externo.
  • 7.4 - Ignorar una etiqueta - Ignorar 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 comando:

Funciona como el BatchLine-Parser, excepto:

Fase 1) Porcentaje de expansión:

  • No %*, %1expansión de argumentos etc.
  • Si var no está definido, entonces no %var%se modifica.
  • Sin manejo especial de %%. Si var = contenido, se %%var%%expande a %content%.

Fase 3) Eco 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 retardada: solo si DelayedExpansion está habilitada

  • Si var no está definido, entonces no !var!se modifica.

Fase 7) Ejecutar comando

  • Los intentos de LLAMAR o GOTO a: la etiqueta dan como resultado un error.
  • Como ya se documentó en la fase 7, una etiqueta ejecutada puede provocar 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 producen 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 sobre cómo CMD.EXE analiza los números


Para cualquiera que desee mejorar las reglas de análisis de cmd.exe, hay un tema de discusión en el foro DosTips donde se pueden informar problemas y hacer sugerencias.

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

dbenham
fuente
44
Hola jeb, gracias por tu comprensión ... Puede ser difícil de entender, ¡pero intentaré pensarlo bien! ¡Parece que has realizado muchas pruebas! Gracias por traducir ( administrador.de/… )
Benoit
2
Fase 5 del lote): %% a ya se habrá cambiado a% a en la Fase 1, por lo que la expansión for-loop realmente expande% a. Además, agregué una explicación más detallada de la fase 1 de Batch en una respuesta a continuación (no tengo privilegio de edición)
dbenham
3
Jeb: ¿quizás la fase 0 podría moverse y combinarse con la fase 6? Eso tiene más sentido para mí, ¿o hay alguna razón por la cual están separados de esa manera?
dbenham
1
@aschipfl: actualicé esa sección. El )realmente hace la función casi como un REMcomando cuando el contador paréntesis es 0. Trate ambos desde la línea de comandos: ) Ignore thisyecho OK & ) Ignore this
dbenham
1
@aschipfl sí, eso es correcto, por lo que a veces ves 'set "var =% expr%"! 'se eliminará el último signo de exclamación, pero obliga a la fase 5
jeb
62

Al invocar un comando desde una ventana de comando, la tokenización de los argumentos de la línea de comando no se realiza mediante 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 argvy procesar la línea de comando sin procesar por sí misma (por ejemplo, con GetCommandLine ()) En el nivel del sistema operativo, Windows pasa las líneas de comando tokenizadas como una sola cadena a los nuevos procesos. Esto contrasta con la mayoría de los shells * nix, donde el shell tokeniza los argumentos de una manera consistente y predecible antes de pasarlos al proceso recién formado. Todo esto significa que puede experimentar un comportamiento de tokenización de argumentos muy divergente entre diferentes programas en Windows, ya que los programas individuales a menudo toman la tokenización de argumentos en sus propias manos.

Si suena a anarquía, es algo así. Sin embargo, ya que un gran número de programas de Windows hacer utilizar el Microsoft C / C ++ en tiempo de ejecución de argv, puede ser útil en general para entender cómo el MSVCRT tokenizes argumentos. Aquí hay un extracto:

  • Los argumentos están delimitados por un espacio en blanco, que es un espacio o una pestaña.
  • Una cadena entre comillas dobles se interpreta como un argumento único, independientemente del espacio en blanco que contenga. Una cadena entre comillas se puede incrustar en un argumento. Tenga en cuenta que el símbolo de intercalación (^) no se reconoce como un carácter de escape o delimitador.
  • Una comilla doble precedida por una barra diagonal inversa, \ ", se interpreta como una comilla doble literal (").
  • Las barras invertidas se interpretan literalmente, a menos que precedan inmediatamente a una comilla doble.
  • Si a un número par de barras invertidas le sigue una comilla doble, entonces se coloca una barra invertida () en la matriz argv para cada par de barras invertidas (\), y la comilla doble (") se interpreta como un delimitador de cadena.
  • Si a un número impar de barras invertidas le sigue una comilla doble, se coloca una barra invertida () en la matriz argv para cada par de barras invertidas (\) y la barra invertida interpreta la secuencia de comillas doble como una secuencia de escape, lo que provoca una comilla doble literal (") que se colocará en argv.

El "lenguaje por lotes" de Microsoft ( .bat) no es una excepción a este entorno anárquico, y ha desarrollado sus propias reglas únicas para la tokenización y el escape. También parece que el símbolo del sistema de cmd.exe realiza un procesamiento previo del argumento de la línea de comando (principalmente para la sustitución de variables y el escape) antes de pasar el argumento al nuevo proceso de ejecución. Puede leer más sobre los detalles de bajo nivel del lenguaje por lotes y el escape de cmd en las excelentes respuestas de jeb y dbenham en esta página.


Creemos una utilidad de línea de comando simple en C y veamos qué dice sobre sus casos de prueba:

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

(Notas: argv [0] siempre es el nombre del ejecutable, y se omite a continuación por brevedad. Probado en Windows XP SP3. Compilado con 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]

Y algunas de mis propias pruebas:

> 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]
Mike Clark
fuente
Gracias por su respuesta. Me desconcierta aún más ver que TinyPerl no generará lo que genera su programa, y ​​tengo dificultades para comprender cómo [a "b" c]podría convertirse [a "b] [c]en un procesamiento posterior.
Benoit
Ahora que lo pienso, esta tokenización de la línea de comando probablemente se realiza completamente por el tiempo de ejecución de C. Se podría escribir un ejecutable de manera que ni siquiera use el tiempo de ejecución de C, en cuyo caso creo que tendría que lidiar con la línea de comandos al pie de la letra y ser responsable de hacer su propia tokenización (si así lo desea). O incluso si su aplicación utiliza el tiempo de ejecución C, puede optar por ignorar argc y argv y simplemente obtener la línea de comando sin procesar a través de, por ejemplo, Win32 GetCommandLine. Quizás TinyPerl está ignorando argv y simplemente tokenizando la línea de comando sin procesar por sus propias reglas.
Mike Clark el
44
"Recuerde que desde el punto de vista de Win32, la línea de comandos es solo una cadena que se copia en el espacio de direcciones del nuevo proceso. La forma en que el proceso de lanzamiento y el nuevo proceso interpretan esta cadena no se rige por reglas sino por convención". -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark el
2
Gracias por esa respuesta realmente agradable. Eso explica mucho en mi opinión. Y eso también explica por qué a veces me parece realmente horrible trabajar con Windows ...
Benoit
Encontré esto con respecto a las barras invertidas y las comillas durante la transformación de la línea de comandos a argv, para los programas Win32 C ++. El recuento de barras invertidas solo se divide entre dos cuando la última barra invertida va seguida de una dblquote, y la dblquote termina una cadena cuando hay un número par de barras invertidas antes.
Benoit
47

Reglas de expansión porcentual

Aquí hay una explicación ampliada de la Fase 1 en la respuesta de jeb (Válido tanto para el modo por lotes como para el modo de línea de comandos).

Fase 1) Porcentaje de expansión Comenzando desde la izquierda, escanea cada carácter para %o <LF>. Si se encuentra entonces

  • 1.05 (línea truncada en <LF>)
    • Si el personaje es <LF>entonces
      • Suelta (ignora) el resto de la línea desde <LF>adelante
      • Pasar a la fase 1.5 (tira <CR>)
    • De lo contrario, el personaje debe ser %, así que proceda a 1.1
  • 1.1 (escape %) omitido si el modo de línea de comando
    • Si el modo por lotes y seguido por otro %,
      reemplace %%con un solo %y continúe escaneando
  • 1.2 (argumento de expansión) omitido si el modo de línea de comando
    • Si no es por lotes, entonces
      • Si seguido de *y las extensiones de comando están habilitadas,
        reemplace %*con el texto de todos los argumentos de la línea de comando (Reemplace con nada si no hay argumentos) y continúe con el escaneo.
      • Else if seguida de <digit>entonces
        Reemplazar %<digit>con valor de argumento (sustituir con nada si no definido) y continuar la exploración.
      • De lo contrario, si seguido de ~y las extensiones de comando están habilitadas, entonces
        • Si es seguido por opcional lista válida de modificadores de argumentos seguido por requerido <digit>entonces
          Reemplazar %~[modifiers]<digit>con valor de argumento modificado (sustituir con nada si no está definida o si se especifica $ PATH: modificador no está definido) y continuar la exploración.
          Nota: los modificadores no distinguen entre mayúsculas y minúsculas y pueden aparecer varias veces en cualquier orden, excepto $ PATH: el modificador solo puede aparecer una vez y debe ser el último modificador antes del<digit>
        • Otra sintaxis de argumento modificado no válido genera un error fatal: todos los comandos analizados se anulan y el procesamiento por lotes se anula si está en modo por lotes.
  • 1.3 (variable de expansión)
    • De lo contrario, si las extensiones de comando están deshabilitadas,
      mire la siguiente cadena de caracteres, separando antes %o al final del búfer, y llámelos VAR (puede ser una lista vacía)
      • Si el siguiente personaje es %entonces
        • Si se define VAR, entonces
          Reemplace %VAR%con el valor de VAR y continúe escaneando
        • De lo contrario, si el modo por lotes, luego
          Eliminar %VAR%y continuar el escaneo
        • Else goto 1.4
      • Else goto 1.4
    • De lo contrario, si las extensiones de comando están habilitadas,
      mire la siguiente cadena de caracteres, separando antes % :o al final del búfer, y llámelos VAR (puede ser una lista vacía). Si VAR se rompe antes :y el carácter subsiguiente se %incluye :como último carácter en VAR y se rompe antes %.
      • Si el siguiente personaje es %entonces
        • Si se define VAR, entonces
          Reemplace %VAR%con el valor de VAR y continúe escaneando
        • De lo contrario, si el modo por lotes, luego
          Eliminar %VAR%y continuar el escaneo
        • Else goto 1.4
      • De lo contrario, si el siguiente personaje es :entonces
        • Si VAR no está definido, entonces
          • Si está en modo por lotes,
            elimine %VAR:y continúe escaneando.
          • Else goto 1.4
        • De lo contrario, si el siguiente personaje es ~entonces
          • Si la siguiente cadena de caracteres coincide con el patrón de, [integer][,[integer]]%entonces
            Reemplace %VAR:~[integer][,[integer]]%con una subcadena de valor de VAR (posiblemente resultando en una cadena vacía) y continúe el escaneo.
          • Else goto 1.4
        • De lo contrario, si lo sigue =o *=luego, la
          sintaxis de búsqueda y reemplazo de variables no válidas genera un error grave: todos los comandos analizados se anulan y el procesamiento por lotes se anula si está en modo por lotes.
        • De lo contrario, si la siguiente cadena de caracteres coincide con el patrón de [*]search=[replace]%, donde la búsqueda puede incluir cualquier conjunto de caracteres excepto =, y reemplazar puede incluir cualquier conjunto de caracteres excepto %, luego
          Reemplazar %VAR:[*]search=[replace]%con el valor de VAR después de realizar la búsqueda y reemplazar (posiblemente resultando en una cadena vacía) y continuar escanear
        • Else goto 1.4
  • 1.4 (tira%)
    • De lo contrario, si está en modo por lotes,
      elimine %y continúe la exploración comenzando con el siguiente carácter después de%
    • De lo contrario, mantenga el encabezado %y continúe escaneando comenzando con el siguiente carácter después del encabezado preservado%

Lo anterior ayuda a explicar por qué este lote

@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

Da estos resultados:

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

Nota 1 - La fase 1 ocurre antes del reconocimiento de las declaraciones REM. ¡Esto es muy importante porque significa que incluso un comentario puede generar un error fatal si tiene una sintaxis de expansión de argumento no válida o una búsqueda de variable no válida y reemplaza la sintaxis!

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

Nota 2 - Otra consecuencia interesante de las reglas de análisis porcentual: las variables que contienen: en el nombre pueden definirse, pero no pueden expandirse a menos que las extensiones de comando estén deshabilitadas. Hay una excepción: un nombre de variable que contiene dos puntos al final se puede expandir mientras las extensiones de comando están habilitadas. Sin embargo, no puede realizar operaciones de subcadena o búsqueda y reemplazo en nombres de variables que terminan con dos puntos. El archivo por lotes a continuación (cortesía de jeb) demuestra este comportamiento

@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%

Nota 3 - Un resultado interesante del orden de las reglas de análisis que Jeb establece en su publicación: Al realizar la búsqueda y reemplazo con expansión retrasada, los caracteres especiales en los términos de búsqueda y reemplazo deben escaparse o citarse. Pero la situación es diferente para el porcentaje de expansión: el término de búsqueda no se debe escapar (aunque se puede citar). El porcentaje de reemplazo de cadena puede o no requerir escape o presupuesto, dependiendo de su intención.

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

Reglas de expansión retrasadas

Aquí hay una explicación ampliada y más precisa de la fase 5 en la respuesta de jeb (Válido tanto para el modo por lotes como para el modo de línea de comandos)

Fase 5) Expansión retrasada

Esta fase se omite si se aplica alguna de las siguientes condiciones:

  • La expansión retrasada está deshabilitada.
  • El comando está dentro de un bloque entre paréntesis a cada lado de una tubería.
  • El token de comando entrante es un script por lotes "desnudo", lo que significa que no está asociado con un CALLbloque entre paréntesis, ninguna forma de concatenación de comandos ( &, &&o ||) o una tubería |.

El proceso de expansión retrasada se aplica a los tokens de forma independiente. Un comando puede tener múltiples tokens:

  • El token de comando. Para la mayoría de los comandos, el nombre del comando en sí es un token. Pero algunos comandos tienen regiones especializadas que se consideran un TOKEN para la fase 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, En que la comparación es uno de ==, equ, neq, lss, leq, gtr, ogeq
  • El token de argumentos
  • El token de destino de la redirección (uno por redirección)

No se realizan cambios en los tokens que no contienen !.

Para cada ficha que contenga al menos una !, escanee cada carácter de izquierda a derecha en busca de ^o !, y si lo encuentra, entonces

  • 5.1 (caret escape) Necesario !o ^literales
    • Si el personaje es un signo de preocupación, ^entonces
      • Eliminar el ^
      • Escanee el siguiente carácter y consérvelo como literal
      • Continuar el escaneo
  • 5.2 (expandir variable)
    • Si el personaje es !, entonces
      • Si las extensiones de comando están deshabilitadas,
        mire la siguiente cadena de caracteres, separándola antes !o <LF>, y llámelos VAR (puede ser una lista vacía)
        • Si el siguiente personaje es !entonces
          • Si se define VAR, entonces
            Reemplace !VAR!con el valor de VAR y continúe escaneando
          • De lo contrario, si está en modo por lotes,
            elimine !VAR!y continúe escaneando
          • Else goto 5.2.1
        • Else goto 5.2.1
      • Porque si están habilitadas las extensiones de comando a continuación
        Mira siguiente cadena de caracteres, rompiendo antes !, :o <LF>, y llamarlos VAR (puede ser una lista vacía). Si VAR se rompe antes :y el siguiente carácter se !incluye :como último carácter en VAR y se rompe antes!
        • Si el siguiente personaje es !entonces
          • Si existe VAR, entonces
            Reemplace !VAR!con el valor de VAR y continúe escaneando
          • De lo contrario, si está en modo por lotes,
            elimine !VAR!y continúe escaneando
          • Else goto 5.2.1
        • De lo contrario, si el siguiente personaje es :entonces
          • Si VAR no está definido, entonces
            • Si está en modo por lotes,
              elimine !VAR:y continúe escaneando
            • Else goto 5.2.1
          • De lo contrario, si el siguiente personaje es ~entonces
            • Si la siguiente cadena de caracteres coincide con el patrón de, [integer][,[integer]]!entonces Reemplace !VAR:~[integer][,[integer]]!con una subcadena de valor de VAR (posiblemente resultando en una cadena vacía) y continúe el escaneo.
            • Else goto 5.2.1
          • De lo contrario, si la siguiente cadena de caracteres coincide con el patrón de [*]search=[replace]!, donde la búsqueda puede incluir cualquier conjunto de caracteres excepto =, y reemplazar puede incluir cualquier conjunto de caracteres excepto !, luego
            Reemplazar !VAR:[*]search=[replace]!con el valor de VAR después de realizar la búsqueda y reemplazar (posiblemente resultando en una cadena vacía) y continuar escaneando
          • Else goto 5.2.1
        • Else goto 5.2.1
      • 5.2.1
        • Si está en modo por lotes, elimine el resto !
          Else conserva el inicio!
        • Continúe el escaneo comenzando con el siguiente carácter después del guión conservado !
dbenham
fuente
3
+1, aquí solo faltan las reglas y la sintaxis de dos puntos para %definedVar:a=b%vs %undefinedVar:a=b%y los %var:~0x17,-010%formularios
jeb
2
Buen punto: amplié la sección de expansión variable para abordar sus inquietudes. También amplié la sección de expansión de argumentos para completar algunos detalles faltantes.
dbenham
2
Después de obtener algunos comentarios privados adicionales de jeb, agregué una regla para nombres de variables que terminaban con dos puntos y agregué la nota 2. También agregué la nota 3 simplemente porque pensé que era interesante e importante.
dbenham
1
@aschipfl - Sí, consideré entrar en más detalles sobre eso, pero no quería ir por ese agujero de conejo. Estaba intencionalmente no comprometida cuando utilicé el término [número entero] .Hay más información en reglas de cómo no cmd.exe analiza números .
dbenham
1
Me faltan las reglas de expansión para el contexto cmd, como que no hay caracteres reservados para el primer carácter del nombre de la variable como %<digit>, %*o %~. Y el comportamiento cambia para variables indefinidas. Tal vez necesite abrir una segunda respuesta
jeb
7

Como se señaló, los comandos pasan toda la cadena de argumentos en μSoft land, y depende de ellos analizar esto en argumentos separados para su propio uso. No hay coherencia en esto entre los diferentes programas y, por lo tanto, no hay un conjunto de reglas para describir este proceso. Realmente necesita verificar cada caso de esquina para cualquier biblioteca C que use su programa.

En cuanto a los .batarchivos del sistema , aquí está esa prueba:

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

Ahora podemos ejecutar algunas pruebas. Vea si puede descubrir exactamente lo que μSoft está tratando de hacer:

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

Bien hasta ahora. (Dejaré de lado lo poco interesante %cmdcmdline%y %0de ahora en adelante).

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

Sin expansión de nombre de archivo.

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

Sin comillas, aunque las comillas evitan la división de argumentos.

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

Las comillas dobles consecutivas les hacen perder cualquier habilidad especial de análisis que hayan tenido. @ El ejemplo de Beniot:

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

Prueba: ¿Cómo se pasa el valor de cualquier entorno var como un argumento único (es decir, como %1) a un archivo bat?

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!

El análisis sensato parece estar roto para siempre.

Para su entretenimiento, trate de añadir diversos ^, \, ', &(& c.) Caracteres a estos ejemplos.

bobbogo
fuente
Para pasar% t% como argumento único, puede usar "% t:" = \ "%" Es decir, usar la sintaxis% VAR: str = reemplazo% para la expansión variable. Metacaracteres de Shell como | y & en los contenidos variables todavía pueden estar expuestos y desordenar el caparazón, a menos que escapes de nuevo ...
Toughy
@Toughy Entonces, en mi ejemplo, tes a "b c. ¿Tiene una receta para conseguir esos 6 caracteres ( a, 2 × espacio, ", b, y c) para aparecer como %1el interior de una .cmd? Aunque me gusta tu pensamiento. args "%t:"=""%"está bastante cerca :-)
bobbogo
5

Ya tiene algunas respuestas excelentes, pero para responder una parte de su pregunta:

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

Lo que está sucediendo allí es que debido a que tiene un espacio antes de =, se crea una variable llamada %a<space>% así cuando echo %a %se evalúa correctamente como b.

La parte restante b% c%se evalúa como texto sin formato + una variable indefinida % c%, que debe repetirse como se ha escrito, para mí echo %a %b% c%devuelvebb% c%

Sospecho que la capacidad de incluir espacios en nombres de variables es más un descuido que una 'característica' planificada

SS64
fuente
0

editar: ver la respuesta aceptada, lo que sigue es incorrecto y explica solo cómo pasar una línea de comando a TinyPerl.


Con respecto a las citas, tengo la sensación de que el comportamiento es el siguiente:

  • cuando "se encuentra un, comienza el bloqueo de cuerdas
  • cuando se produce el bloqueo de la cuerda:
    • cada personaje que no "es un es englobado
    • cuando "se encuentra a:
      • si es seguido por ""( por lo tanto, un triple "), se agrega una comilla doble a la cadena
      • si es seguido por "( por lo tanto, un doble "), entonces se agrega una comilla doble a la cadena y termina la secuencia global
      • si el siguiente carácter no es ", termina el bloqueo de cadena
    • cuando termina la línea, termina el engrosamiento de la cuerda.

En breve:

"a """ b "" c"""consta de dos cadenas: a " b "yc"

"a"", "a"""y "a""""son todos la misma cadena si al final de una línea

Benoit
fuente
¡El tokenizador y el bloqueo de cadenas dependen del comando! Un "conjunto" funciona de manera diferente que una "llamada" o incluso un "si"
jeb
sí, pero ¿qué pasa con los comandos externos? ¿Supongo que cmd.exe siempre les pasa los mismos argumentos?
Benoit
1
cmd.exe pasa siempre el resultado de la expansión como una cadena, no los tokens a un comando externo. Depende del comando externo cómo escapar y tokenizarlo, findtr usa barra invertida, el siguiente puede usar otra cosa
jeb
0

Tenga en cuenta que Microsoft ha publicado el código fuente de su Terminal. Puede funcionar de manera similar a la línea de comando con respecto al análisis sintáctico. Quizás alguien esté interesado en probar las reglas de análisis de ingeniería inversa de acuerdo con las reglas de análisis del terminal.

Enlace al código fuente.

usuario7427029
fuente