179 votes

Comment le Windows Command Interpreter (CMD.EXE) analyse-t-il les scripts?

J'ai couru dans ss64.com ce qui fournit une bonne aide sur comment écrire des scripts batch que la fenêtre de l'Interpréteur de commandes qui seront exécutées.

Cependant, j'ai été incapable de trouver une bonne explication de la grammaire de scripts batch, comment les choses d'élargir ou de ne pas développer, et comment échapper des choses.

Voici des exemples de questions que je n'ai pas été en mesure de résoudre:

  • Quelle est la citation gérée par le système? J'ai fait un TinyPerl script
    ( foreach $i (@ARGV) { print '*' . $i ; } ), compilé et l'a appelé de cette façon :
    • my_script.exe "a ""b"" c" → sortie est *a "b*c
    • my_script.exe """a b c""" → la sortie en *"a*b*c"
  • Comment l'interne echo commande de travail? Ce qui est développé à l'intérieur de cette commande?
  • Pourquoi dois-je utiliser for [...] %%I dans le fichier des scripts, 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 puis-je écho %PROCESSOR_ARCHITECTURE% littéralement? J'ai trouvé que l' echo.exe %""PROCESSOR_ARCHITECTURE% fonctionne, est-il une meilleure solution?
  • Comment faire des 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 assurer une variable passe à une commande qu'un seul argument, si jamais cette variable contient des guillemets?
  • Comment sont les variables stockées lors de l'utilisation de l' set commande? Par exemple, si je n' set a=a" b puis echo.%a% - je obtenir a" b. Si j'utilise des echo.exe de la UnxUtils, je reçois a b. Comment se fait - %a% se développe d'une manière différente?

Merci pour vos lumières.

260voto

jeb Points 26035

J'ai fait un peu/beaucoup d'expériences, et ce qui semble être les principaux résultats.

Afin de mieux comprendre comment des lots de travaux, et pourquoi parfois échapper à des œuvres et à d'autres moments, il semble échouer. - Je faire ce travail par de nombreuses expériences, et j'essais de construction afin que je puisse identifier l'ordre des phases.

Il existe de multiples domaines à examiner. J'ai eu l'

  • BatchLineParser - Le à l'intérieur de l'analyseur de fichiers par lots, pour les lignes ou blocs
  • CmdLineParser - Comme le BatchLineParser, mais directement à l'invite de commandes, travaux de différents
  • LabelParser - Comment appeler/goto et les étiquettes de travail
  • CommandBlockCaching - Comment parenthèse et la mise en cache fonctionne
  • Générateur de jetons - Faire unique des jetons(groupes de personnages) construire et dans laquelle les phases

Le BatchLineParser:

Une ligne de code dans un fichier de commandes a plusieurs phases (sur la ligne de commande de l'expansion est différente!).

Le processus commence avec la phase 1

Phase/commande
1) Phase(En Pourcentage):

  • Un double - %% est remplacé par un simple %
  • L'Expansion de l'argument des variables (%1, %2, etc.)
  • L'Expansion de l' %var%, si var n'existe pas remplacer par rien
  • Pour une explication complète lisez ceci du dbenham Même thread: pourcentage de l'expansion

1.5) Supprimer tous <CR> (CarriageReturn 0x0d) de la ligne

2) Phase(caractères Spéciaux, " <LF> ^ & | < > ( ): Regarder chaque personnage

  • Si c'est une citation (") bascule la citation drapeau, si le devis drapeau est active, les caractères spéciaux suivants ne sont plus spécial: ^ & | < > ( ).
  • Si c'est un signe (^) le prochain personnage n'a pas de signification particulière, le signe lui-même est supprimée, si le signe est le dernier caractère de la ligne, la ligne suivante est ajoutée, le premier personnage de la ligne suivante est toujours traitée comme évadé personnage.
    • <LF> s'arrête le traitement immédiatement, mais pas avec un curseur en face
  • Si c'est l'un des caractères spéciaux & | < > séparer la ligne à ce point, dans le cas de la conduite (|) les deux parties obtient une phase de redémarrage (un peu plus complexe ...) Pour plus d'informations sur la façon dont les tuyaux sont analysées et traitées, regardez cette question et réponses: Pourquoi expansion retardée d'échouer lorsqu'à l'intérieur d'une canalisation d'eau bloc de code?
  • Dans cette phase, le jeton principal de la liste est en construction, les séparateurs de jetons sont <space> <tab> , ; = et <0xFF> (aussi connu comme l'espace insécable)
  • Processus de parenthèse (composé instructions sur plusieurs lignes):
    • Si l'analyseur n'est pas la recherche d'un jeton de commande, puis ( n'est pas spécial.
    • Si l'analyseur est à la recherche d'un jeton de commande et trouve (, puis de lancer un nouveau composé de déclaration et d'incrément de la parenthèse compteur
    • Si la parenthèse compteur est > 0 alors ) met fin à l'instruction composée et décrémente la parenthèse compteur.
    • Si l'extrémité de la ligne est atteint et que la parenthèse compteur est > 0 alors la ligne suivante sera annexé à l'instruction composée (commence de nouveau à la phase 1)
    • Si la parenthèse compteur = 0, et l'analyseur est à la recherche d'une commande, alors ) et tous les caractères restants sur la ligne sont ignorés
  • Dans cette phase REM, SI et sont détectés, pour la gestion spéciale d'entre eux.
  • Si le premier élément est "rem", seulement deux jetons sont traitées, important pour le multiligne signe

3) Phase(echo): Si "l'écho est sur" imprimer le résultat de la phase 1 et 2

  • Pour-boucle-blocs sont fait l'écho à plusieurs reprises, la première fois dans le contexte de la boucle for, avec non développés pour-boucle-vars
  • Pour chaque itération, le bloc est fait l'écho avec élargis à-boucle-vars

---- Ces deux phases ne sont pas vraiment la suite directe, mais il ne fait aucune différence
4) Phase(à-boucle-vars expansion): l'Expansion de l' %%a et ainsi de suite

5) Phase(point d'Exclamation): Seulement si l'expansion retardée est sur, regardez chaque personnage

  • Si c'est un signe (^) le prochain personnage n'a pas de signification particulière, le signe lui-même est supprimé
  • Si il est un point d'exclamation, recherche pour le prochain point d'exclamation (signes ne sont pas observées plus), développez le contenu de la variable
    • Consécutives de l'ouverture d' ! sont fusionnés en un seul !
    • Tout en restant ! qui ne peuvent pas être associé est supprimé
  • Si aucun point d'exclamation se trouve dans cette phase, le résultat est ignoré, le résultat de la phase 4 est utilisé à la place (importante pour les carets)
  • Important: Lors de cette phase de citations et d'autres spéciques caractères sont ignorés
  • L'expansion de vars, à ce stade, est "safe", parce que les caractères spéciaux ne sont pas détectés plus (même <CR> ou <LF>)

6) Phase(appel/caret doublement): Uniquement si le cmd jeton est de les APPELER

  • Si le premier élément est "call", de commencer avec la phase 1 de nouveau, mais s'arrête après la phase 2, l'expansion retardée ne sont pas traitées une deuxième fois ici
  • Supprimer le premier CALL, de sorte que plusieurs APPELS peuvent être empilés
  • Double tous les carets (normal carets semble rester inchangé, parce que, dans la phase 2, ils sont réduits à un seul, mais dans les citations qu'ils sont effectivly doublé)

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

  • Jetons différents sont utilisés ici, dépend de l'organisation interne de la commande exécutée
  • Dans le cas d'un set "name=content", le contenu complet de la première signe égal à la dernière citation de la ligne est utilisée en tant que contenu-jeton, si il n'y a pas de citation après le signe égal, le reste de la ligne est utilisée.

CmdLineParser:

Des œuvres comme la BatchLine-Parser, mais:

  • Goto/appel d'une étiquette n'est pas autorisé

Phase1(En Pourcentage):

  • %var% sera remplacé par le contenu de var, si la var n'est pas défini, l'expresssion sera inchangé
  • Pas de traitement particulier de % de % de, la seconde pour cent pourrait être le début d'une var, set var=contenu, %%var%% étend à l' %Contenu%

Phase5(point d'exclamation): uniquement si "DelayedExpansion" est activée

  • !var! sera remplacé par le contenu de var, si la var n'est pas défini, l'expresssion sera inchangé

pour la boucle de commande de bloc

par exemple, for /F "usebackq" %%a IN (commande bloc) DO echo %%a

Le bloc de commandes sera analysée en deux temps, d'abord le BatchLineParser(la boucle est à l'intérieur d'un lot) ou le CmdLineParser(boucle sur le cmd-line) est active, lors de la deuxième manche, toujours le CmdLineParser est active. Dans la deuxième manche, DelayedExpansion est active que si elle est activé avec la clé de registre

La deuxième manche est comme appeler la ligne avec cmd /c

Détermination des variables ne sont donc pas persistant.

J'espère que ça aide Jan Erik

65voto

Mike Clark Points 5966

Lors de l'appel d'une commande dans une fenêtre de commande, la segmentation des arguments de ligne de commande n'est pas fait par cmd.exe (un.k.un. "la coquille"). Le plus souvent la segmentation est effectuée par le nouvellement formé processus en C/C++ runtime, 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 de l'ignorer argv et de traiter les raw de ligne de commande pour lui-même (par exemple avec GetCommandLine()). Au niveau de l'OS, Windows passe les lignes de commande untokenized comme une seule chaîne de procédés nouveaux. C'est contrairement à la plupart des *nix coquilles, où le shell tokenizes arguments d'une manière cohérente, de manière prévisible avant de les transmettre à de nouveaux processus. Tout cela signifie que vous pouvez rencontrer sauvagement divergentes argument de segmentation en unités de comportement à travers différents programmes sur Windows, comme des programmes individuels prennent souvent l'argument de la segmentation dans leurs propres mains.

Si cela ressemble à de l'anarchie, il est. Cependant, depuis un grand nombre de programmes Windows ne utiliser Microsoft C/C++ runtime argv, il peut être utile de comprendre comment les MSVCRT tokenizes arguments. Voici un extrait:

  • Les Arguments sont séparés par un espace blanc, qui est un espace ou une tabulation.
  • Une chaîne de caractères entourée par des guillemets doubles est interprété comme un argument unique, indépendamment de l'espace blanc. Une chaîne de caractères entre guillemets peuvent être intégrées dans un argument. Notez que l'accent circonflexe (^) 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 littéral guillemet double (").
  • Les barres obliques inverses sont interprétés littéralement, à moins qu'ils précèdent immédiatement un guillemet double.
  • Si un même nombre de barres obliques inverses est suivie par un guillemet double, puis une barre oblique inverse () est placé dans le tableau argv pour chaque paire de barres obliques inverses (\), et le guillemet double (") est interprétée comme un délimiteur de chaîne.
  • Si un nombre impair de barres obliques inverses est suivie par un guillemet double, puis une barre oblique inverse () est placé 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 le solde de la barre oblique inverse, causant un littéral de guillemets doubles ( "" ) pour être placé dans argv.

Le Microsoft "lot de la langue" (.chauve-souris) n'est pas une exception à cet environnement anarchique, et il a développé ses propres règles pour la segmentation et de s'évader. Il ressemble également à cmd.exe s'invite de commande ne faire certains de prétraitement de l'argument de ligne de commande (surtout pour la substitution de variables et de s'évader) avant de passer l'argument de la nouvelle de l'exécution de processus. Vous pouvez lire plus sur les détails de bas niveau de la langue de lot et cmd s'échapper dans la qualité des réponses par jeb et dbenham sur cette page.


Nous allons créer un simple utilitaire de ligne de commande en C et voir 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;
}

(Notes: 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.)

Input : "a ""b"" c"
Output: argv[1][a "b" c]

Input : """a b c"""
Output: argv[1]["a b c"]

Input : "a"" b c
Output: argv[1][a" b c]

Et quelques-uns de mes propres tests:

Input : a "b" c
Output: argv[1][a]
        argv[2][b]
        argv[3][c]

Input : a "b c" "d e
Output: argv[1][a]
        argv[2][b c]
        argv[3][d e]

Input : a \"b\" c
Output: argv[1][a]
        argv[2]["b"]
        argv[3][c]

56voto

dbenham Points 46458

Voici une explication plus détaillée de traitement par Lots de la Phase 1 de de jeb répondre

1)(pour Cent) A partir de la gauche, l'analyse de chaque personnage pour %. Si trouvé alors

  • 1.1 (escape %)
    Si elle est suivie par un autre % alors
    Remplacer %% avec un seul % et de poursuivre l'analyse
  • 1.2 (extension de l'argument)
    • D'autre si elle est suivie d' * et les extensions de commandes sont activées, puis
      Remplacer %* avec le texte de tous les arguments de ligne de commande
    • D'autre si elle est suivie d' <digit> alors
      Remplacer %<digit> avec la valeur de l'argument (la remplacer par rien si undefined) et de poursuivre l'analyse
    • D'autre si elle est suivie d' ~ et les extensions de commandes sont activées, puis
      • Si elle est suivie par une option de la liste valide de l'argument des modificateurs suivie par <digit> alors
        Remplacer %~[modifiers]<digit> modification de valeur de l'argument (la remplacer par rien si pas définie ou si spécifié $PATH: modificateur n'est pas définie) et de poursuivre l'analyse.
        Remarque: les modificateurs sont insensibles à la casse et peuvent apparaître plusieurs fois dans n'importe quel ordre, à l'exception de $PATH: modificateur ne peut apparaître qu'une seule fois et doit être le dernier modificateur avant l' <digit>
      • D'autre invalide modifié argument de la syntaxe soulève erreur fatale: le traitement par lot abandonne!
  • 1.3 (développez variable)
    • Sinon si les extensions de commandes sont désactivés, puis
      Regardez à la prochaine chaîne de caractères, en rupture avant de % ou <LF>, et de les appeler VAR (peut-être une liste vide)
      • Si le caractère suivant est % alors
        Remplacer %VAR% de la valeur de la VAR (remplacer par rien si VAR n'est définie) et de poursuivre l'analyse
      • Else goto 1.4
    • Sinon si les extensions de commandes sont activées, puis
      Regardez à la prochaine chaîne de caractères, en rupture avant de % : ou <LF>, et de les appeler VAR (peut-être une liste vide). Si VAR pauses avant d' : et le caractère suivant est % alors incluent : comme le dernier caractère dans le VAR et pause avant d' %.
      • Si le caractère suivant est % alors
        Remplacer %VAR% de la valeur de la VAR (remplacer par rien si VAR n'est définie) et de poursuivre l'analyse
      • Sinon si le caractère suivant est :alors
        • Si VAR n'est pas défini alors
          Retirez %VAR: et de poursuivre l'analyse.
        • Sinon si le caractère suivant est ~ alors
          • Si la prochaine chaîne de caractères correspond au modèle de l' [integer][,[integer]]% alors
            Remplacer %VAR:~[integer][,[integer]]% avec sous-chaîne de valeur de la VAR (ce qui peut résulter en une chaîne vide) et de poursuivre l'analyse.
          • Else goto 1.4
        • D'autre si elle est suivie d' = ou *= alors
          Non valide variable de recherche et de remplacement de la syntaxe soulève erreur fatale: le traitement par lot abandonne!
        • Sinon, si à côté de la chaîne de caractères correspond au modèle de l' [*]search=[replace]% alors
          Remplacer %VAR:[*]search=[replace]% de la valeur de VAR après l'exécution de la recherche et de remplacement (ce qui peut résulter en une chaîne vide) et de poursuivre l'analyse
        • Else goto 1.4
  • 1.4 (strip %)
    D'autre retirez % et continuer avec la numérisation

Le ci-dessus permet d'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

Note 1 - la Phase 1 se produit avant la reconnaissance de REM consolidés. Ceci est très important car cela signifie que même une remarque peut générer une erreur fatale si elle a invalid argument de l'expansion ou la syntaxe non valide variable de recherche et de remplacement de la syntaxe!

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

Note 2 - un Autre fait intéressant conséquence de l' % règles d'analyse: Variables contenant : le nom peut être défini, mais ils ne peuvent être étendus, sauf si les extensions de commandes sont désactivées. Il y a une exception - un nom de variable contenant un seul colon à la fin peut être agrandi, tandis que les extensions de commandes sont activées. Cependant, vous ne pouvez pas effectuer de sous-chaîne ou de rechercher et remplacer des opérations sur les variables des noms se terminant par un point-virgule. Le fichier de commandes ci-dessous (avec l'aimable autorisation 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 intéressant résultat de l'ordre de l'analyse de règles que jeb dispose dans son post: Lors de l'exécution de la recherche et de la remplacer avec la normale de l'expansion, les caractères spéciaux ne devraient PAS être échappés (si elles peuvent être citées). Mais lors de l'exécution de la recherche et de la remplacer par une expansion retardée, les caractères spéciaux DOIVENT être échappés (sauf si elles sont citées).

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

8voto

bobbogo Points 4201

Comme l'a souligné, les commandes sont passées à l'ensemble de la chaîne de l'argument de µSoft terre, et c'est à eux d'analyser cette séparer les arguments pour leur propre usage. Il n'y a pas de consistencty dans cette entre les différents programmes, et il n'est donc pas un ensemble de règles pour décrire ce processus. Vous avez vraiment besoin de vérifier à chaque coin de cas pour ce que C de la bibliothèque de votre programme.

Autant que le système .bat fichiers, ici, c'est que 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. Voir si vous pouvez deviner ce que µSoft essayons de faire:

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

Amende jusqu'à présent. (Je laisse le inintéressant %cmdcmdline% et %0 à partir de maintenant.)

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

Pas de nom de fichier d'extension.

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

Pas de citation de 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]

Consécutives des guillemets leur fait perdre toute spéciale de l'analyse des capacités qu'ils ont. @Beniot de l'exemple:

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

Quiz: Comment faites-vous passer la valeur de n'importe quel environnement var comme un seul argument (c'est à dire, comme %1) pour 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 analyse semble irrémédiablement brisé.

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

1voto

JBRWilkinson Points 3155

Rob van der Woude a sur son site une formidable référence en matière de scripts Batch et de commandes Windows .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X