Comment fonctionne L'interpréteur de commandes Windows (CMD.EXE) analyser les scripts?

je suis tombé sur ss64.com qui fournit une bonne aide sur la façon d'écrire des scripts de traitement par lots que L'interpréteur de commandes Windows exécutera.

cependant, je n'ai pas été en mesure de trouver une bonne explication de la grammaire des scripts de traitement par lots, comment les choses se développent ou ne pas se développer, et comment échapper aux choses.

Voici des exemples de questions que je n'ai pas pu résoudre:

  • comment le système de cotation est-il géré? J'ai fait un TinyPerl script

    ( foreach $i (@ARGV) { print '*' . $i ; } ), compilé et appelé de cette façon :
    • my_script.exe "a ""b"" c" → la sortie est *a "b*c
    • my_script.exe """a b c""" → sortie it *"a*b*c"
  • Comment fonctionne la commande interne echo ? Ce qui est développé à l'intérieur de cette commande?
  • Pourquoi dois-je utiliser for [...] %%I dans les scripts de fichiers, mais for [...] %I dans les sessions interactives?
  • Quels sont les caractères d'échappement, et dans quel contexte? Comment échapper un signe de pourcentage? Par exemple, comment faire littéralement écho à %PROCESSOR_ARCHITECTURE% ? J'ai trouvé que echo.exe %""PROCESSOR_ARCHITECTURE% fonctionne, y a-t-il une meilleure solution?
  • comment les paires de % correspondent-elles? Exemple:
    • set b=a , echo %a %b% c%%a a c%
    • set a =b , echo %a %b% c%bb c%
  • Comment puis-je m'assurer qu'une variable passe à une commande en tant qu'argument unique si jamais cette variable contient des guillemets doubles?
  • comment les variables sont-elles stockées en utilisant la commande set ? Par exemple, si je fais set a=a" b et puis echo.%a% j'obtiens a" b . Si par contre j'utilise echo.exe des UnxUtils, j'obtiens a b . Comment se fait %a% se développe d'une manière différente?

Merci pour vos lumières.

117
demandé sur Benoit 2010-11-04 10:38:57

6 réponses

j'ai réalisé de nombreuses expériences pour étudier la grammaire des scripts de traitement par lots. J'ai aussi étudié les différences entre le mode par lots et le mode en ligne de commande.

Analyseur De Ligne De Lots:

le traitement d'une ligne de code dans un fichier de lot comporte plusieurs phases.

voici un bref aperçu des différentes phases:

Phase 0) Lire La Ligne:

Phase 1) Pourcentage D'Expansion:

Phase 1.5) Supprimer <CR> : supprimer tous les caractères de retour de chariot (0x0D)

Phase 2) traiter les caractères spéciaux, tokenize, et construire un bloc de commande en cache: c'est un processus complexe qui est affecté par des choses telles que les citations, les caractères spéciaux, les délimiteurs de jeton, et les échappées de caret.

Phase 3) faire écho à la(AUX) Commande (s) parsée (s) seulement si le bloc de commande n'a pas commencé par @ , et que L'écho était activé au début de l'étape précédente.

Phase 4) pour %X expansion variable: seulement si A for command est actif et que les commandes après DO sont en cours de traitement.

Phase 5) Expansion retardée: seulement si l'expansion retardée est activé

Phase 5.3) traitement des conduites: seulement si les commandes sont de chaque côté d'une conduite

Phase 5.5) Execute Redirection:

Phase 6) Traitement des appels/doublage Caret: seulement si le token de commande est appel

Phase 7) Exécuter: la commande est exécutée


et voici les détails pour chaque phase:

noter que les phases décrites ci-dessous ne sont qu'un modèle du fonctionnement de l'analyseur par lots. La réelle cmd.exe internes peuvent ne pas tenir compte de ces phases. Mais ce modèle est efficace pour prédire le comportement des scripts batch.

Phase 0) lire la ligne: Lire la ligne d'entrée.

  • en lisant une ligne à être analysée comme une commande, <Ctrl-Z> (0x1A) est lu comme <LF> (LineFeed 0x0A)
  • quand GOTO ou appel lit des lignes lors de la recherche d'un: étiquette, <Ctrl-Z> , est traité comme lui - même-il est pas converti en <LF>

Phase 1) Pourcentage D'Expansion:

  • A le double %% est remplacé par un simple %
  • l'Expansion de l'argument variables ( %1 , %2 , etc.)
  • Expansion de %var% , si var n'existe pas le remplacer par rien
  • pour une explication complète, lisez la première moitié de ceci de dbenham même fil: pourcentage de Phase

Phase 1.5) Supprimer <CR> : supprimer tous les retours de chariot (0x0D) de la ligne

Phase 2) traiter les caractères spéciaux, tokenize, et construire un bloc de commande en cache: c'est un processus complexe qui est affecté par des choses telles que les citations, les caractères spéciaux, les délimiteurs de jeton, et les échappées de caret. Ce qui suit est une approximation de ce processus.

il y a certains concepts qui sont important tout au long de cette phase.

  • Un jeton est simplement une chaîne de caractères qui est traité comme une unité.
  • Les Tokens
  • sont séparés par des délimiteurs de tokens. Les délimiteurs de token standard sont: <space> <tab> ; , = <0x0B> <0x0C> et <0xFF>

    Consécutives jeton délimiteurs sont traités comme un seul - il n'y a aucun vide jetons entre jeton délimiteurs
  • il n'y a pas de délimiteur de token dans une chaîne Citée. La chaîne Citée entière est toujours traitée comme une partie d'un seul token. Un jeton peut être composé d'une combinaison de chaînes de caractères citées et de caractères non cités.

les caractères suivants peuvent avoir une signification particulière dans cette phase, selon le contexte: ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

regardez chaque caractère de gauche à droite:

  • S'il s'agit d'un caret ( ^ ), le caractère suivant est échappé, et le caret échappant est enlevé. Les caractères échappés perdent toute signification spéciale (sauf <LF> ).
  • Si c'est une citation ( " ), bascule le drapeau de citation. Si le drapeau de citation est actif, alors seulement " et <LF> sont spéciaux. Tous les autres caractères perdent leur signification particulière jusqu'à ce que la citation suivante bascule le drapeau de citation. Il n'est pas possible d'échapper à la citation de clôture. Tous ces personnages sont toujours dans le même jeton.
  • <LF> éteint toujours le drapeau de citation. Les autres comportements varient selon le contexte, mais les citations ne modifient jamais le comportement de <LF> .
    • échappé <LF>
      • <LF> est dépouillé
      • le personnage suivant s'est échappé. Si à la fin du tampon de ligne, la ligne suivante est lue et ajoutée à la ligne courante avant d'échapper au caractère suivant. Si le caractère suivant est <LF> , il est traité comme un caractère littéral, ce qui signifie que ce processus n'est pas récursif.
    • sans escapade <LF> non compris parenthèse
      • <LF> est dépouillé et l'analyse de la ligne courante est terminée.
      • tous les caractères restants dans le tampon de ligne sont simplement ignorés.
    • sans échappement <LF> dans une DANS les parenthèses bloc
      • <LF> est converti en un <space>
      • si à la fin du tampon de ligne, alors la ligne suivante est lire et annexé à la présente.
    • sans échappement <LF> à l'intérieur d'un bloc de commandes entre parenthèses
      • <LF> est converti en <LF><space> , et le <space> est traité comme faisant partie de la ligne suivante du bloc de commande.
      • si à la fin du tampon de ligne, alors la ligne suivante est lue et ajoutée à l'espace.
  • si c'est l'un des caractères spéciaux & | < ou > , séparez la ligne à ce point afin de manipuler les pipes, commande la concaténation, et la redirection.
    • dans le cas d'un tuyau ( | ), chaque côté est une commande séparée (ou bloc de commande) qui obtient un traitement spécial dans la phase 5.3
    • dans le cas de & , && , ou || commande concaténation, chaque côté de la concaténation est traité comme une commande séparée.
    • dans le cas de < , << , > , ou >> redirection, la clause de redirection est analysée, temporairement supprimée, puis ajoutée à la fin de la commande courante. Une clause de redirection se compose d'un chiffre optionnel de gestion de fichier, de l'opérateur de redirection et du jeton de destination de redirection.
      • Si le jeton qui précède l'opérateur de redirection est un seul chiffre, puis le chiffre spécifie le gestionnaire de fichier à rediriger. Si le jeton de la poignée n'est pas trouvé, alors la redirection de sortie par défaut est 1 (stdout), et la redirection d'entrée par défaut est 0 (stdin).
  • si le tout premier token pour cette commande (avant de déplacer la redirection vers la fin) commence par @ , alors le @ a un sens particulier. ( @ n'est pas dans aucun autre contexte)
    • La spéciale @ est supprimé.
    • si ECHO est activé, alors cette commande, ainsi que toute commande concaténée suivante sur cette ligne, sont exclues de l'écho de phase 3. Si le @ est avant une ouverture ( , alors le bloc entier entre parenthèses est exclu de l'écho de phase 3.
  • instructions sur plusieurs lignes):
    • si l'analyseur ne cherche pas un jeton de commande, alors ( n'est pas spécial.
    • si l'analyseur cherche un jeton de commande et trouve ( , alors démarrez une nouvelle instruction composée et incrémentez le compteur de parenthèses
    • si le compteur de parenthèses est > 0 alors ) termine l'énoncé composé et décrète le compteur de parenthèses.
    • si la fin de ligne est atteinte et que le compteur de parenthèses est > 0, alors la ligne suivante sera ajoutée à la déclaration composée (recommence avec la phase 0)
    • si le compteur de parenthèses est 0 et que l'analyseur cherche une commande, alors ) fonctionne de la même façon qu'une instruction REM pourvu qu'elle soit immédiatement suivie d'un délimiteur de token, d'un caractère spécial, d'une nouvelle ligne ou d'une fin de fichier
      • tous les caractères spéciaux perdent leur sens sauf ^ (la concaténation de ligne est possible)
      • une fois la fin de la ligne logique atteinte, toute la "commande" est écartée.
  • chaque commande est divisée en une série de jetons. Le premier token est toujours traité comme un token de commande (après que le spécial @ a été enlevé et que la redirection a été déplacée à la fin).
    • à la tête de les délimiteurs de token avant la commande token sont dépouillés
    • lors de l'analyse du token de commande, ( fonctionne comme un délimiteur de token de commande, en plus des délimiteurs de token standard
    • la manipulation des jetons suivants dépend de la commande.
  • la plupart des commandes concatèrent simplement tous les arguments après la commande token en un seul token argument. Tous les délimiteurs de token d'arguments sont conservés. Les options d'Argument ne sont généralement pas analysées avant la phase 7.
  • trois commandes reçoivent un traitement spécial-si, pour, et REM
    • s'il est divisé en deux ou trois parties distinctes qui sont traitées indépendamment. Une erreur de syntaxe dans la construction du SI résultera en une erreur de syntaxe fatale.
      • l'opération de comparaison est la commande réelle qui s'écoule tout au long de la phase 7
        • tous Si les options sont entièrement analysées dans la phase 2.
        • les délimiteurs de jeton consécutifs s'effondrent dans un seul espace.
        • selon l'opérateur de comparaison, il y aura un ou deux jetons de valeur qui seront identifiés.
      • le vrai bloc de commande est l'ensemble des commandes après la condition, et est divisé comme tout autre bloc de commande. Si ELSE doit être utilisé, alors le bloc vrai doit être mis entre parenthèses.
      • le bloc de fausses commandes optionnel est l'ensemble des commandes après ELSE. Encore une fois, ce bloc de commande est analysé normalement.
      • les blocs True et False ne s'écoulent pas automatiquement dans les phases suivantes. Leur traitement ultérieur est contrôlé par la phase 7.
    • pour est divisé en deux après le DO. Une erreur de syntaxe dans la construction entraînera une fatale erreur de syntaxe.
      • Le passage à travers N'est la POUR l'itération de commande que les flux tout au long de la phase 7
        • toutes les options sont entièrement analysées dans la phase 2.
        • la clause entre parenthèses traite <LF> comme <space> . Après la clause est analysée, tous les jetons sont concaténées pour former un seul jeton.
        • les délimiteurs de jeton consécutifs, Non escapades/non cotées, s'effondrent en un seul espace tout au long de la commande pour DO.
      • la partie après DO est un bloc de commande qui est divisé normalement. Le traitement ultérieur du bloc de commande DO est contrôlé par l'itération de la phase 7.
    • Le REM
    • détecté en phase 2 est traité de façon radicalement différente de toutes les autres commandes.
      • un seul argument est parsé-l'analyseur ignore caractères après le premier jeton argument.
      • S'il n'y a qu'un seul jeton d'argument qui se termine par un ^ non enregistré qui termine la ligne, alors le jeton d'argument est jeté, et la ligne suivante est analysée et ajoutée au REM. Cela se répète jusqu'à ce qu'il y ait plus d'un jeton, ou le dernier caractère n'est pas ^ .
      • la commande REM peut apparaître en sortie de phase 3, mais la commande n'est jamais exécutée, et l'original le texte de l'argument est repris - les caractères échappés ne sont pas supprimés.
  • si la commande token commence par : , et qu'il s'agit du premier tour de la phase 2 (pas de redémarrage en raison de L'appel de la phase 6) alors
    • le jeton est normalement traité comme une étiquette non coupée .
      • le reste de La ligne est analysée, cependant ) , < , > , & et | n'ont plus de signification particulière. L'ensemble du reste de la ligne est considéré comme faisant partie de l'étiquette de "commande".
      • le ^ continue d'être spécial, ce qui signifie que la continuation de la ligne peut être utilisée pour ajouter la ligne suivante à l'étiquette.
      • An Unexécuted Label dans un bloc entre parenthèses résultera en une erreur de syntaxe fatale à moins qu'elle ne soit immédiatement suivi d'une commande ou étiquette exécutée sur la ligne suivante.
        • notez que ( n'a plus de signification particulière pour la première commande qui suit le étiquette Inexécutée dans ce contexte.
      • la commande est annulée après l'analyse des étiquettes. Les phases suivantes n'ont pas lieu pour le label
    • il y a trois exceptions qui peuvent faire en sorte qu'une étiquette trouvée dans la phase 2 soit traitée comme une étiquette exécutée qui continue de passer par la phase 7.
      • il y a une redirection qui précède le jeton d'étiquette, et il y a une | pipe ou & , && , ou || concaténation de commande sur la ligne.
      • il y a une redirection qui précède le token d'étiquette, et le la commande est dans un bloc entre parenthèses.
      • le jeton d'étiquette est la toute première commande sur une ligne à l'intérieur d'un bloc entre parenthèses, et la ligne ci-dessus s'est terminée par un étiquette Inexécutée .
    • ce qui suit se produit lorsqu'un étiquette exécutée est découvert dans la phase 2
      • l'étiquette, ses arguments et sa redirection sont tous exclus de toute sortie echo en phase 3
      • toutes les commandes subséquentes concaténées sur la ligne sont entièrement parsées et exécutées.
    • pour plus d'informations sur Etiquettes exécutées vs. Etiquettes non éditées , voir https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Phase 3) faire écho à la(AUX) Commande (s) parsée (s) seulement si le bloc de commande n'a pas commencé par @ , et que L'écho était activé au début de l'étape précédente.

Phase 4) pour %X expansion variable: seulement si A for command est actif et que les commandes après DO sont en cours de traitement.

  • à ce stade, la phase 1 du traitement par lots aura déjà converti un Pour variable comme %%X en %X . La ligne de commande a des règles de pourcentage d'expansion différentes pour la phase 1. C'est la raison pour laquelle les lignes de commande utilisent %X alors que les fichiers par lots utilisent %%X pour les variables FOR.
  • pour les noms de variables sont sensibles à la casse, mais ~modifiers ne sont pas sensibles à la casse.
  • ~modifiers ont priorité sur les noms variables. Si un caractère suivant ~ est à la fois un modificateur et un valide pour le nom de la variable, et il existe un caractère subséquent qui est actif pour le nom de la variable, alors le caractère est interprété comme un modificateur.
  • POUR les noms de variables sont globales, mais seulement dans le contexte d'une clause. Si une routine est appelée à l'intérieur d'une clause FOR DO, alors les variables FOR ne sont pas développées à l'intérieur de la routine appelée. Mais si la routine a son propre pour commander, alors tous " actuellement défini pour les variables sont accessibles aux commandes DO internes.
  • pour les noms de variables peut être réutilisé dans les FORs imbriqués. La valeur de FOR intérieure a priorité, mais une fois que la valeur de FOR intérieure se ferme, la valeur de FOR extérieure est restaurée.
  • si ECHO était activé au début de cette phase, alors la phase 3) est répétée pour montrer les commandes parsed DO après que les variables FOR ont été étendues.

- - - - à partir de ce point, chaque commande identifiée dans la phase 2 est traitée séparément.

---- Les Phases 5 à 7 sont terminées pour une commande avant de passer à la suivante.

Phase 5) Expansion retardée: seulement si l'expansion retardée est en marche

  • si la commande se trouve à l'intérieur d'un bloc entre parenthèses de chaque côté d'un tuyau, alors sauter cette étape.
  • chaque token pour une commande est analysé pour une expansion retardée indépendamment.
    • la plupart des commandes analysent deux tokens ou plus - le token de commande, le token d'arguments et chaque token de destination de redirection.
    • la commande FOR utilise le jeton in clause uniquement.
    • la commande IF analyse les valeurs de comparaison seulement - soit une ou deux, selon l'opérateur de comparaison.
  • pour chaque jeton parsé, Vérifiez d'abord s'il contient un ! . Si ce n'est pas le cas, le jeton n'est pas analysé - important pour les caractères ^ . Si le jeton contient ! , scannez chaque caractère de gauche à droite:
    • si c'est un caret ( ^ ) le caractère suivant n'a pas de signification particulière, le caret lui-même est enlevé
    • s'il s'agit d'un point d'exclamation, prochain point d'exclamation (les carets ne sont plus observés), passez à la valeur de la variable.
      • ouverture consécutive ! sont réunis en un seul !
      • Tout ce qui reste ! qui ne peut pas être apparié est enlevé
    • Important: à cette phase, les guillemets et autres caractères spéciaux sont ignorés
    • L'expansion de vars à ce stade est "safe", car les caractères spéciaux ne sont plus détectés (même <CR> ou <LF> )
    • pour une explication plus complète, lisez la 2ème moitié de ceci de dbenham même thread - Point d'Exclamation Phase
    • il y a des cas extrêmes où ces règles semblent échouer:

      Voir l'expansion retardée échoue dans certains cas

Phase 5.3) traitement des tubes: seulement si les commandes sont de chaque côté d'un tube

Chaque côté du tube est traité indépendamment.

  • s'il s'agit d'un bloc de commande entre parenthèses, alors tous les <LF> avec une commande avant et après sont convertis en <space>& . Les autres <LF> sont dénudés.
  • la commande (ou bloc de commande) est exécutée asynchrone dans un nouveau cmd.exe fil via

    %comspec% /S /D /c" commandBlock" . Cela signifie que le bloc de commande obtient un redémarrage de phase, mais cette fois en mode ligne de commande.
  • c'est la fin du traitement pour les commandes pipe.
  • pour plus d'information sur la façon dont les pipes sont analysées et traitées, regardez cette question et les réponses: pourquoi l'expansion retardée échoue-t-elle lorsque à l'intérieur d'un les canalisations de bloc de code?

Phase 5.5) exécution Redirection: toute redirection découverte dans la phase 2 est maintenant exécutée.

Phase 6) Traitement des appels/doublage Caret: seulement si le token de commande est appel, ou si le texte avant le premier délimiteur de token standard existant est appel. Si L'appel est analysé à partir d'un token de commande plus grand, alors la portion inutilisée est préprogrammée sur le token des arguments avant de continuer.

Phase 7) Exécuter: la commande est exécutée

  • 7.1 - exécuter la commande interne - si la commande token est citée, sauter cette étape. Sinon, tentez d'analyser une commande interne et exécutez.
    • les tests suivants sont effectués pour déterminer si un jeton de commande non coté représente un jeton interne commande:
      • si la commande token correspond exactement à une commande interne, exécutez-la.
      • autrement briser le jeton de commande avant la première occurrence de + / [ ] <space> <tab> , ; ou =

        Si le texte précédent est une commande interne, alors rappelez-vous cette commande
        • si vous avez le commandement mode ligne, ou si la commande est à partir d'un bloc entre parenthèses, si le bloc de commande vrai ou faux, pour le bloc de commande DO, ou impliqué dans la concaténation de commande, alors exécuter la commande interne
        • autrement (doit être une commande autonome en mode discontinu) balayer le dossier courant et le chemin pour un .COM,.EXE. ,Chauve-souris, ou .Fichier CMD dont le nom de base correspond au jeton de commande original
          • Si le premier fichier correspondant est un .BAT or .CMD, puis passez à 7.3.exec et exécutez ce script
          • Else (match pas trouvé ou le premier match .EXE or .COM) exécuter la commande interne rappelée
      • autrement briser le jeton de commande avant la première occurrence de . \ ou :

        Si le texte précédent n'est pas une commande interne, passez à 7.2

        Le reste du texte précédent peut une commande interne. Souvenez-vous de cette commande.
      • briser le jeton de commande avant la première occurrence de + / [ ] <space> <tab> , ; ou =

        Si le texte précédent est un chemin vers un fichier existant, allez à 7.2

        Sinon, exécutez la commande interne mémorisée.
    • si une commande interne est analysée à partir d'un token de commande plus grand, alors la partie inutilisée du token de commande est incluse dans la liste d'arguments
    • ce n'est pas parce qu'un token de commande est interprété comme une commande interne qu'il s'exécutera avec succès. Chaque commande interne a ses propres règles quant à la façon dont les arguments et les options sont analysés, et quelle syntaxe est permise.
    • toutes les commandes internes imprimeront de l'aide au lieu de remplir leur fonction si /? est détecté. La plupart reconnaissent /? si elle apparaît n'importe où dans les arguments. Mais quelques commandes comme ECHO et SET n'impriment de l'aide que si le premier jeton d'argument commence par /? .
    • SET a une sémantique intéressante:
      • si une commande SET a une citation avant le nom de la variable

        set "name=content" ignored --> valeur= content

        ensuite, le texte entre le premier signe égal et la dernière citation est utilisé comme contenu (premier signe égal et dernière citation exclue). Texte après la dernière citation est ignoré. S'il n'y a pas de citation après le signe égal, le reste de la ligne est utilisé comme contenu.
      • si une commande SET n'a pas de citation avant le nom

        set name="content" not ignored --> valeur= "content" not ignored

        ensuite, le reste de la ligne après l'equal est utilisé comme contenu, y compris toutes les citations qui peuvent être présentes.
    • si la comparaison est évaluée, et selon que l'état est vrai ou faux, le bloc de commande dépendant approprié déjà analysé est traité, en commençant par la phase 5.
    • la clause IN DE A FOR command est itérée de façon appropriée.
      • S'il s'agit d'une FOR /F qui itère la sortie d'un bloc de commandes, puis:
        • la clause IN est exécutée dans un nouveau cmd.processus exe via CMD / C.
        • le bloc de commande doit passer par l'ensemble du processus d'analyse une deuxième fois, mais cette fois dans un contexte de ligne de commande
        • ECHO démarre, et l'extension retardée démarre généralement désactivé (dépendant du réglage du registre)
        • tous les changements d'environnement fabriqué par le bloc de commande In clause sera perdu une fois l'enfant cmd.fin du processus exe
      • pour chaque itération:
        • les valeurs variables pour sont définies
        • le bloc de commande DO déjà analysé est alors traité, en commençant par la phase 4.
    • GOTO utilise la logique suivante pour localiser l'étiquette:
      • l'étiquette est analysée à partir du premier jeton d'argument
      • le script est scanné pour la prochaine occurrence de l'étiquette
        • l'analyse commence à partir de la position du fichier actuel
        • si la fin du fichier est atteinte, alors la boucle de balayage de retour au début du fichier et continue au point de départ original.
      • le scan s'arrête au première occurrence de l'étiquette qu'il trouve, et le pointeur de fichier est réglé sur la ligne qui suit immédiatement l'étiquette. L'exécution du script reprend à partir de ce point. Notez qu'un vrai GOTO réussi annulera immédiatement tout bloc de code analysé, y compris pour les boucles.
      • si l'étiquette n'est pas trouvée, ou si le jeton d'étiquette est manquant, alors le GOTO échoue, un message d'erreur est imprimé, et la pile d'appels est activée. Cela fonctionne effectivement comme une sortie / B, sauf les commandes parsées dans le bloc de commande courant qui suivent le GOTO sont encore exécutées, mais dans le contexte de L'appelant (le contexte qui existe après EXIT /B)
      • Voir https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 pour une description plus précise des règles utilisées pour l'analyse des étiquettes.
    • renommer et copier accepte les jokers pour les chemins source et cible. Mais Microsoft le fait. un travail terrible documentant comment les jokers fonctionnent, surtout pour le chemin de la cible. Un jeu utile de règles de Joker peut être trouvé à comment la commande Windows renommer interprète-t-elle les jokers?
  • 7.2-exécuter le changement de volume - autrement si la commande token ne commence pas par une citation, est exactement deux caractères de long, et le 2ème caractère est deux points, puis changer le volume
    • tous les tokens d'argument sont ignorés
    • si le volume spécifié par le premier caractère ne peut pas être trouvé, alors avorter avec une erreur
    • un token de commande de :: entraînera toujours une erreur à moins que SUBST ne soit utilisé pour définir un volume pour ::

      Si SUBST est utilisé pour définir un volume pour :: , alors le volume sera changé, il ne sera pas traité comme une étiquette.
  • 7.3 - Exécuter la commande externe - d'Autre essayer de traiter la commande comme une commande externe.
    • si le 2e caractère du jeton de commande est deux points, alors vérifier le volume spécifié par le 1er caractère peut être trouvé.

      Si le volume ne peut pas être trouvé, alors abandonner avec une erreur.
    • si en mode discontinu et que la commande token commence par : , puis passez à 7.4

      Notez que si le jeton d'étiquette commence par :: , alors cela ne sera pas atteint parce que l'étape précédente aura avorté avec une erreur à moins que SUBST ne soit utilisé pour définir un volume pour :: .
    • identifiez la commande externe à exécuter.
      • il s'agit d'un processus complexe qui peut impliquer le volume courant, le répertoire courant, la variable chemin, la variable PATHEXT et les associations de fichiers ou.
      • si une commande externe valide ne peut pas être identifiée, alors annuler avec une erreur.
    • si en mode ligne de commande et que le jeton de commande commence par : , alors passez à 7.4

      Notez que ceci est rarement atteint parce que l'étape précédente aura avorté avec une erreur à moins que la commande token commence par :: , et que SUBST soit utilisé pour définir un volume pour :: , et la commande token entière est un chemin valide vers une commande externe.
    • 7.3.exec - exécutez la commande externe.
  • 7.4 - ignorer une étiquette - ignorer la commande et tous ses arguments si la commande token commence par : .

    Les règles énoncées aux articles 7.2 et 7.3 peuvent empêcher une étiquette d'atteindre ce point.

Parser En Ligne De Commande:

fonctionne comme le BatchLine-Parser, sauf:

Phase 1) Pourcentage D'Expansion:

  • %var% est toujours remplacé par le contenu de var, mais si var n'est pas défini, alors l'expression sera inchangé.
  • pas de manipulation spéciale de %% . Si var=contenu, alors %%var%% extension à %content% .

Phase 3) l'Écho de la analysée de commande(s)

  • ceci n'est pas effectué après la phase 2. Il n'est effectué qu'après la phase 4 pour le bloc de commande FOR DO.

Phase 5) Expansion retardée: seulement si L'extension retardée est activée

  • !var! est encore remplacé par le contenu de var, mais si var n'est pas défini, alors l'expression sera inchangé.

Phase 7) Exécuter La Commande

  • les tentatives d'appeler ou de passer à A :label entraînent une erreur.
  • même si les étiquettes ne peuvent pas être appelées, une ligne valide peut toujours contenir une étiquette. Comme déjà documenté dans la phase 7, une étiquette exécutée peut entraîner une erreur sous différents scénario.
    • les étiquettes exécutées par lots ne peuvent causer d'erreur que si elles commencent par ::
    • les étiquettes exécutées en ligne de commande entraînent presque toujours une erreur

Parsing des valeurs entières

il existe de nombreux contextes dans lesquels le cmd.exe analyse des valeurs entières de chaînes, et les règles sont incompatibles:

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

pour plus de détails sur ces règles, voir règles sur la façon de CMD.EXE parses numéros


pour quiconque souhaite améliorer ces règles, il y a un sujet de discussion sur le forum DosTips où les questions peuvent être rapportées et des suggestions faites.

j'Espère que ça aide

Jan Erik ( jeb) - auteur Original et découvreur des différentes phases

Dave Benham ( dbenham) - beaucoup plus de contenu et d'édition

147
répondu dbenham 2018-08-04 13:40:11

Lorsqu'on invoque une commande depuis une fenêtre de commande, la tokenisation des arguments en ligne de commande n'est pas faite par cmd.exe (A. K. A. "The shell"). La plupart du temps, la tokenisation est faite par l'exécution C/C++ des processus nouvellement formés, mais ce n'est pas nécessairement le cas -- par exemple, si le nouveau processus n'a pas été écrit en C/C++, ou si le nouveau processus choisit d'ignorer argv et de traiter la ligne de commande brute pour lui-même (par exemple avec GetCommandLine() ). À L'OS niveau, Windows passe des lignes de commande non initialisées comme une chaîne simple à de nouveaux processus. Ceci est en contraste avec la plupart des shells *nix, où la shell tokenise les arguments d'une manière cohérente et prévisible avant de les passer au processus nouvellement formé. Tout cela signifie que vous pouvez éprouver wildly divergent argument tokenization behavior à travers différents programmes sur Windows, comme les programmes individuels prennent souvent l'argument tokenization dans leurs propres mains.

si ça sonne comme l'anarchie, c'est. Cependant, étant donné qu'un grand nombre de programmes Windows do utilisent les arguments argv de Microsoft C/C++ runtime , il peut être généralement utile de comprendre comment les tokenizes MSVCRT . En voici un extrait:

  • les Arguments sont délimités par un espace blanc, qui est soit un espace, soit un onglet.
  • une corde entourée de guillemets doubles est interprétée comme seul argument, indépendamment de l'espace blanc. Une chaîne de caractères Citée peut être intégrée dans un argument. Notez que l'accent circonflexe (^) n'est pas reconnu comme un caractère d'échappement ou un délimiteur.
  • un double guillemet précédé d'un antislash,\", est interprété comme un double guillemet littéral (").
  • barres obliques Inverses sont interprétés littéralement, à moins qu'ils précèdent immédiatement un guillemet double.
  • Si un nombre pair de les antislashes sont suivis d'un guillemet double, puis un antislash () est placé dans le tableau argv pour chaque paire de antislashes (\), et le guillemet double (") est interprété comme un délimiteur de chaîne.
  • si un nombre impair de antislashes est suivi d'un double guillemet, alors un antislash () est placé dans le tableau argv pour chaque paire de antislashes (\) et le double guillemet est interprété comme une séquence d'échappement par le antislash restant, provoquant une double guillemet littéral ( " ) à placer dans argv.

le Microsoft "batch language" ( .bat ) ne fait pas exception à cet environnement anarchique, et il a développé ses propres règles uniques pour la tokenisation et l'évasion. On dirait aussi du cmd.l'invite de commande d'exe fait un prétraitement de l'argument de ligne de commande (principalement pour la substitution de variables et l'échappement) avant de passer l'argument à la nouvelle exécution processus. Vous pouvez lire plus au sujet des détails de bas niveau du langage de fournée et cmd échappant dans les excellentes réponses par jeb et dbenham sur cette page.


construisons un utilitaire en ligne de commande simple en C et voyons ce qu'il dit de vos cas de test:

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] est toujours le nom de l'exécutable, et est omis ci-dessous par souci de brièveté. Testé sur Windows XP SP3. Compilé avec 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]

Et quelques-uns de mes propres 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]
58
répondu Mike Clark 2015-01-13 18:55:35

Voici une explication détaillée de la Phase 1 dans réponse de jeb (valide pour le mode par lots et le mode en ligne de commande).

Phase 1) Pourcentage D'Expansion À partir de la gauche, numérisez chaque caractère pour % . Si trouvé alors

  • 1.1 (s'échapper % ) sautés si le mode de ligne de commande
    • En mode discontinu et suivi d'un autre % puis

      Remplacer %% par % et continuer le balayage
  • 1.2 (extension de l'argument) ignorée si le mode de ligne de commande
    • sinon en mode discontinu
      • si suivi de * et les extensions de commandes sont activées alors

        Remplacer %* par le texte de tous les arguments en ligne de commande (remplacer par rien s'il n'y a pas d'arguments) et continuer le scan.
      • Sinon si suivi de <digit> puis

        Remplacer %<digit> par la valeur de l'argument (remplacer par rien si non défini) et continuer le balayage.
      • Sinon si suivi de ~ et les extensions de commandes sont activées alors
        • si suivi de facultatif liste valide de l'argument des modificateurs suivie par le <digit> puis

          Remplacer %~[modifiers]<digit> par une valeur d'argument modifiée (remplacer par rien si non défini ou si spécifié $PATH: le modificateur n'est pas défini) et continuer le scan.

          Note: les modificateurs ne sont pas sensibles à la casse et peuvent apparaître plusieurs fois dans n'importe quel ordre, sauf $PATH: le modificateur ne peut apparaître qu'une seule fois et doit être le dernier modificateur avant le <digit>
        • sinon la syntaxe d'argument modifiée invalide soulève erreur fatale: toutes les commandes parsées sont interrompues, et le traitement par lots se termine si en mode lot!
  • 1.3 (variable expand)
    • Sinon si les extensions de commandes sont désactivées alors

      Regardez la chaîne suivante de caractères, cassant avant % ou <LF> , et appelez-les VAR (peut être une liste vide)
      • si le caractère suivant est % alors
        • si VAR est défini alors

          " Remplacer %VAR% par la valeur de VAR et continuer le balayage
        • Sinon si le mode de traitement par lots alors

          Supprimer %VAR% et continuer le balayage
        • sinon passez à 1.4
      • sinon passez à 1.4
    • Sinon si les extensions de commandes sont activées alors

      Regardez la chaîne suivante de caractères, cassant avant % : ou <LF> , et appelez-les VAR (peut être une liste vide). Si VAR casse avant : et le caractère suivant est % alors inclure : comme le dernier personnage dans VAR et break avant % .
      • si le caractère suivant est % alors
        • si VAR est défini alors

          " Remplacer %VAR% par la valeur de VAR et continuer le balayage
        • Sinon si le mode de traitement par lots alors

          Supprimer %VAR% et continuer le balayage
        • sinon passez à 1.4
      • Sinon, si le prochain caractère est : alors
        • si VAR n'est pas défini alors
          • Si le mode batch puis

            Supprimer %VAR: et continuer le balayage.
          • sinon passez à 1.4
        • Sinon si le prochain caractère est ~ alors
          • si la chaîne suivante de caractères correspond au motif [integer][,[integer]]% puis

            Remplacer %VAR:~[integer][,[integer]]% par une soustraction de la valeur de VAR (pouvant donner lieu à une chaîne vide) et poursuivre le balayage.
          • sinon passez à 1.4
        • sinon suivi de = ou *= puis

          Erreur fatale: toutes les commandes parsées sont interrompues, et le traitement par lots est interrompu si en mode par lots!
        • Sinon si la chaîne suivante de caractères correspond au motif [*]search=[replace]% , où la recherche peut inclure n'importe quel ensemble de caractères sauf = et <LF> , et remplacer peut inclure n'importe quel ensemble de caractères sauf % et <LF> , puis remplacer
          15191010920 " %VAR:[*]search=[replace]% avec valeur de VAR après avoir effectué la recherche et remplacer (résultant éventuellement en chaîne vide) et continuer la numérisation
        • Autres goto 1.4
  • 1.4 (%bande)
    • Sinon si le mode de traitement par lots

      Supprimer % et continuer avec scan
    • Else préserver % et de poursuivre l'analyse

ce qui précède explique pourquoi ce lot

@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

donne ces résultats:

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

Note 1 - la Phase 1 précède la reconnaissance des déclarations REM. Ceci est très important car cela signifie que même une remarque peut générer une erreur fatale si elle a une syntaxe d'extension d'argument invalide ou une recherche de variable invalide et remplacer la syntaxe!

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

Note 2 - une autre conséquence intéressante des règles % parsing: les Variables contenant : dans le nom peuvent être définies, mais elles ne peuvent pas être étendues à moins que les extensions de commandes ne soient désactivées. Il y a une exception - un nom de variable contenant un seul deux-points à la fin peut être étendu alors que les extensions de commandes sont activées. Cependant, vous ne pouvez pas effectuer des opérations de soustraitance ou de recherche et de remplacement sur des noms de variables se terminant par deux points. Le fichier batch ci-dessous (avec la permission de jeb) démontre ceci: comportement

@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 - un résultat intéressant de l'ordre des règles d'analyse que jeb expose dans son post: lors de la recherche et de remplacer avec une expansion normale, les caractères spéciaux ne doivent pas être échappés (bien qu'ils puissent être cités). Mais lors de l'exécution de la recherche et de remplacer par l'expansion retardée, les caractères spéciaux doivent être échappés (sauf s'ils sont cités).

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

Voici une explication plus détaillée et plus précise de la phase 5 dans réponse de jeb (valide à la fois pour le mode par lots et le mode en ligne de commande)

Note il y a des cas de bord où ces règles échouent:

Voir examen des doublures avec appel 1519870920"

Phase 5) Expansion retardée seulement si l'expansion retardée est activé, et la ligne contient au moins un ! , puis À partir de la gauche, numérisez chaque caractère pour ^ ou ! , et si trouvé, puis

  • 5.1 (signe d'échappement) Nécessaire pour ! ou ^ littéraux
    • si le caractère est un caret ^ alors
      • supprimer le ^
      • scanner le prochain caractère et conservez-le comme un littéral
      • continuer le scan
  • 5.2 (variable expand)
    • si le caractère est ! , alors
      • si les extensions de commandes sont désactivées alors

        Regardez la chaîne suivante de caractères, cassant avant ! ou <LF> , et appelez-les VAR (peut être un vide liste)
        • si le caractère suivant est ! alors
          • si la VAR est définie, alors

            Remplacer !VAR! par la valeur de VAR et continuer le balayage
          • Sinon si le mode de traitement par lots alors

            Supprimer !VAR! et continuer le balayage
          • sinon passez à 5.2.1
        • sinon passez à 5.2.1
      • Sinon si les extensions de commandes sont activées alors

        Regardez la chaîne suivante de caractères, cassant avant ! , : , ou <LF> , et appelez-les VAR (peut être une liste vide). Si VAR casse avant : et le caractère suivant est ! alors inclure : comme dernier caractère dans VAR et break avant !
        • si le caractère suivant est ! alors
          • si VAR existe, alors
            15191010920" Remplacer !VAR! par la valeur de VAR et continuer le balayage
          • Sinon si le mode de traitement par lots alors

            Supprimer !VAR! et continuer le balayage
          • sinon passez à 5.2.1
        • Sinon si le prochain caractère est : alors
          • si VAR n'est pas défini alors
            • Si le mode batch puis

              Supprimer !VAR: et continuer le balayage
            • sinon passez à 5.2.1
          • Sinon si la chaîne suivante de caractères correspond au motif

            ~[integer][,[integer]]! puis

            Remplacer !VAR:~[integer][,[integer]]! par une soustraction de la valeur de VAR (pouvant donner lieu à une chaîne vide) et poursuivre le balayage
          • Sinon si Suivant la chaîne de caractères correspond au motif [*]search=[replace]! , où la recherche peut inclure n'importe quel ensemble de caractères sauf = et <LF> , et remplacer peut inclure n'importe quel ensemble de caractères sauf ! et <LF> , puis

            Remplacer !VAR:[*]search=[replace]! par la valeur de VAR après avoir effectué la recherche et remplacer (ce qui peut donner lieu à une chaîne vide) et continuer le balayage
          • sinon passez à 5.2.1
        • sinon passez à 5.2.1
      • 5.2.1
        • si le mode de traitement par lots, supprimer le !

          Sinon préserver le !
        • continuer le scan en commençant par le caractère suivant après le !
41
répondu dbenham 2018-02-02 20:13:38

comme indiqué, les commandes sont passées la chaîne d'argument entière dans µSoft land, et c'est à eux de la Parser en arguments séparés pour leur propre usage. Il n'y a pas de cohérence entre les différents programmes et, par conséquent, il n'y a pas de règles uniques pour décrire ce processus. Vous devez vraiment vérifier chaque cas de coin pour n'importe quelle bibliothèque C que votre programme utilise.

en ce qui concerne les fichiers du système .bat , voici le 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

Maintenant, nous pouvons faire quelques tests. Voyez si vous pouvez comprendre ce que µSoft essaie de faire:

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

très bien jusqu'à présent. (Je laisse de côté les inintéressants %cmdcmdline% et %0 à partir de maintenant.)

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

pas d'extension de nom de fichier.

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

Pas de citer le décapage, bien que les citations n'prévenir l'argument de la séparation.

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

doubles citations consécutives leur fait perdre toute capacité d'analyse spéciale qu'ils auraient pu avoir. Exemple de @ Beniot:

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

Quiz: Comment passer la valeur d'une var d'environnement comme un argument simple (i.e., comme %1 ) à un fichier 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!

Sane parsing semble à jamais cassé.

Pour votre divertissement, essayez d'ajouter divers ^ , \ , ' , & (&c.) les caractères de ces exemples.

8
répondu bobbogo 2014-08-05 18:48:44

vous avez déjà quelques grandes réponses ci-dessus, mais pour répondre à une partie de votre question:

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

ce qui se passe là-bas est que parce que vous avez un espace avant le =, une variable est créée appelée %a<space>% donc, quand vous echo %a % qui est évalué correctement comme b .

la partie restante b% c% est alors évaluée en tant que texte simple + une variable non définie % c% , qui devrait être reprise comme dactylographiée, pour moi echo %a %b% c% renvoie bb% c%

je soupçonne que la capacité d'inclure des espaces dans des noms de variables est plus un oubli qu'une "caractéristique" prévue 151990920""

5
répondu ss64 2014-08-11 21:01:13

edit: voir réponse acceptée, ce qui suit est erroné et explique seulement comment passer une ligne de commande à TinyPerl.


en ce qui concerne les citations, j'ai le sentiment que le comportement est le suivant:

  • quand un " est trouvé, chaîne de caractères globbing commence
  • quand la chaîne se globalise:
    • tout caractère qui n'est pas un " est globbed
    • quand un " :
      • si elle est suivie par "" (donc un triple " ), puis un guillemet double est ajouté à la chaîne
      • si elle est suivie de " (donc un double " ) alors une double citation est ajoutée à la chaîne et la chaîne globbing ends
      • si le caractère suivant n'est pas " , les fins de chaîne globbing
    • quand la ligne se termine, la corde se termine.

en bref:

"a """ b "" c""" se compose de deux cordes: a " b " et c"

"a"" , "a""" et "a"""" sont tous la même chaîne si à la fin d'une ligne

0
répondu Benoit 2011-11-01 19:26:18