Comment l'interpréteur de commandes Windows (CMD.EXE) analyse-t-il les scripts?

142

Je suis tombé sur ss64.com qui fournit une bonne aide sur la façon d'écrire des scripts batch que l'interpréteur de commandes Windows exécutera.

Cependant, je n'ai pas pu trouver une bonne explication de la grammaire des scripts batch, comment les choses se développent ou ne s'étendent pas, et comment échapper aux choses.

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

  • Comment le système de devis est-il géré? J'ai créé un script TinyPerl
    ( foreach $i (@ARGV) { print '*' . $i ; }), je l'ai compilé et je l'ai appelé ainsi:
    • my_script.exe "a ""b"" c" → la sortie est *a "b*c
    • my_script.exe """a b c""" → le sortir *"a*b*c"
  • Comment fonctionne la echocommande interne ? Qu'est-ce qui est développé dans cette commande?
  • Pourquoi dois-je utiliser for [...] %%Idans des scripts de fichiers, mais for [...] %Idans des sessions interactives?
  • Quels sont les personnages d'échappement et dans quel contexte? Comment échapper à un signe de pourcentage? Par exemple, comment puis-je faire écho %PROCESSOR_ARCHITECTURE%littéralement? J'ai trouvé que ça echo.exe %""PROCESSOR_ARCHITECTURE%marche, y a-t-il une meilleure solution?
  • Comment les paires de %match? 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 lors de l'utilisation de la setcommande? Par exemple, si je fais set a=a" bet alors echo.%a%j'obtiens a" b. Si j'utilise cependant echo.exedes UnxUtils, j'obtiens a b. Comment se %a%développe-t-il d'une manière différente?

Merci pour tes lumières.

Benoit
la source
Rob van der Woude a un superbe script Batch et une référence d'invite de commande Windows sur son site.
JBRWilkinson

Réponses:

200

Nous avons effectué des expériences pour étudier la grammaire des scripts batch. Nous avons également étudié les différences entre les modes batch et ligne de commande.

Analyseur de ligne par lots:

Voici un bref aperçu des phases de l'analyseur de ligne de fichier de commandes:

Phase 0) Lire la ligne:

Phase 1) Pourcentage d'expansion:

Phase 2) Traiter les caractères spéciaux, tokenize et créer un bloc de commande mis en cache: il s'agit d'un processus complexe qui est affecté par des éléments tels que les guillemets, les caractères spéciaux, les délimiteurs de jetons et les échappements d'insertion.

Phase 3) Faire écho à la ou aux commandes analysées Uniquement si le bloc de commande n'a pas commencé par @, et ECHO était ON au début de l'étape précédente.

Phase 4) Extension de %Xvariable FOR : Uniquement si une commande FOR est active et que les commandes après DO sont en cours de traitement.

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

Phase 5.3) Traitement des tuyaux: Uniquement si les commandes se trouvent de chaque côté d'un tuyau

Phase 5.5) Exécuter la redirection:

Phase 6) Traitement CALL / Doublage du curseur: Uniquement si le jeton de commande est CALL

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


Voici les détails de chaque phase:

Notez que les phases décrites ci-dessous ne sont qu'un modèle du fonctionnement de l'analyseur par lots. Les composants internes réels de cmd.exe peuvent ne pas refléter ces phases. Mais ce modèle est efficace pour prédire le comportement des scripts batch.

Phase 0) Lire la ligne: Lisez d'abord la ligne d'entrée <LF>.

  • Lors de la lecture d'une ligne à analyser en tant que commande, <Ctrl-Z>(0x1A) est lu comme <LF>(LineFeed 0x0A)
  • Lorsque GOTO ou CALL lit des lignes tout en recherchant un: label <Ctrl-Z>,, est traité comme lui-même - il n'est pas converti en<LF>

Phase 1) Pourcentage d'expansion:

  • Un double %%est remplacé par un simple%
  • Expansion des arguments ( %*, %1, %2, etc.)
  • Expansion de %var%, si var n'existe pas, remplacez-la par rien
  • La ligne est tronquée au début, <LF>pas dans l' %var%expansion
  • Pour une explication complète, lisez la première moitié de ceci sur dbenham Même fil: Phase de pourcentage

Phase 2) Traiter les caractères spéciaux, tokenize et créer un bloc de commande mis en cache: il s'agit d'un processus complexe qui est affecté par des éléments tels que les guillemets, les caractères spéciaux, les délimiteurs de jetons et les échappements d'insertion. Ce qui suit est une approximation de ce processus.

Certains concepts sont importants tout au long de cette phase.

  • Un jeton est simplement une chaîne de caractères qui est traitée comme une unité.
  • Les jetons sont séparés par des délimiteurs de jetons. Les délimiteurs de jetons standard sont <space> <tab> ; , = <0x0B> <0x0C>et <0xFF>
    Les délimiteurs de jetons consécutifs sont traités comme un seul - il n'y a pas de jetons vides entre les délimiteurs de jetons
  • Il n'y a pas de délimiteurs de jeton dans une chaîne entre guillemets. La chaîne entière entre guillemets est toujours traitée comme faisant partie d'un seul jeton. Un seul jeton peut être constitué d'une combinaison de chaînes entre guillemets et de caractères non entre guillemets.

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

Regardez chaque caractère de gauche à droite:

  • Si <CR>alors supprimez-le, comme s'il n'était jamais là (sauf pour un comportement de redirection étrange )
  • Si un signe d'insertion ( ^), le caractère suivant est échappé et le signe d'insertion d'échappement est supprimé. Les caractères échappés perdent toute signification spéciale (sauf pour <LF>).
  • S'il s'agit d'un guillemet ( "), activez l'indicateur de citation. Si l'indicateur de devis est actif, alors seulement "et <LF>sont spéciaux. Tous les autres caractères perdent leur signification spéciale jusqu'à ce que la citation suivante désactive l'indicateur de citation. Il n'est pas possible d'échapper au devis de clôture. Tous les caractères entre guillemets sont toujours dans le même jeton.
  • <LF>désactive toujours l'indicateur de citation. D'autres comportements varient selon le contexte, mais les citations ne modifient jamais le comportement de <LF>.
    • Échappé <LF>
      • <LF> est dépouillé
      • Le caractère suivant est échappé. Si à la fin du tampon de ligne, alors la ligne suivante est lue et traitée par les phases 1 et 1.5 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 littéral, ce qui signifie que ce processus n'est pas récursif.
    • Sans échappement <LF>pas entre parenthèses
      • <LF> est supprimé et l'analyse de la ligne actuelle est terminée.
      • Tous les caractères restants dans le tampon de ligne sont simplement ignorés.
    • Sans échappement <LF>dans un bloc entre parenthèses FOR IN
      • <LF> est converti en un <space>
      • Si à la fin du tampon de ligne, la ligne suivante est lue et ajoutée à la ligne courante.
    • Sans échappement <LF>dans un bloc de commande 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, la ligne suivante est lue et ajoutée à l'espace.
  • Si l'un des caractères spéciaux & | <ou >, divisez la ligne à ce stade afin de gérer les tubes, la concaténation des commandes et la redirection.
    • Dans le cas d'un tube ( |), chaque côté est une commande séparée (ou bloc de commandes) qui reçoit un traitement spécial dans la phase 5.3
    • Dans le cas de &, &&ou de ||concaténation de commandes, chaque côté de la concaténation est traité comme une commande distincte.
    • Dans le cas de <, <<, >ou la >>redirection, la clause de redirection est analysé, retiré temporairement, puis ajouté à la fin de la commande en cours. Une clause de redirection se compose d'un chiffre de descripteur de fichier facultatif, 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 non échappé, le chiffre spécifie le descripteur de fichier à rediriger. Si le jeton de poignée n'est pas trouvé, la redirection de sortie prend par défaut la valeur 1 (stdout) et la redirection d'entrée par défaut sur 0 (stdin).
  • Si le tout premier jeton pour cette commande (avant de déplacer la redirection vers la fin) commence par @, alors le @a une signification particulière. ( @n'est pas spécial dans aucun autre contexte)
    • Le spécial @est supprimé.
    • Si ECHO est activé, cette commande, ainsi que toutes les commandes concaténées suivantes 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.
  • Traiter les parenthèses (fournit des instructions composées sur plusieurs lignes):
    • Si l'analyseur ne recherche pas de jeton de commande, ce (n'est pas spécial.
    • Si l'analyseur recherche un jeton de commande et le trouve (, 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'instruction composée et décrémente le compteur de parenthèses.
    • Si la fin de la ligne est atteinte et que le compteur de parenthèses est> 0, la ligne suivante sera ajoutée à l'instruction composée (recommence avec la phase 0)
    • Si le compteur de parenthèses est égal à 0 et que l'analyseur recherche une commande, alors )fonctionne comme une REMinstruction tant qu'elle est immédiatement suivie d'un délimiteur de jeton, d'un caractère spécial, d'une nouvelle ligne ou d'une fin de fichier
      • Tous les caractères spéciaux perdent leur signification sauf ^(la concaténation des lignes est possible)
      • Une fois que la fin de la ligne logique est atteinte, la "commande" entière est rejetée.
  • Chaque commande est analysée en une série de jetons. Le premier jeton est toujours traité comme un jeton de commande (après que les spéciaux @ont été supprimés et la redirection déplacée vers la fin).
    • Les délimiteurs de jeton avant le jeton de commande sont supprimés
    • Lors de l'analyse du jeton de commande, (fonctionne comme un délimiteur de jeton de commande, en plus des délimiteurs de jeton standard
    • La gestion des jetons suivants dépend de la commande.
  • La plupart des commandes concaténent simplement tous les arguments après le jeton de commande en un seul jeton d'argument. Tous les délimiteurs de jeton d'argument sont conservés. Les options d'argument ne sont généralement pas analysées avant la phase 7.
  • Trois commandes bénéficient d'un traitement spécial - IF, FOR et REM
    • IF est divisé en deux ou trois parties distinctes qui sont traitées indépendamment. Une erreur de syntaxe dans la construction IF entraînera une erreur de syntaxe fatale.
      • L'opération de comparaison est la commande réelle qui passe tout au long de la phase 7
        • Toutes les options IF sont entièrement analysées en phase 2.
        • Les délimiteurs de jetons consécutifs se réduisent en un seul espace.
        • En fonction de l'opérateur de comparaison, il y aura un ou deux jetons de valeur qui seront identifiés.
      • Le bloc de commande True est l'ensemble des commandes après la condition et est analysé comme n'importe quel autre bloc de commande. Si ELSE doit être utilisé, le bloc True doit être entre parenthèses.
      • Le bloc de commande optionnel False est l'ensemble des commandes après ELSE. Encore une fois, ce bloc de commande est analysé normalement.
      • Les blocs de commande Vrai et Faux ne sont pas automatiquement transférés dans les phases suivantes. Leur traitement ultérieur est contrôlé par la phase 7.
    • FOR est divisé en deux après le DO. Une erreur de syntaxe dans la construction FOR entraînera une erreur de syntaxe fatale.
      • La partie passant par DO est la commande d'itération FOR réelle qui traverse la phase 7
        • Toutes les options FOR sont entièrement analysées en phase 2.
        • La clause entre parenthèses IN est traitée <LF>comme <space>. Une fois la clause IN analysée, tous les jetons sont concaténés pour former un seul jeton.
        • Les délimiteurs de jetons consécutifs sans échappement / sans guillemets se réduisent en un seul espace tout au long de la commande FOR via DO.
      • La partie après DO est un bloc de commande qui est analysé 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 manière radicalement différente de toutes les autres commandes.
      • Un seul jeton d'argument est analysé - l'analyseur ignore les caractères après le premier jeton d'argument.
      • La commande REM peut apparaître dans la sortie de la phase 3, mais la commande n'est jamais exécutée et le texte de l'argument d'origine est renvoyé - les carets d'échappement ne sont pas supprimés, sauf ...
        • S'il n'y a qu'un seul jeton d'argument qui se termine par un jeton sans échappement ^qui termine la ligne, 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 que le dernier caractère ne soit pas ^.
  • Si le jeton de commande commence par :, et que c'est le premier tour de la phase 2 (pas un redémarrage dû à CALL en phase 6), alors
    • Le jeton est normalement traité comme une étiquette non exécutée .
      • Mais le reste de la ligne est analysée, ), <, >, &et |ne plus avoir une signification particulière. Tout le reste de la ligne est considéré comme faisant partie de l'étiquette «commande».
      • Le ^continue d'être spécial, ce qui signifie que la continuation de ligne peut être utilisée pour ajouter la ligne suivante à l'étiquette.
      • Une étiquette non exécutée dans un bloc entre parenthèses entraînera une erreur de syntaxe fatale à moins qu'elle ne soit immédiatement suivie d'une commande ou d'une étiquette exécutée sur la ligne suivante.
        • (n'a plus de signification particulière pour la première commande qui suit l' étiquette non exécutée .
      • La commande est abandonnée une fois l'analyse des étiquettes terminée. Les phases suivantes n'ont pas lieu pour le label
    • Il existe trois exceptions qui peuvent entraîner le traitement d'une étiquette trouvée dans la phase 2 comme une étiquette exécutée qui continue l'analyse jusqu'à la phase 7.
      • Il y a redirection qui précède l'étiquette jeton, et il y a un |tuyau ou &, &&ou ||concaténation de commande sur la ligne.
      • Il y a une redirection qui précède le jeton d'étiquette et la commande se trouve dans un bloc entre parenthèses.
      • Le jeton d'étiquette est la toute première commande sur une ligne dans un bloc entre parenthèses, et la ligne ci-dessus se termine par une étiquette non exécutée .
    • Ce qui suit se produit lorsqu'une étiquette exécutée est découverte dans la phase 2
      • L'étiquette, ses arguments et sa redirection sont tous exclus de toute sortie d'écho en phase 3
      • Toutes les commandes concaténées ultérieures sur la ligne sont entièrement analysées et exécutées.
    • Pour plus d' informations sur les étiquettes Exécuté par rapport aux étiquettes non exécutés , voir https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Phase 3) Faire écho à la ou aux commandes analysées Uniquement si le bloc de commande n'a pas commencé par @, et ECHO était ON au début de l'étape précédente.

Phase 4) Extension de %Xvariable FOR : Uniquement si une commande FOR est active 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 une variable FOR comme %%Xen %X. La ligne de commande a des règles d'expansion de pourcentage différentes pour la phase 1. C'est la raison pour laquelle les lignes de commande utilisent %Xmais les fichiers de commandes utilisent %%Xpour les variables FOR.
  • Les noms de variables FOR sont sensibles à la casse, mais ~modifiersne le sont pas.
  • ~modifiersont la priorité sur les noms de variables. Si un caractère suivant ~est à la fois un modificateur et un nom de variable FOR valide et qu'il existe un caractère suivant qui est un nom de variable FOR actif, le caractère est interprété comme un modificateur.
  • Les noms de variables FOR sont globaux, mais uniquement dans le contexte d'une clause DO. Si une routine est CALLed à partir d'une clause FOR DO, les variables FOR ne sont pas développées dans la routine CALLed. Mais si la routine a sa propre commande FOR, alors toutes les variables FOR actuellement définies sont accessibles aux commandes DO internes.
  • Les noms de variables FOR peuvent être réutilisés dans des FOR imbriqués. La valeur FOR interne est prioritaire, mais une fois que INNER FOR se ferme, la valeur FOR externe est restaurée.
  • Si ECHO était sur ON au début de cette phase, alors la phase 3) est répétée pour afficher les commandes DO analysées une fois que les variables FOR ont été développées.

---- A partir de ce moment, chaque commande identifiée en 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: Uniquement si l'expansion retardée est activée, la commande n'est pas dans un bloc entre parenthèses de chaque côté d'un tube , et la commande n'est pas un script batch "nu" (nom du script sans parenthèses, CALL, concaténation de commandes, ou tuyau).

  • Chaque jeton d'une commande est analysé indépendamment pour une expansion retardée.
    • La plupart des commandes analysent deux jetons ou plus: le jeton de commande, le jeton d'arguments et chaque jeton de destination de redirection.
    • La commande FOR analyse uniquement le jeton de clause IN.
    • La commande IF analyse uniquement les valeurs de comparaison - une ou deux, selon l'opérateur de comparaison.
  • Pour chaque jeton analysé, vérifiez d'abord s'il en contient !. Sinon, le jeton n'est pas analysé - ce qui est important pour les ^caractères. Si le jeton contient !, scannez chaque caractère de gauche à droite:
    • S'il s'agit d'un signe d'insertion ( ^), le caractère suivant n'a pas de signification particulière, le signe d'insertion lui-même est supprimé
    • S'il s'agit d'un point d'exclamation, recherchez le point d'exclamation suivant (les carets ne sont plus observés), développez jusqu'à la valeur de la variable.
      • Les ouvertures consécutives !sont regroupées en une seule!
      • Tout élément non apparié restant !est supprimé
    • L'expansion des variables à ce stade est "sûre", 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 deuxième moitié de ce fil de discussion sur dbenham même - Phase d'exclamation

Phase 5.3) Traitement des tuyaux: Uniquement si les commandes sont de chaque côté d'un tuyau.
Chaque côté du tuyau est traité indépendamment et de manière asynchrone.

  • Si la commande est interne à cmd.exe, ou s'il s'agit d'un fichier de commandes, ou s'il s'agit d'un bloc de commande entre parenthèses, elle est exécutée dans un nouveau thread cmd.exe via %comspec% /S /D /c" commandBlock", de sorte que le bloc de commande obtient un redémarrage de phase, mais cette fois en mode ligne de commande.
    • Si un bloc de commande entre parenthèses, alors tous <LF>avec une commande avant et après sont convertis en <space>&. D'autres <LF>sont dépouillés.
  • C'est la fin du traitement des commandes pipe.
  • Voir Pourquoi l'expansion retardée échoue-t-elle à l'intérieur d'un bloc de code canalisé? pour en savoir plus sur l'analyse et le traitement des tuyaux

Phase 5.5) Exécuter la redirection: Toute redirection découverte dans la phase 2 est maintenant exécutée.

Phase 6) Traitement de l'APPEL / Doublage du curseur: Uniquement si le jeton de commande est CALL, ou si le texte avant le premier délimiteur de jeton standard survenant est CALL. Si CALL est analysé à partir d'un jeton de commande plus grand, la partie inutilisée est ajoutée au jeton d'arguments avant de continuer.

  • Scannez le jeton d'arguments pour un /? . S'il se trouve n'importe où dans les jetons, abandonnez la phase 6 et passez à la phase 7, où l'AIDE pour l'APPEL sera imprimée.
  • Retirez le premier CALL , afin que plusieurs appels puissent être empilés
  • Double tous les carets
  • Redémarrez les phases 1, 1.5 et 2, mais ne passez pas à la phase 3
    • Tous les caractères doublés sont ramenés à un caret tant qu'ils ne sont pas cités. Mais malheureusement, les carets cités restent doublés.
    • La phase 1 change un peu
      • Les erreurs d'expansion à l'étape 1.2 ou 1.3 annulent l'APPEL, mais l'erreur n'est pas fatale - le traitement par lots se poursuit.
    • Les tâches de la phase 2 sont un peu modifiées
      • Toute nouvelle redirection non entre guillemets et sans échappement qui n'a pas été détectée au premier tour de la phase 2 est détectée, mais elle est supprimée (y compris le nom de fichier) sans effectuer réellement la redirection
      • Tout signe d'insertion non entre guillemets et sans échappement nouvellement apparu à la fin de la ligne est supprimé sans effectuer de continuation de ligne
      • L'appel est interrompu sans erreur si l'un des éléments suivants est détecté
        • Apparaissant récemment non cité, non échappé &ou|
        • Le jeton de commande résultant commence par sans guillemets, sans échappement (
        • Le tout premier jeton après l'appel supprimé a commencé par @
      • Si la commande résultante est un IF ou FOR apparemment valide, l'exécution échouera par la suite avec une erreur indiquant que IFouFOR n'est pas reconnue comme une commande interne ou externe.
      • Bien sûr, l'APPEL n'est pas abandonné dans ce 2ème tour de la phase 2 si le jeton de commande résultant est une étiquette commençant par :.
  • Si le jeton de commande résultant est CALL, redémarrez la phase 6 (se répète jusqu'à ce qu'il n'y ait plus d'appels)
  • Si le jeton de commande résultant est un script batch ou une étiquette:, l'exécution de l'APPEL est entièrement gérée par le reste de la phase 6.
    • Poussez la position actuelle du fichier de script de commandes sur la pile d'appels afin que l'exécution puisse reprendre à partir de la position correcte lorsque l'APPEL est terminé.
    • Configurez les jetons d'argument% 0,% 1,% 2, ...% N et% * pour l'APPEL, en utilisant tous les jetons résultants
    • Si le jeton de commande est une étiquette commençant par :, alors
      • Redémarrez la phase 5. Cela peut avoir un impact sur ce que: l'étiquette est appelée. Mais comme les jetons% 0 etc. ont déjà été configurés, cela ne modifiera pas les arguments qui sont passés à la routine CALLed.
      • Exécutez l'étiquette GOTO pour positionner le pointeur de fichier au début du sous-programme (ignorez tous les autres jetons qui peuvent suivre l'étiquette:) Voir la phase 7 pour les règles sur le fonctionnement de GOTO.
    • Sinon, transférez le contrôle au script batch spécifié.
    • L'exécution de l'étiquette ou du script CALLed: continue jusqu'à ce que EXIT / B ou la fin du fichier soit atteint, à quel point la pile CALL est sautée et l'exécution reprend à partir de la position du fichier enregistré.
      La phase 7 n'est pas exécutée pour les scripts CALLed ou: labels.
  • Sinon, le résultat de la phase 6 passe à la phase 7 pour exécution.

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

  • 7.1 - Exécuter la commande interne - Si le jeton de commande est cité, alors sautez cette étape. Sinon, essayez d'analyser une commande interne et de l'exécuter.
    • Les tests suivants sont effectués pour déterminer si un jeton de commande sans guillemets représente une commande interne:
      • Si le jeton de commande correspond exactement à une commande interne, exécutez-la.
      • Sinon casser 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 en mode ligne de commande, ou si la commande provient d'un bloc entre parenthèses, si un bloc de commande vrai ou faux, un bloc de commande FOR DO ou si elle est impliquée dans la concaténation de commandes, exécutez la commande interne
        • Sinon (doit être une commande autonome en mode de traitement par lots) scannez le dossier actuel et le PATH pour un fichier .COM, .EXE, .BAT ou .CMD dont le nom de base correspond au jeton de commande d'origine
          • Si le premier fichier correspondant est un .BAT ou .CMD, allez à 7.3.exec et exécutez ce script
          • Sinon (correspondance non trouvée ou première correspondance .EXE ou .COM) exécutez la commande interne mémorisée
      • Sinon coupez le jeton de commande avant la première occurrence de . \ou :
        Si le texte précédent n'est pas une commande interne, allez à 7.2
        Sinon, le texte précédent peut être une commande interne. Souvenez-vous de cette commande.
      • Casser 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 jeton de commande plus grand, la partie inutilisée du jeton de commande est incluse dans la liste d'arguments
    • Ce n'est pas parce qu'un jeton de commande est analysé 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 la syntaxe autorisée.
    • Toutes les commandes internes afficheront l'aide au lieu d'exécuter leur fonction si elle /?est détectée. La plupart reconnaissent /?s'il 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 un guillemet avant que le nom de la variable et les extensions ne soient activés
        set "name=content" ignored -> valeur = content
        alors le texte entre le premier signe égal et le dernier guillemet est utilisé comme contenu (premier égal et dernier guillemet exclus). Le texte après le dernier guillemet est ignoré. S'il n'y a pas de guillemet après le signe égal, le reste de la ligne est utilisé comme contenu.
      • Si une commande SET n'a pas de guillemet avant le nom
        set name="content" not ignored -> valeur = "content" not ignored
        alors tout le reste de la ligne après l'égal est utilisé comme contenu, y compris tous les guillemets qui peuvent être présents.
    • Une comparaison IF est évaluée et, selon que la condition est vraie ou fausse, le bloc de commande dépendant déjà analysé approprié est traité, en commençant par la phase 5.
    • La clause IN d'une commande FOR est itérée de manière appropriée.
      • S'il s'agit d'un FOR / F qui itère la sortie d'un bloc de commande, alors:
        • La clause IN est exécutée dans un nouveau processus cmd.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émarrera sur ON et l'expansion différée commencera généralement désactivée (en fonction du paramètre de registre)
        • Toutes les modifications d'environnement effectuées par le bloc de commande de la clause IN seront perdues une fois le processus cmd.exe enfant terminé
      • Pour chaque itération:
        • Les valeurs de la variable FOR sont définies
        • Le bloc de commande DO déjà analysé est ensuite traité en commençant par la phase 4.
    • GOTO utilise la logique suivante pour localiser le: label
      • L'étiquette est analysée à partir du premier jeton d'argument
      • Le script est analysé pour la prochaine occurrence de l'étiquette
        • L'analyse commence à partir de la position actuelle du fichier
        • Si la fin du fichier est atteinte, l'analyse revient au début du fichier et continue au point de départ d'origine.
      • L'analyse s'arrête à la première occurrence de l'étiquette trouvée et le pointeur de fichier est défini 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 les boucles FOR.
      • 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 s'affiche. Cela fonctionne effectivement comme un EXIT / B, sauf que toutes les commandes déjà analysées dans le bloc de commande actuel qui suivent le GOTO sont toujours exécutées, mais dans le contexte du CALLer (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.
    • RENAME et COPY acceptent tous les deux des caractères génériques pour les chemins source et cible. Mais Microsoft fait un travail terrible en documentant le fonctionnement des caractères génériques, en particulier pour le chemin cible. Un ensemble utile de règles génériques peut être trouvé dans Comment la commande Windows RENAME interprète-t-elle les caractères génériques?
  • 7.2 - Exécuter le changement de volume - Sinon, si le jeton de commande ne commence pas par un guillemet, fait exactement deux caractères et le 2ème caractère est un deux-points, changez le volume
    • Tous les jetons d'argument sont ignorés
    • Si le volume spécifié par le premier caractère est introuvable, abandonnez avec une erreur
    • Un jeton 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 modifié, il ne sera pas traité comme une étiquette.
  • 7.3 - Exécuter une commande externe - Sinon, essayez de traiter la commande comme une commande externe.
    • Si en mode ligne de commande et la commande n'est pas cité et ne commence pas par une spécification de volume, l' espace blanc, ,, ;,= ou +alors casser la commande jeton à la première occurrence <space> , ;ou =et préfixer le reste à l'argument jeton (s).
    • Si le deuxième caractère du jeton de commande est un signe deux-points, vérifiez que le volume spécifié par le premier caractère peut être trouvé.
      Si le volume est introuvable, abandonnez avec une erreur.
    • Si en mode batch et que le jeton de commande commence par : , alors allez à 7.4
      Notez que si le jeton d'étiquette commence par ::, alors cela ne sera pas atteint car l'étape précédente aura abandonné 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 actuel, le répertoire actuel, la variable PATH, la variable PATHEXT et / ou les associations de fichiers.
      • Si une commande externe valide ne peut pas être identifiée, abandonnez avec une erreur.
    • Si en mode ligne de commande et que le jeton de commande commence par :, alors allez à 7.4
      Notez que cela est rarement atteint car l'étape précédente aura été abandonnée avec une erreur à moins que le jeton de commande ne commence par:: , et SUBST est utilisé pour définir un volume pour ::, et le le jeton de commande entier est un chemin valide vers une commande externe.
    • 7.3.exec - Exécute la commande externe.
  • 7.4 - Ignorer une étiquette - Ignorer la commande et tous ses arguments si le jeton de commande commence par :.
    Les règles en 7.2 et 7.3 peuvent empêcher une étiquette d'atteindre ce point.

Analyseur de ligne de commande:

Fonctionne comme le BatchLine-Parser, sauf:

Phase 1) Pourcentage d'expansion:

  • Non %* ,%1 etc. extension d'argument
  • Si var n'est pas défini, alors %var% est laissé inchangé.
  • Aucune manipulation particulière de %%. Si var = content, alors%%var%% développe en%content% .

Phase 3) Faire écho à la ou aux commandes analysées

  • 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: uniquement si DelayedExpansion est activée

  • Si var n'est pas défini, alors !var! est laissé inchangé.

Phase 7) Exécuter la commande

  • Les tentatives de CALL ou GOTO a: label génèrent une erreur.
  • Comme déjà documenté dans la phase 7, une étiquette exécutée peut entraîner une erreur dans différents scénarios.
    • Les étiquettes exécutées par lots ne peuvent provoquer une erreur que si elles commencent par ::
    • Les étiquettes exécutées en ligne de commande entraînent presque toujours une erreur

Analyse des valeurs entières

Il existe de nombreux contextes différents dans lesquels cmd.exe analyse les valeurs entières des chaînes et les règles sont incohérentes:

  • SET /A
  • IF
  • %var:~n,m% (expansion de sous-chaîne variable)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Les détails de ces règles peuvent être trouvés dans Règles pour la manière dont CMD.EXE analyse les nombres


Pour toute personne souhaitant améliorer les règles d'analyse cmd.exe, il existe un sujet de discussion sur le forum DosTips où les problèmes peuvent être signalés et des suggestions.

J'espère que cela aide
Jan Erik (jeb) - Auteur original et découvreur de phases
Dave Benham (dbenham) - Beaucoup de contenu et d'édition supplémentaires

dbenham
la source
4
Bonjour jeb, merci pour votre perspicacité… C'est peut-être difficile à comprendre, mais je vais essayer d'y réfléchir! Vous semblez avoir effectué de nombreux tests! Merci d'avoir traduit ( administrator.de/… )
Benoit
2
Batch phase 5) - %% a aura déjà été changé en% a dans la phase 1, donc l'expansion de la boucle for étend vraiment% a. De plus, j'ai ajouté une explication plus détaillée de la phase 1 du lot dans une réponse ci-dessous (je n'ai pas le privilège d'édition)
dbenham
3
Jeb - peut-être que la phase 0 pourrait être déplacée et combinée avec la phase 6? Cela a plus de sens pour moi, ou y a-t-il une raison pour laquelle ils sont séparés comme ça?
dbenham
1
@aschipfl - J'ai mis à jour cette section. Le )fonctionne vraiment presque comme une REMcommande lorsque le compteur de parenthèses est 0. Essayez les deux à partir de la ligne de commande ) Ignore thisecho OK & ) Ignore this
:,
1
@aschipfl oui c'est correct, donc vous voyez parfois 'set "var =% expr%"! 'le dernier point d'exclamation sera supprimé mais force la phase 5
jeb
62

Lors de l'appel d'une commande à partir d'une fenêtre de commande, la tokenisation des arguments de la ligne de commande n'est pas effectuée par cmd.exe(alias "le shell"). Le plus souvent, la tokenisation est effectuée par le runtime 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 argvet de traiter la ligne de commande brute pour elle-même (par exemple avec GetCommandLine () ). Au niveau du système d'exploitation, Windows transmet les lignes de commande non validées sous forme de chaîne unique aux nouveaux processus. Cela contraste avec la plupart des shells * nix, où le shell tokenise les arguments de manière cohérente et prévisible avant de les transmettre au processus nouvellement formé. Tout cela signifie que vous pouvez rencontrer des comportements de tokenisation des arguments extrêmement divergents entre différents programmes sous Windows, car les programmes individuels prennent souvent en main la tokenisation des arguments.

Si cela ressemble à de l'anarchie, c'est en quelque sorte. Cependant, étant donné qu'un grand nombre de programmes Windows n'utiliser le moteur d' exécution Microsoft C / C ++ de , il peut être généralement utile de comprendre comment le MSVCRT tokenizes arguments. Voici un extrait:argv

  • Les arguments sont délimités par un espace blanc, qui est un espace ou une tabulation.
  • Une chaîne entourée de guillemets doubles est interprétée comme un argument unique, quel que soit l'espace blanc contenu à l'intérieur. Une chaîne entre guillemets peut être incorporée dans un argument. Notez que le signe d'insertion (^) n'est pas reconnu comme un caractère d'échappement ou un délimiteur.
  • Un guillemet double précédé d'une barre oblique inverse, \ ", est interprété comme un guillemet double littéral (").
  • Les barres obliques inverses sont interprétées littéralement, à moins qu'elles ne précèdent immédiatement un guillemet double.
  • Si un nombre pair de barres obliques inverses est suivi d'un guillemet double, une barre oblique inverse () est placée dans le tableau argv pour chaque paire de barres obliques inverses (\), et le guillemet double (") est interprété comme un délimiteur de chaîne.
  • Si un nombre impair de barres obliques inverses est suivi d'un guillemet double, une barre oblique inverse () est placée dans le tableau argv pour chaque paire de barres obliques inverses (\) et le guillemet double est interprété comme une séquence d'échappement par la barre oblique inverse restante, provoquant un guillemet double littéral (") à placer dans argv.

Le "langage par lots" de Microsoft ( .bat) ne fait pas exception à cet environnement anarchique, et il a développé ses propres règles uniques pour la tokenisation et l'échappement. Il semble également que l'invite de commande de cmd.exe effectue un prétraitement de l'argument de ligne de commande (principalement pour la substitution de variable et l'échappement) avant de transmettre l'argument au processus qui vient d'être exécuté. Vous pouvez en savoir plus sur les détails de bas niveau du langage batch et de l'échappement de cmd dans les excellentes réponses de jeb et dbenham sur cette page.


Construisons un simple utilitaire de ligne de commande en C et voyons ce qu'il dit à propos 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;
}

(Remarques: argv [0] est toujours le nom de l'exécutable, et est omis ci-dessous par souci de concision. 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]
Mike Clark
la source
Merci pour votre réponse. Je suis encore plus perplexe de voir que TinyPerl ne sortira pas ce que votre programme produit, et j'ai du mal à comprendre comment [a "b" c]pourrait devenir [a "b] [c]le post-traitement.
Benoit
Maintenant que j'y pense, cette tokenisation de la ligne de commande est probablement entièrement réalisée par le runtime C. Un exécutable pourrait être écrit de telle sorte qu'il n'utilise même pas le runtime C, auquel cas je pense qu'il devrait gérer la ligne de commande mot pour mot, et être responsable de faire sa propre tokenisation (s'il le voulait.) Ou même si votre application utilise le runtime C, vous pouvez choisir d'ignorer argc et argv et simplement obtenir la ligne de commande brute via par exemple Win32 GetCommandLine. Peut-être que TinyPerl ignore argv et symbolise simplement la ligne de commande brute selon ses propres règles.
Mike Clark
4
"N'oubliez pas que du point de vue de Win32, la ligne de commande est juste une chaîne qui est copiée dans l'espace d'adressage du nouveau processus. La manière dont le processus de lancement et le nouveau processus interprètent cette chaîne n'est pas régie par des règles mais par des conventions." -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark
2
Merci pour cette très belle réponse. Cela explique beaucoup à mon avis. Et cela explique aussi pourquoi je trouve parfois ça vraiment nul de travailler avec Windows…
Benoit
J'ai trouvé cela concernant les contre-obliques et les guillemets lors de la transformation de la ligne de commande en argv, pour les programmes Win32 C ++. Le nombre de barres obliques inverses n'est divisé par deux que lorsque la dernière barre oblique inverse est suivie d'un dblquote, et le dblquote termine une chaîne lorsqu'il y a un nombre pair de barres obliques inverses avant.
Benoit du
47

Règles d'expansion en pourcentage

Voici une explication détaillée de la phase 1 dans la réponse de jeb (valable pour le mode batch et le mode ligne de commande).

Phase 1) Pourcentage d'expansion En commençant par la gauche, scannez chaque caractère pour %ou <LF>. Si trouvé alors

  • 1.05 (tronquer la ligne à <LF>)
    • Si le personnage est <LF>alors
      • Supprimez (ignorez) le reste de la ligne à <LF>partir de
      • Aller à la phase 1.5 (bande <CR>)
    • Sinon, le personnage doit l'être %, alors passez à 1.1
  • 1.1 (escape %) ignoré si le mode ligne de commande
    • Si le mode de traitement par lots et suivie d' une autre , %puis
      remplacer %%avec un seul %et de poursuivre l' analyse
  • 1.2 (argument d'extension) ignoré en mode ligne de commande
    • Sinon si mode batch alors
      • Si suivi de *et les extensions de commande sont activées,
        remplacez %*par le texte de tous les arguments de ligne de commande (remplacez par rien s'il n'y a pas d'arguments) et poursuivez l'analyse.
      • Sinon si suivi de <digit>then
        Remplacez %<digit>par la valeur de l'argument (remplacez par rien si non défini) et poursuivez l'analyse.
      • Sinon si suivi de ~et les extensions de commande sont activées, alors
        • Si suivi d'une liste facultative valide de modificateurs d'argument suivie de obligatoire, <digit>alors
          Remplacez %~[modifiers]<digit>par la valeur d'argument modifiée (remplacez par rien si non défini ou si spécifié $ PATH: le modificateur n'est pas défini) et continuez l'analyse.
          Remarque: les modificateurs sont insensibles à 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 de l'argument modifié invalide génère une erreur fatale: toutes les commandes analysées sont abandonnées et le traitement par lots s'interrompt en mode batch!
  • 1.3 (développer la variable)
    • Sinon, si les extensions de commande sont désactivées,
      regardez la chaîne de caractères suivante, coupant avant %ou à la fin du tampon, et appelez-les VAR (peut être une liste vide)
      • Si le caractère suivant est %alors
        • Si VAR est défini,
          remplacez %VAR%par la valeur de VAR et poursuivez l'analyse
        • Sinon si le mode par lots puis
          Supprimer %VAR%et continuer l'analyse
        • Sinon, allez à 1.4
      • Sinon, allez à 1.4
    • Sinon, si les extensions de commande sont activées,
      regardez la chaîne de caractères suivante, interrompant avant % :ou à la fin du tampon, et appelez-les VAR (peut-être une liste vide). Si VAR est interrompu avant :et que le caractère suivant est %alors inclus :comme dernier caractère dans VAR et interrompu avant %.
      • Si le caractère suivant est %alors
        • Si VAR est défini,
          remplacez %VAR%par la valeur de VAR et poursuivez l'analyse
        • Sinon si le mode par lots puis
          Supprimer %VAR%et continuer l'analyse
        • Sinon, allez à 1.4
      • Sinon, si le caractère suivant est :alors
        • Si VAR n'est pas défini, alors
          • Si le mode par lots,
            supprimez %VAR:et poursuivez l'analyse.
          • Sinon, allez à 1.4
        • Sinon si le caractère suivant est ~alors
          • Si la chaîne de caractères suivante correspond au modèle de [integer][,[integer]]%then
            Remplacez-la %VAR:~[integer][,[integer]]%par la sous-chaîne de valeur de VAR (résultant éventuellement en une chaîne vide) et continuez l'analyse.
          • Sinon, allez à 1.4
        • Sinon, si suivi de =ou *=alors La
          syntaxe de recherche et de remplacement de variable non valide génère une erreur fatale: toutes les commandes analysées sont abandonnées et le traitement par lots s'interrompt s'il est en mode batch!
        • Sinon, si la chaîne de caractères suivante correspond au modèle de [*]search=[replace]%, où la recherche peut inclure n'importe quel jeu de caractères sauf =, et remplacer peut inclure n'importe quel jeu de caractères sauf %, alors
          Remplacer %VAR:[*]search=[replace]%par la valeur de VAR après avoir effectué la recherche et le remplacement (résultant éventuellement en une chaîne vide) et continuer analyse
        • Sinon, allez à 1.4
  • 1,4 (bande%)
    • Sinon Si le mode par lots,
      Supprimer %et continuer l'analyse en commençant par le caractère suivant après le%
    • Sinon, conserver le %début et continuer le balayage en commençant par le caractère suivant après le début conservé%

Ce qui précède aide à expliquer 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

Remarque 1 - La phase 1 a lieu avant 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'expansion d'argument invalide ou une syntaxe de recherche et de remplacement de variable invalide!

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

Note 2 - Autre conséquence intéressante des règles d'analyse%: Les variables contenant: dans le nom peuvent être définies, mais elles ne peuvent être développées que si les extensions de commande sont désactivées. Il y a une exception: un nom de variable contenant un simple deux-points à la fin peut être développé pendant que les extensions de commande sont activées. Cependant, vous ne pouvez pas effectuer de sous-chaînes ni d'opérations de recherche et de remplacement sur les noms de variables se terminant par deux-points. Le fichier de commandes ci-dessous (gracieuseté de jeb) illustre ce 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 article: Lors de l'exécution de rechercher et de remplacer avec une expansion retardée, les caractères spéciaux dans les termes de recherche et de remplacement doivent être échappés ou entre guillemets. Mais la situation est différente pour l'expansion en pourcentage - le terme de recherche ne doit pas être échappé (bien qu'il puisse être cité). La chaîne de pourcentage de remplacement peut nécessiter ou non un échappement ou une citation, selon votre intention.

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

Règles d'expansion retardée

Voici une explication développée et plus précise de la phase 5 dans la réponse de jeb (valable à la fois pour le mode batch et le mode ligne de commande)

Phase 5) Expansion retardée

Cette phase est ignorée si l'une des conditions suivantes s'applique:

  • L'expansion retardée est désactivée.
  • La commande se trouve dans un bloc entre parenthèses de chaque côté d'un tuyau.
  • Le jeton de commande entrant est un script batch "nu", ce qui signifie qu'il n'est associé à CALLaucun bloc entre parenthèses, à aucune forme de concaténation de commande ( &, &&ou ||) ou à un tube |.

Le processus d'expansion différée est appliqué aux jetons indépendamment. Une commande peut avoir plusieurs jetons:

  • Le jeton de commande. Pour la plupart des commandes, le nom de la commande lui-même est un jeton. Mais quelques commandes ont des régions spécialisées qui sont considérées comme un TOKEN pour la phase 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKENOù la comparaison est l' une ==, equ, neq, lss, leq, gtr, ougeq
  • Le jeton arguments
  • Le jeton de destination de la redirection (un par redirection)

Aucune modification n'est apportée aux jetons qui n'en contiennent pas !.

Pour chaque jeton qui en contient au moins un !, scannez chaque caractère de gauche à droite pour ^ou !, et s'il est trouvé, alors

  • 5.1 (caret escape) Nécessaire !ou ^littéraux
    • Si le caractère est un signe d'insertion ^alors
      • Retirer le ^
      • Scannez le caractère suivant et conservez-le comme un littéral
      • Continuer le scan
  • 5.2 (développer la variable)
    • Si le caractère est !, alors
      • Si les extensions de commande sont désactivées,
        regardez la chaîne de caractères suivante, avant !ou <LF>, et appelez-les VAR (peut être une liste vide)
        • Si le caractère suivant est !alors
          • Si VAR est défini,
            remplacez !VAR!par la valeur de VAR et poursuivez l'analyse
          • Sinon si le mode par lots puis
            Supprimer !VAR!et continuer l'analyse
          • Sinon, allez à 5.2.1
        • Sinon, allez à 5.2.1
      • Sinon , si les extensions de commandes sont activées alors
        Regardez chaîne de caractères suivante, avant la rupture !, :ou <LF>, et appelez les VAR (peut être une liste vide). Si VAR est interrompu avant :et que le caractère suivant est !alors inclus :comme dernier caractère dans VAR et interrompu avant!
        • Si le caractère suivant est !alors
          • Si VAR existe,
            remplacez !VAR!par la valeur de VAR et poursuivez l'analyse
          • Sinon si le mode par lots puis
            Supprimer !VAR!et continuer l'analyse
          • Sinon, allez à 5.2.1
        • Sinon, si le caractère suivant est :alors
          • Si VAR n'est pas défini, alors
            • Si le mode par lots,
              supprimer !VAR:et continuer l'analyse
            • Sinon, allez à 5.2.1
          • Sinon si le caractère suivant est ~alors
            • Si la chaîne de caractères suivante correspond au modèle de [integer][,[integer]]!then Remplacez !VAR:~[integer][,[integer]]!par la sous-chaîne de valeur de VAR (résultant éventuellement en une chaîne vide) et continuez l'analyse.
            • Sinon, allez à 5.2.1
          • Sinon, si la chaîne de caractères suivante correspond au modèle de [*]search=[replace]!, où la recherche peut inclure n'importe quel jeu de caractères sauf =, et remplacer peut inclure n'importe quel jeu de caractères sauf !, alors
            Remplacer !VAR:[*]search=[replace]!par la valeur de VAR après avoir effectué la recherche et le remplacement (résultant éventuellement en une chaîne vide) et continuer l'analyse
          • Sinon, allez à 5.2.1
        • Sinon, allez à 5.2.1
      • 5.2.1
        • Si le mode de traitement par lots, supprimez le premier !
          Else conserve le!
        • Continuez l'analyse en commençant par le caractère suivant après le début conservé !
dbenham
la source
3
+1, Seules la syntaxe et les règles du deux-points manquent ici pour %definedVar:a=b%vs %undefinedVar:a=b%et les %var:~0x17,-010%formulaires
jeb
2
Bon point - J'ai élargi la section d'expansion variable pour répondre à vos préoccupations. J'ai également développé la section d'expansion des arguments pour remplir certains détails manquants.
dbenham
2
Après avoir reçu des commentaires privés supplémentaires de jeb, j'ai ajouté une règle pour les noms de variables se terminant par deux points, et ajouté la note 2. J'ai également ajouté la note 3 simplement parce que je pensais que c'était intéressant et important.
dbenham
1
@aschipfl - Ouais, j'ai envisagé d'entrer plus en détail à ce sujet, mais je ne voulais pas aller dans ce terrier de lapin. J'étais intentionnellement sans engagement lorsque j'ai utilisé le terme [entier]. Il y a plus d'informations sur Rules pour savoir comment CMD.EXE analyse les nombres .
dbenham
1
Il me manque les règles d'expansion pour le contexte cmd, par exemple, il n'y a pas de caractères réservés pour le premier caractère du nom de variable comme %<digit>, %*ou %~. Et le comportement change pour les variables non définies. Vous devez peut-être ouvrir une deuxième réponse
jeb
7

Comme indiqué, les commandes reçoivent la chaîne d'arguments entière dans μSoft land, et c'est à elles de l'analyser en arguments séparés pour leur propre usage. Il n'y a pas de cohérence entre les différents programmes et il n'y a donc pas un ensemble de règles pour décrire ce processus. Vous devez vraiment vérifier chaque cas de coin pour la bibliothèque C utilisée par votre programme.

En ce qui concerne les .batfichiers système , voici ce 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

Nous pouvons maintenant exécuter des 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 vais laisser de côté l'inintéressant %cmdcmdline%et à %0partir de maintenant.)

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

Aucune extension de nom de fichier.

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

Pas de suppression des guillemets, bien que les guillemets empêchent le fractionnement des arguments.

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

Les guillemets doubles consécutifs leur font perdre toutes les capacités d'analyse spéciales qu'ils auraient pu avoir. L'exemple de @ Beniot:

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

Quiz: Comment passez-vous la valeur d'une variable d'environnement en tant qu'argument unique (c.-à-d. As %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!

Une analyse saine semble à jamais interrompue.

Pour vos loisirs, essayez d' ajouter divers ^, \, ', &(etc.). Caractères à ces exemples.

bobbogo
la source
Pour passer% t% comme argument unique, vous pouvez utiliser "% t:" = \ "%" Autrement dit, utilisez la syntaxe% VAR: str = replacement% pour l'expansion des variables. Métacaractères Shell comme | et & dans le contenu variable peuvent toujours être exposés et gâcher le shell, à moins que vous ne les échappiez à nouveau ....
Toughy
@Toughy Donc, dans mon exemple, tc'est a "b c. Avez - vous une recette pour obtenir ces 6 caractères ( a2 × espace, ", bet capparaître) comme %1dans un .cmd? J'aime ta réflexion. args "%t:"=""%"est assez proche :-)
bobbogo
5

Vous avez déjà quelques bonnes 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, c'est que parce que vous avez un espace avant le =, une variable est créée appelée %a<space>% ainsi lorsque vous echo %a %qui est évaluée correctement comme b.

La partie restante b% c%est ensuite évaluée en tant que texte brut + une variable non définie % c%, qui devrait être renvoyée en écho comme tapée, pour moi echo %a %b% c%renvoiebb% c%

Je soupçonne que la possibilité d'inclure des espaces dans les noms de variables est plus un oubli qu'une `` fonctionnalité '' prévue

SS64
la source
0

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


Concernant les citations, j'ai le sentiment que le comportement est le suivant:

  • quand a "est trouvé, le globbing de chaîne commence
  • lorsque le globbing de chaîne se produit:
    • chaque caractère qui n'est pas un "est globulé
    • quand a "est trouvé:
      • s'il est suivi de ""(donc un triple ") alors un guillemet double est ajouté à la chaîne
      • s'il est suivi de "(donc un double "), alors un guillemet double est ajouté à la chaîne et la chaîne de caractères se termine
      • si le caractère suivant ne l'est pas ", le balayage des chaînes se termine
    • à la fin de la ligne, le balayage des chaînes se termine.

En bref:

"a """ b "" c"""se compose de deux chaînes: a " b "etc"

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

Benoit
la source
le tokenizer et le globbing de chaîne dépendent de la commande! Un "set" fonctionne différemment d'un "appel" ou même d'un "si"
jeb
oui, mais qu'en est-il des commandes externes? Je suppose que cmd.exe leur transmet toujours les mêmes arguments?
Benoit
1
cmd.exe transmet toujours le résultat de l'expansion sous forme de chaîne et non les jetons à une commande externe. Cela dépend de la commande externe comment l'échapper et la tokeniser, findstr utilise une barre oblique inverse le suivant peut utiliser autre chose
jeb
0

Notez que Microsoft a publié le code source de son terminal. Il peut fonctionner de la même manière que la ligne de commande en ce qui concerne l'analyse syntaxique. Peut-être que quelqu'un est intéressé à tester les règles d'analyse par rétro-ingénierie conformément aux règles d'analyse du terminal.

Lien vers le code source.

user7427029
la source