Wie analysiert der Windows Command Interpreter (CMD.EXE) Skripte?


142

Ich bin auf ss64.com gestoßen, das eine gute Hilfe zum Schreiben von Batch-Skripten bietet, die der Windows- Befehlsinterpreter ausführen wird.

Ich konnte jedoch keine gute Erklärung für die Grammatik finden von Batch-Skripten finden, wie sich Dinge erweitern oder nicht erweitern und wie man Dingen entgeht.

Hier sind Beispielfragen, die ich nicht lösen konnte:

  • Wie wird das Angebotssystem verwaltet? Ich habe ein TinyPerl- Skript
    ( foreach $i (@ARGV) { print '*' . $i ; }) erstellt, es kompiliert und folgendermaßen aufgerufen:
    • my_script.exe "a ""b"" c" → Ausgabe ist *a "b*c
    • my_script.exe """a b c""" → ausgeben *"a*b*c"
  • Wie funktioniert das interne echo Befehl? Was wird in diesem Befehl erweitert?
  • Warum muss ich for [...] %%Iin Dateiskripten aber verwendenfor [...] %I in interaktiven Sitzungen verwenden?
  • Was sind die Fluchtzeichen und in welchem ​​Kontext? Wie entkomme ich einem Prozentzeichen? Wie kann ich zum Beispiel %PROCESSOR_ARCHITECTURE%buchstäblich widerhallen ? ich habe das gefundenecho.exe %""PROCESSOR_ARCHITECTURE% funktioniert, gibt es eine bessere Lösung?
  • Wie %passen Paare zusammen? Beispiel:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • Wie stelle ich sicher, dass eine Variable als einzelnes Argument an einen Befehl übergeben wird, wenn diese Variable jemals doppelte Anführungszeichen enthält?
  • Wie werden Variablen bei Verwendung des setBefehls gespeichert ? Zum Beispiel, wenn ich es tue set a=a" bund dann echo.%a%erhalte ich a" b. Wenn ich jedoch echo.exevon den UnxUtils benutze, bekomme ich a b. Wie kommt es, dass %a%sich das anders ausdehnt?

Danke für deine Lichter.


Antworten:


200

Wir haben Experimente durchgeführt, um die Grammatik von Batch-Skripten zu untersuchen. Wir haben auch Unterschiede zwischen Batch- und Befehlszeilenmodus untersucht.

Batch Line Parser:

Hier ist eine kurze Übersicht über die Phasen im Batch-File-Line-Parser:

Phase 0) Zeile lesen:

Phase 1) Prozentuale Expansion:

Phase 2) Verarbeiten Sie Sonderzeichen, tokenisieren Sie und erstellen Sie einen zwischengespeicherten Befehlsblock: Dies ist ein komplexer Prozess, der von Anführungszeichen, Sonderzeichen, Token-Trennzeichen und Caret-Escapezeichen beeinflusst wird.

Phase 3) Echo der analysierten Befehle Nur dann, wenn der Befehlsblock nicht mit begonnen @hat und ECHO zu Beginn des vorherigen Schritts eingeschaltet war.

Phase 4) FOR- %XVariablenerweiterung: Nur wenn ein FOR-Befehl aktiv ist und die Befehle nach DO verarbeitet werden.

Phase 5) Verzögerte Erweiterung: Nur wenn die verzögerte Erweiterung aktiviert ist

Phase 5.3) Rohrbearbeitung: Nur wenn sich Befehle auf beiden Seiten einer Rohrleitung befinden

Phase 5.5) Umleitung ausführen:

Phase 6) CALL-Verarbeitung / Caret-Verdopplung: Nur wenn das Befehlstoken CALL ist

Phase 7) Ausführen: Der Befehl wird ausgeführt


Hier sind Details für jede Phase:

Beachten Sie, dass die unten beschriebenen Phasen nur ein Modell für die Funktionsweise des Batch-Parsers sind. Die tatsächlichen cmd.exe-Interna spiegeln diese Phasen möglicherweise nicht wider. Dieses Modell ist jedoch effektiv bei der Vorhersage des Verhaltens von Batch-Skripten.

Phase 0) Zeile lesen: Lesen Sie zuerst die Eingabezeile durch <LF>.

  • Beim Lesen einer Zeile, die als Befehl analysiert werden soll, wird <Ctrl-Z>(0x1A) als gelesen<LF> (LineFeed 0x0A)
  • Wenn GOTO oder CALL beim Lesen nach einem: -Label Zeilen lesen <Ctrl-Z>, wird es als sich selbst behandelt - es wird nicht in konvertiert<LF>

Phase 1) Prozentuale Expansion:

  • Ein Doppel %%wird durch ein Einzel ersetzt%
  • Erweiterung der Argumente ( %*, %1, %2, etc.)
  • Erweiterung von %var% , wenn var nicht existiert, ersetzen Sie es durch nichts
  • Die Linie wird zunächst <LF>nicht innerhalb der %var%Erweiterung abgeschnitten
  • Für eine vollständige Erklärung lesen Sie die erste Hälfte von dbenham. Gleicher Thread: Prozentphase

Phase 2) Verarbeiten von Sonderzeichen, Tokenisieren und Erstellen eines zwischengespeicherten Befehlsblocks: Dies ist ein komplexer Prozess, der von Anführungszeichen, Sonderzeichen, Token-Trennzeichen und Caret-Escapezeichen beeinflusst wird. Was folgt, ist eine Annäherung an diesen Prozess.

Es gibt Konzepte, die in dieser Phase wichtig sind.

  • Ein Token ist einfach eine Zeichenfolge, die als Einheit behandelt wird.
  • Token werden durch Token-Trennzeichen getrennt. Die Standard-Token-Begrenzer sind <space> <tab> ; , = <0x0B> <0x0C>und <0xFF>
    aufeinanderfolgende Token-Begrenzer werden als eins behandelt. Zwischen den Token-Begrenzern befinden sich keine leeren Token
  • Innerhalb einer Zeichenfolge in Anführungszeichen gibt es keine Token-Trennzeichen. Die gesamte Zeichenfolge in Anführungszeichen wird immer als Teil eines einzelnen Tokens behandelt. Ein einzelnes Token kann aus einer Kombination von Zeichenfolgen in Anführungszeichen und Zeichen ohne Anführungszeichen bestehen.

Die folgenden Zeichen können in dieser Phase je nach Kontext eine besondere Bedeutung haben: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

Schauen Sie sich jedes Zeichen von links nach rechts an:

  • Wenn <CR>dann entfernen Sie es, als ob es nie da wäre (außer für seltsames Umleitungsverhalten )
  • Bei einem Caret ( ^) wird das nächste Zeichen maskiert und das entkommende Caret entfernt. Entkommene Zeichen verlieren jede besondere Bedeutung (außer <LF>).
  • Wenn ein Zitat ( "), schalten Sie das Anführungszeichen um. Wenn das Anführungszeichen aktiv ist, dann nur "und <LF>sind speziell. Alle anderen Zeichen verlieren ihre spezielle Bedeutung, bis das nächste Anführungszeichen die Anführungszeichenflagge ausschaltet. Es ist nicht möglich, sich dem Schlusszitat zu entziehen. Alle in Anführungszeichen gesetzten Zeichen befinden sich immer im selben Token.
  • <LF>schaltet immer die Anführungszeichenflagge aus. Andere Verhaltensweisen variieren je nach Kontext, aber Anführungszeichen ändern niemals das Verhalten von <LF>.
    • Entkam <LF>
      • <LF> wird abgestreift
      • Das nächste Zeichen wird maskiert. Wenn am Ende des Zeilenpuffers, wird die nächste Zeile von den Phasen 1 und 1.5 gelesen und verarbeitet und an die aktuelle angehängt, bevor das nächste Zeichen ausgeblendet wird. Wenn das nächste Zeichen ist <LF>, wird es als Literal behandelt, was bedeutet, dass dieser Prozess nicht rekursiv ist.
    • <LF>Nicht entgangen, nicht in Klammern
      • <LF> wird entfernt und das Parsen der aktuellen Zeile wird beendet.
      • Alle verbleibenden Zeichen im Zeilenpuffer werden einfach ignoriert.
    • In <LF>einem FOR IN-Klammerblock nicht entkommen
      • <LF> wird in a umgewandelt <space>
      • Wenn am Ende des Zeilenpuffers, wird die nächste Zeile gelesen und an die aktuelle angehängt.
    • In <LF>einem in Klammern gesetzten Befehlsblock nicht entkommen
      • <LF>wird in konvertiert <LF><space>und das <space>wird als Teil der nächsten Zeile des Befehlsblocks behandelt.
      • Wenn am Ende des Zeilenpuffers, wird die nächste Zeile gelesen und an das Leerzeichen angehängt.
  • Wenn eines der Sonderzeichen & | <oder >, teilen Sie die Zeile an dieser Stelle, um Pipes, Befehlsverkettung und Umleitung zu verarbeiten.
    • Im Falle eines Rohres (| ) ist jede Seite ein separater Befehl (oder Befehlsblock), der in Phase 5.3 eine spezielle Behandlung erhält
    • Im Fall von &, &&oder ||Befehlsverkettung, wobei jede Seite der Verkettung wird als separater Befehl behandelt.
    • Im Fall von <, <<, >, oder >>Umleitung wird die Umleitungs Klausel geparst, vorübergehend entfernt und dann bis zum Ende des aktuellen Befehls angehängt. Eine Umleitungsklausel besteht aus einer optionalen Dateihandle-Ziffer, dem Umleitungsoperator und dem Umleitungsziel-Token.
      • Wenn das Token vor dem Umleitungsoperator eine einzelne nicht entkoppelte Ziffer ist, gibt die Ziffer das umzuleitende Dateihandle an. Wenn das Handle-Token nicht gefunden wird, wird standardmäßig 1 (stdout) und standardmäßig 0 (stdin) ausgegeben.
  • Wenn das allererste Token für diesen Befehl (vor dem Verschieben der Umleitung zum Ende) mit beginnt @, hat das @eine besondere Bedeutung. ( @ist in keinem anderen Kontext etwas Besonderes)
    • Das Special @wird entfernt.
    • Wenn ECHO eingeschaltet ist, werden dieser Befehl zusammen mit den folgenden verketteten Befehlen in dieser Zeile vom Phase-3-Echo ausgeschlossen. Befindet sich das @vor einer Öffnung (, wird der gesamte Block in Klammern vom Phase-3-Echo ausgeschlossen.
  • Prozessklammer (ermöglicht zusammengesetzte Anweisungen über mehrere Zeilen hinweg):
    • Wenn der Parser nicht nach einem Befehlstoken sucht, (ist dies nichts Besonderes.
    • Wenn der Parser nach einem Befehlstoken sucht und dieses findet (, starten Sie eine neue zusammengesetzte Anweisung und erhöhen Sie den Klammerzähler
    • Wenn der Klammerzähler> 0 ist ), wird die zusammengesetzte Anweisung beendet und der Klammerzähler dekrementiert.
    • Wenn das Zeilenende erreicht ist und der Klammerzähler> 0 ist, wird die nächste Zeile an die zusammengesetzte Anweisung angehängt (beginnt erneut mit Phase 0).
    • Wenn der Klammerzähler 0 ist und der Parser nach einem Befehl sucht, )funktioniert er ähnlich wie eine REMAnweisung, solange unmittelbar darauf ein Token-Trennzeichen, ein Sonderzeichen, eine neue Zeile oder ein Dateiende folgt
      • Alle Sonderzeichen verlieren ihre Bedeutung außer ^(Zeilenverkettung ist möglich)
      • Sobald das Ende der logischen Zeile erreicht ist, wird der gesamte "Befehl" verworfen.
  • Jeder Befehl wird in eine Reihe von Token analysiert. Das erste Token wird immer als Befehlstoken behandelt (nachdem das Special entfernt @und die Umleitung an das Ende verschoben wurde).
    • Führende Token-Trennzeichen vor dem Befehlstoken werden entfernt
    • Funktioniert beim Parsen des Befehlstokens (zusätzlich zu den Standard-Token-Trennzeichen als Befehlstoken-Begrenzer
    • Die Behandlung nachfolgender Token hängt vom Befehl ab.
  • Die meisten Befehle verketten einfach alle Argumente nach dem Befehlstoken zu einem einzigen Argumenttoken. Alle Trennzeichen für Argumenttoken bleiben erhalten. Argumentoptionen werden normalerweise erst in Phase 7 analysiert.
  • Drei Befehle werden speziell behandelt - IF, FOR und REM
    • IF ist in zwei oder drei verschiedene Teile aufgeteilt, die unabhängig voneinander verarbeitet werden. Ein Syntaxfehler in der IF-Konstruktion führt zu einem schwerwiegenden Syntaxfehler.
      • Die Vergleichsoperation ist der eigentliche Befehl, der bis zur Phase 7 durchläuft
        • Alle IF-Optionen werden in Phase 2 vollständig analysiert.
        • Aufeinanderfolgende Token-Begrenzer werden in einem einzigen Leerzeichen zusammengefasst.
        • Abhängig vom Vergleichsoperator werden ein oder zwei Wertmarken identifiziert.
      • Der True-Befehlsblock ist der Befehlssatz nach der Bedingung und wird wie jeder andere Befehlsblock analysiert. Wenn ELSE verwendet werden soll, muss der True-Block in Klammern gesetzt werden.
      • Der optionale Befehlsblock False ist der Befehlssatz nach ELSE. Auch dieser Befehlsblock wird normal analysiert.
      • Die Befehlsblöcke True und False fließen nicht automatisch in die nachfolgenden Phasen. Ihre nachfolgende Verarbeitung wird durch Phase 7 gesteuert.
    • FOR wird nach dem DO in zwei Teile geteilt. Ein Syntaxfehler in der FOR-Konstruktion führt zu einem schwerwiegenden Syntaxfehler.
      • Der Teil durch DO ist der eigentliche FOR-Iterationsbefehl, der den gesamten Weg durch Phase 7 durchläuft
        • Alle FOR-Optionen werden in Phase 2 vollständig analysiert.
        • Die IN-Klausel in Klammern behandelt <LF>als <space>. Nachdem die IN-Klausel analysiert wurde, werden alle Token zu einem einzigen Token zusammengefasst.
        • Aufeinanderfolgende nicht begrenzte / nicht zitierte Token-Trennzeichen werden während des gesamten FOR-Befehls über DO in einem einzigen Leerzeichen zusammengefasst.
      • Der Teil nach DO ist ein Befehlsblock, der normal analysiert wird. Die nachfolgende Verarbeitung des DO-Befehlsblocks wird durch die Iteration in Phase 7 gesteuert.
    • In Phase 2 erkanntes REM wird dramatisch anders behandelt als alle anderen Befehle.
      • Es wird nur ein Argument-Token analysiert - der Parser ignoriert Zeichen nach dem ersten Argument-Token.
      • Der REM-Befehl wird möglicherweise in der Ausgabe der Phase 3 angezeigt, der Befehl wird jedoch nie ausgeführt, und der ursprüngliche Argumenttext wird wiederholt. Escape-Carets werden nicht entfernt, außer ...
        • Wenn es nur ein Argument-Token gibt, das mit einem Leerzeichen ^endet, das die Zeile beendet, wird das Argument-Token weggeworfen und die nachfolgende Zeile wird analysiert und an das REM angehängt. Dies wird wiederholt, bis mehr als ein Token vorhanden ist oder das letzte Zeichen nicht mehr vorhanden ist ^.
  • Wenn das Befehlstoken mit beginnt :und dies die erste Runde von Phase 2 ist (kein Neustart aufgrund von CALL in Phase 6), dann
    • Das Token wird normalerweise als nicht ausgeführtes Label behandelt .
      • Der Rest der Zeile wird analysiert, aber ), <, >, &und |nicht mehr hat eine besondere Bedeutung. Der gesamte Rest der Zeile wird als Teil der Bezeichnung "Befehl" betrachtet.
      • Das ist ^weiterhin etwas Besonderes, dh die Zeilenfortsetzung kann verwendet werden, um die nachfolgende Zeile an das Etikett anzuhängen.
      • Ein nicht ausgeführtes Label in einem Block in Klammern führt zu einem schwerwiegenden Syntaxfehler, es sei denn, in der nächsten Zeile folgt sofort ein Befehl oder ein ausgeführtes Label .
        • (hat keine besondere Bedeutung mehr für den ersten Befehl, der auf das nicht ausgeführte Label folgt .
      • Der Befehl wird abgebrochen, nachdem die Etikettenanalyse abgeschlossen ist. Nachfolgende Phasen finden für das Etikett nicht statt
    • Es gibt drei Ausnahmen, die dazu führen können, dass ein in Phase 2 gefundenes Label als ausgeführtes Label behandelt wird , das die Analyse in Phase 7 fortsetzt.
      • Es ist die Umleitung , dass das Etikett vorangeht Token und es gibt ein |Rohr oder &, &&oder ||Befehlsverkettung auf der Linie.
      • Vor dem Label-Token steht eine Umleitung, und der Befehl befindet sich in einem Block in Klammern.
      • Das Label-Token ist der allererste Befehl in einer Zeile innerhalb eines Blocks in Klammern, und die obige Zeile endete mit einem nicht ausgeführten Label .
    • Folgendes tritt auf, wenn in Phase 2 ein ausgeführtes Label entdeckt wird
      • Das Label, seine Argumente und seine Umleitung sind in Phase 3 von jeder Echoausgabe ausgeschlossen
      • Alle nachfolgenden verketteten Befehle in der Zeile werden vollständig analysiert und ausgeführt.
    • Weitere Informationen zu Ausgeführt Labels vs. Nicht ausgeführte Labels finden https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Phase 3) Echo der analysierten Befehle Nur dann, wenn der Befehlsblock nicht mit begonnen @hat und ECHO zu Beginn des vorherigen Schritts eingeschaltet war.

Phase 4) FOR- %XVariablenerweiterung: Nur wenn ein FOR-Befehl aktiv ist und die Befehle nach DO verarbeitet werden.

  • Zu diesem Zeitpunkt hat Phase 1 der Stapelverarbeitung bereits eine FOR-Variable wie %%Xin konvertiert %X. Die Befehlszeile hat unterschiedliche prozentuale Erweiterungsregeln für Phase 1. Dies ist der Grund, warum Befehlszeilen %XBatch-Dateien %%Xfür FOR-Variablen verwenden.
  • FOR-Variablennamen unterscheiden zwischen Groß- und Kleinschreibung, jedoch ~modifiersnicht zwischen Groß- und Kleinschreibung.
  • ~modifiersVorrang vor Variablennamen haben. Wenn ein nachfolgendes Zeichen ~sowohl ein Modifikator als auch ein gültiger FOR-Variablenname ist und ein nachfolgendes Zeichen vorhanden ist, das ein aktiver FOR-Variablenname ist, wird das Zeichen als Modifikator interpretiert.
  • FOR-Variablennamen sind global, jedoch nur im Kontext einer DO-Klausel. Wenn eine Routine aus einer FOR DO-Klausel heraus aufgerufen wird, werden die FOR-Variablen innerhalb der CALLed-Routine nicht erweitert. Wenn die Routine jedoch über einen eigenen FOR-Befehl verfügt, sind alle derzeit definierten FOR-Variablen für die inneren DO-Befehle zugänglich.
  • FOR-Variablennamen können in verschachtelten FORs wiederverwendet werden. Der innere FOR-Wert hat Vorrang, aber sobald INNER FOR geschlossen wird, wird der äußere FOR-Wert wiederhergestellt.
  • Wenn ECHO zu Beginn dieser Phase eingeschaltet war, wird Phase 3) wiederholt, um die analysierten DO-Befehle anzuzeigen, nachdem die FOR-Variablen erweitert wurden.

---- Ab diesem Zeitpunkt wird jeder in Phase 2 identifizierte Befehl separat verarbeitet.
---- Die Phasen 5 bis 7 werden für einen Befehl abgeschlossen, bevor mit dem nächsten fortgefahren wird.

Phase 5) Verzögerte Erweiterung: Nur wenn die verzögerte Erweiterung aktiviert ist, befindet sich der Befehl nicht in einem Klammerblock auf beiden Seiten einer Pipe , und der Befehl ist kein "nacktes" Batch-Skript (Skriptname ohne Klammern, CALL, Befehlsverkettung, oder Rohr).

  • Jedes Token für einen Befehl wird unabhängig für eine verzögerte Erweiterung analysiert.
    • Die meisten Befehle analysieren zwei oder mehr Token - das Befehlstoken, das Argumenttoken und jedes Umleitungszieltoken.
    • Der FOR-Befehl analysiert nur das IN-Klauseltoken.
    • Der IF-Befehl analysiert nur die Vergleichswerte - je nach Vergleichsoperator entweder einen oder zwei.
  • Überprüfen Sie für jedes analysierte Token zunächst, ob es eines enthält !. Wenn nicht, wird das Token nicht analysiert - wichtig für ^Zeichen. Wenn das Token enthält !, scannen Sie jedes Zeichen von links nach rechts:
    • Wenn es sich um ein Caret ( ^) handelt, hat das nächste Zeichen keine besondere Bedeutung, das Caret selbst wird entfernt
    • Wenn es sich um ein Ausrufezeichen handelt, suchen Sie nach dem nächsten Ausrufezeichen (Carets werden nicht mehr beobachtet) und erweitern Sie es auf den Wert der Variablen.
      • Aufeinanderfolgende Öffnungen !werden zu einer einzigen zusammengefasst!
      • Verbleibende ungepaarte !werden entfernt
    • Das Erweitern von vars in dieser Phase ist "sicher", da Sonderzeichen nicht mehr erkannt werden (gerade <CR>oder <LF>)
    • Für eine vollständigere Erklärung lesen Sie die 2. Hälfte davon aus dbenham gleichen Thread - Ausrufezeichen Phase

Phase 5.3) Rohrverarbeitung: Nur wenn sich Befehle auf beiden Seiten eines Rohrs befinden
Jede Seite des Rohrs wird unabhängig und asynchron verarbeitet.

  • Wenn der Befehl in cmd.exe intern ist oder es sich um eine Batchdatei handelt oder wenn es sich um einen Befehlsblock in Klammern handelt, wird er in einem neuen cmd.exe-Thread über ausgeführt %comspec% /S /D /c" commandBlock", sodass der Befehlsblock einen Phasenneustart erhält, diesmal jedoch im Befehlszeilenmodus.
    • Wenn ein Befehlsblock in Klammern steht, werden alle <LF>mit einem Befehl davor und danach in konvertiert <space>&. Andere <LF>werden abgestreift.
  • Dies ist das Ende der Verarbeitung für die Pipe-Befehle.
  • Siehe Warum schlägt die verzögerte Erweiterung fehl, wenn sie sich in einem Pipeline-Codeblock befindet? Weitere Informationen zum Parsen und Verarbeiten von Rohren

Phase 5.5) Umleitung ausführen: Jede in Phase 2 erkannte Umleitung wird jetzt ausgeführt.

Phase 6) CALL-Verarbeitung / Caret-Verdopplung: Nur wenn das Befehlstoken CALL ist oder wenn der Text vor dem ersten vorkommenden Standard-Token-Trennzeichen CALL ist. Wenn CALL von einem größeren Befehlstoken analysiert wird, wird der nicht verwendete Teil dem Argumenttoken vorangestellt, bevor Sie fortfahren.

  • Scannen Sie das Argument-Token nach einem nicht zitierten /?. Wenn sich irgendwo innerhalb der Token etwas befindet, brechen Sie Phase 6 ab und fahren Sie mit Phase 7 fort, in der die HILFE für ANRUF gedruckt wird.
  • Entfernen Sie den ersten CALL, damit mehrere CALLs gestapelt werden können
  • Verdoppeln Sie alle Carets
  • Starten Sie die Phasen 1, 1.5 und 2 neu, fahren Sie jedoch nicht mit Phase 3 fort
    • Doppelte Carets werden auf ein Caret reduziert, solange sie nicht zitiert werden. Leider bleiben die zitierten Carets verdoppelt.
    • Phase 1 ändert sich etwas
      • Erweiterungsfehler in Schritt 1.2 oder 1.3 brechen den CALL ab, aber der Fehler ist nicht schwerwiegend - die Stapelverarbeitung wird fortgesetzt.
    • Phase-2-Aufgaben werden etwas geändert
      • Jede neu auftretende nicht zitierte, nicht entführte Umleitung, die in der ersten Runde von Phase 2 nicht erkannt wurde, wird erkannt, aber entfernt (einschließlich des Dateinamens), ohne die Umleitung tatsächlich durchzuführen
      • Jedes neu erscheinende nicht zitierte, nicht entflohene Caret am Ende der Zeile wird entfernt, ohne dass eine Fortsetzung der Zeile durchgeführt wird
      • Der CALL wird ohne Fehler abgebrochen, wenn einer der folgenden Fehler erkannt wird
        • Neu erscheinend ohne Zitat, ohne Flucht &oder|
        • Das resultierende Befehlstoken beginnt mit nicht zitiert, nicht entführt (
        • Der allererste Token nach dem entfernten CALL begann mit @
      • Wenn der resultierende Befehl ein scheinbar gültiges IF oder FOR ist, schlägt die Ausführung anschließend mit einem Fehler fehl, der angibt, dass er als interner oder externer Befehl erkannt wird IFoder FORnicht.
      • Natürlich wird der CALL in dieser zweiten Runde von Phase 2 nicht abgebrochen, wenn das resultierende Befehlstoken eine Bezeichnung ist, die mit beginnt :.
  • Wenn das resultierende Befehlstoken CALL ist, starten Sie Phase 6 neu (wiederholt sich, bis kein CALL mehr erfolgt).
  • Wenn das resultierende Befehlstoken ein Batch-Skript oder ein: -Label ist, wird die Ausführung des CALL im Rest von Phase 6 vollständig ausgeführt.
    • Verschieben Sie die aktuelle Position der Batch-Skriptdatei auf den Aufrufstapel, damit die Ausführung an der richtigen Position fortgesetzt werden kann, wenn der Aufruf abgeschlossen ist.
    • Richten Sie die Argumenttoken% 0,% 1,% 2, ...% N und% * für den CALL unter Verwendung aller resultierenden Token ein
    • Wenn das Befehlstoken eine Bezeichnung ist, die mit beginnt :, dann
      • Starten Sie Phase 5 neu. Dies kann sich auf Folgendes auswirken: Label wird aufgerufen. Da die Token% 0 usw. bereits eingerichtet wurden, werden die Argumente, die an die Routine CALLed übergeben werden, nicht geändert.
      • Führen Sie das GOTO-Label aus, um den Dateizeiger am Anfang des Unterprogramms zu positionieren (ignorieren Sie alle anderen Token, die möglicherweise dem: label folgen). In Phase 7 finden Sie Regeln zur Funktionsweise von GOTO.
        • Wenn das Token: label fehlt oder das Label: nicht gefunden wird, wird der Aufrufstapel sofort geöffnet, um die gespeicherte Dateiposition wiederherzustellen, und der CALL wird abgebrochen.
        • Wenn das: -Label /? Enthält, wird die GOTO-Hilfe gedruckt, anstatt nach dem: -Label zu suchen. Der Dateizeiger bewegt sich nicht, sodass der Code nach dem CALL zweimal ausgeführt wird, einmal im CALL-Kontext und dann erneut nach der CALL-Rückkehr. Siehe Warum CALL die GOTO-Hilfemeldung in diesem Skript druckt. Und warum werden Befehle danach zweimal ausgeführt? Für mehr Information.
    • Andernfalls wird die Steuerung an das angegebene Batch-Skript übertragen.
    • Die Ausführung des CALLed: -Labels oder -Skripts wird fortgesetzt, bis entweder EXIT / B oder das Dateiende erreicht ist. An diesem Punkt wird der CALL-Stapel gelöscht und die Ausführung an der gespeicherten Dateiposition fortgesetzt.
      Phase 7 wird nicht für aufgerufene Skripte oder: Labels ausgeführt.
  • Andernfalls fällt das Ergebnis von Phase 6 zur Ausführung in Phase 7 durch.

Phase 7) Ausführen: Der Befehl wird ausgeführt

  • 7.1 - Internen Befehl ausführen - Wenn das Befehlstoken in Anführungszeichen gesetzt ist, überspringen Sie diesen Schritt. Versuchen Sie andernfalls, einen internen Befehl zu analysieren und auszuführen.
    • Die folgenden Tests werden durchgeführt, um festzustellen, ob ein nicht zitiertes Befehlstoken einen internen Befehl darstellt:
      • Wenn das Befehlstoken genau mit einem internen Befehl übereinstimmt, führen Sie ihn aus.
      • Andernfalls brechen Sie das Befehlstoken vor dem ersten Auftreten von + / [ ] <space> <tab> , ;oder. =
        Wenn der vorhergehende Text ein interner Befehl ist, merken Sie sich diesen Befehl
        • Wenn Sie sich im Befehlszeilenmodus befinden oder wenn der Befehl aus einem Block in Klammern stammt, wenn der Befehlsblock true oder false, der Befehlsblock FOR DO oder an der Befehlsverkettung beteiligt ist, führen Sie den internen Befehl aus
        • Andernfalls (muss im Batch-Modus ein eigenständiger Befehl sein) durchsuchen Sie den aktuellen Ordner und den PATH nach einer .COM-, .EXE-, .BAT- oder .CMD-Datei, deren Basisname mit dem ursprünglichen Befehlstoken übereinstimmt
          • Wenn die erste übereinstimmende Datei eine .BAT- oder .CMD-Datei ist, gehen Sie zu 7.3.exec und führen Sie das Skript aus
          • Andernfalls (Übereinstimmung nicht gefunden oder erste Übereinstimmung ist .EXE oder .COM) führen Sie den gespeicherten internen Befehl aus
      • Andernfalls brechen Sie das Befehlstoken vor dem ersten Auftreten von . \oder. :
        Wenn der vorhergehende Text kein interner Befehl ist, gehen Sie zu 7.2.
        Andernfalls kann der vorhergehende Text ein interner Befehl sein. Denken Sie an diesen Befehl.
      • Unterbrechen Sie das Befehlstoken vor dem ersten Auftreten von + / [ ] <space> <tab> , ;oder. =
        Wenn der vorhergehende Text ein Pfad zu einer vorhandenen Datei ist, gehen Sie zu 7.2.
        Andernfalls führen Sie den gespeicherten internen Befehl aus.
    • Wenn ein interner Befehl von einem größeren Befehlstoken analysiert wird, wird der nicht verwendete Teil des Befehlstokens in die Argumentliste aufgenommen
    • Nur weil ein Befehlstoken als interner Befehl analysiert wird, bedeutet dies nicht, dass er erfolgreich ausgeführt wird. Jeder interne Befehl hat seine eigenen Regeln, wie die Argumente und Optionen analysiert werden und welche Syntax zulässig ist.
    • Alle internen Befehle drucken die Hilfe, anstatt ihre Funktion auszuführen, wenn sie /?erkannt werden. Die meisten erkennen, /?ob es irgendwo in den Argumenten erscheint. Einige Befehle wie ECHO und SET drucken jedoch nur dann Hilfe, wenn das erste Argument-Token mit beginnt /?.
    • SET hat einige interessante Semantik:
      • Wenn ein SET-Befehl ein Anführungszeichen enthält, bevor der Variablenname und die Erweiterungen aktiviert sind
        set "name=content" ignored -> value =, content
        wird der Text zwischen dem ersten Gleichheitszeichen und dem letzten Anführungszeichen als Inhalt verwendet (erstes Gleichheitszeichen und letztes Anführungszeichen ausgeschlossen). Text nach dem letzten Anführungszeichen wird ignoriert. Wenn nach dem Gleichheitszeichen kein Anführungszeichen steht, wird der Rest der Zeile als Inhalt verwendet.
      • Wenn ein SET-Befehl kein Anführungszeichen vor dem Namen
        set name="content" not ignored -> value = enthält, "content" not ignored
        wird der gesamte Rest der Zeile nach dem Gleichwert als Inhalt verwendet, einschließlich aller möglicherweise vorhandenen Anführungszeichen.
    • Ein IF-Vergleich wird ausgewertet, und abhängig davon, ob die Bedingung wahr oder falsch ist, wird der entsprechende bereits analysierte abhängige Befehlsblock ab Phase 5 verarbeitet.
    • Die IN-Klausel eines FOR-Befehls wird entsprechend iteriert.
      • Wenn dies ein FOR / F ist, das die Ausgabe eines Befehlsblocks wiederholt, dann:
        • Die IN-Klausel wird in einem neuen cmd.exe-Prozess über CMD / C ausgeführt.
        • Der Befehlsblock muss den gesamten Analyseprozess ein zweites Mal durchlaufen, diesmal jedoch in einem Befehlszeilenkontext
        • ECHO startet EIN und die verzögerte Erweiterung wird normalerweise deaktiviert gestartet (abhängig von der Registrierungseinstellung).
        • Alle vom Befehlsblock der IN-Klausel vorgenommenen Umgebungsänderungen gehen verloren, sobald der untergeordnete Prozess cmd.exe beendet wird
      • Für jede Iteration:
        • Die FOR-Variablenwerte sind definiert
        • Der bereits analysierte DO-Befehlsblock wird dann ab Phase 4 verarbeitet.
    • GOTO verwendet die folgende Logik, um das Label zu finden
      • Das Label wird vom ersten Argument-Token analysiert
      • Das Skript wird nach dem nächsten Auftreten des Etiketts durchsucht
        • Der Scan beginnt an der aktuellen Dateiposition
        • Wenn das Dateiende erreicht ist, kehrt der Scan zum Anfang der Datei zurück und fährt mit dem ursprünglichen Startpunkt fort.
      • Der Scan wird beim ersten Auftreten des gefundenen Etiketts gestoppt, und der Dateizeiger wird auf die Zeile unmittelbar nach dem Etikett gesetzt. Die Ausführung des Skripts wird ab diesem Zeitpunkt fortgesetzt. Beachten Sie, dass ein erfolgreiches echtes GOTO jeden analysierten Codeblock, einschließlich FOR-Schleifen, sofort abbricht.
      • Wenn das Etikett nicht gefunden wird oder das Etikettentoken fehlt, schlägt das GOTO fehl, eine Fehlermeldung wird gedruckt und der Aufrufstapel wird gelöscht. Dies funktioniert effektiv als EXIT / B, außer dass bereits analysierte Befehle im aktuellen Befehlsblock, die auf GOTO folgen, noch ausgeführt werden, jedoch im Kontext des CALLer (dem Kontext, der nach EXIT / B existiert).
      • Unter https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 finden Sie eine genauere Beschreibung der Regeln, die zum Parsen von Labels verwendet werden.
    • RENAME und COPY akzeptieren Platzhalter für den Quell- und den Zielpfad. Aber Microsoft macht einen schrecklichen Job und dokumentiert, wie die Platzhalter funktionieren, insbesondere für den Zielpfad. Eine nützliche Reihe von Platzhalterregeln finden Sie unter Wie interpretiert der Windows-Befehl RENAME Platzhalter?
  • 7.2 - Volumenänderung ausführen - Wenn das Befehlstoken nicht mit einem Anführungszeichen beginnt, genau zwei Zeichen lang ist und das zweite Zeichen ein Doppelpunkt ist, ändern Sie die Lautstärke
    • Alle Argumenttoken werden ignoriert
    • Wenn das durch das erste Zeichen angegebene Volume nicht gefunden werden kann, brechen Sie den Vorgang mit einem Fehler ab
    • Ein Befehlstoken von ::führt immer zu einem Fehler, es sei denn, SUBST wird zum Definieren eines Volumes für verwendet. ::
      Wenn SUBST zum Definieren eines Volumes für verwendet wird ::, wird das Volume geändert und nicht als Bezeichnung behandelt.
  • 7.3 - Externen Befehl ausführen - Andernfalls versuchen Sie, den Befehl als externen Befehl zu behandeln.
    • Wenn im Befehlszeilenmodus und der Befehl nicht zitiert wird , und beginnt nicht mit einem Volumen Spezifikation, white-space, ,, ;, =oder +dann den Befehl beim ersten Auftreten Token brechen von <space> , ;oder =und stellen Sie vor dem Rest das Argument Token (s).
    • Wenn das 2. Zeichen des Befehlstokens ein Doppelpunkt ist, überprüfen Sie, ob das durch das 1. Zeichen angegebene Volume gefunden werden kann.
      Wenn das Volume nicht gefunden werden kann, brechen Sie den Vorgang mit einem Fehler ab.
    • Wenn im Batch-Modus das Befehlstoken mit beginnt :, gehen Sie zu 7.4.
      Beachten Sie, dass wenn das Label-Token mit beginnt ::, dies nicht erreicht wird, da der vorherige Schritt mit einem Fehler abgebrochen wurde, es sei denn, SUBST wird zum Definieren eines Volumes für verwendet ::.
    • Identifizieren Sie den auszuführenden externen Befehl.
      • Dies ist ein komplexer Prozess, der das aktuelle Volume, das aktuelle Verzeichnis, die PATH-Variable, die PATHEXT-Variable und / oder Dateizuordnungen umfassen kann.
      • Wenn ein gültiger externer Befehl nicht identifiziert werden kann, brechen Sie den Vorgang mit einem Fehler ab.
    • Wenn im Befehlszeilenmodus und das Befehlstoken mit beginnt :, gehen Sie zu 7.4.
      Beachten Sie, dass dies selten erreicht wird, da der vorherige Schritt mit einem Fehler abgebrochen wurde, es sei denn, das Befehlstoken beginnt mit ::und SUBST wird verwendet, um ein Volume für ::und zu definieren Das gesamte Befehlstoken ist ein gültiger Pfad zu einem externen Befehl.
    • 7.3.exec - Führen Sie den externen Befehl aus.
  • 7.4 - Beschriftung ignorieren - Ignoriert den Befehl und alle seine Argumente, wenn das Befehlstoken mit beginnt :.
    Die Regeln in 7.2 und 7.3 können verhindern, dass ein Etikett diesen Punkt erreicht.

Befehlszeilen-Parser:

Funktioniert wie der BatchLine-Parser, außer:

Phase 1) Prozentuale Expansion:

  • Nein %*, %1usw. Argument Expansion
  • Wenn var undefiniert %var%ist, bleibt es unverändert.
  • Keine besondere Behandlung von %%. Wenn var = content, wird auf %%var%%erweitert %content%.

Phase 3) Geben Sie die analysierten Befehle wieder.

  • Dies wird nicht nach Phase 2 durchgeführt. Es wird nur nach Phase 4 für den FOR DO-Befehlsblock durchgeführt.

Phase 5) Verzögerte Erweiterung: Nur wenn DelayedExpansion aktiviert ist

  • Wenn var undefiniert !var!ist, bleibt es unverändert.

Phase 7) Befehl ausführen

  • Versuche, a: label anzurufen oder zu erhalten, führen zu einem Fehler.
  • Wie bereits in Phase 7 dokumentiert, kann ein ausgeführtes Label in verschiedenen Szenarien zu einem Fehler führen.
    • Stapelausgeführte Etiketten können nur dann einen Fehler verursachen, wenn sie mit beginnen ::
    • Über die Befehlszeile ausgeführte Beschriftungen führen fast immer zu einem Fehler

Parsen von Ganzzahlwerten

Es gibt viele verschiedene Kontexte, in denen cmd.exe ganzzahlige Werte aus Zeichenfolgen analysiert und die Regeln inkonsistent sind:

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

Details zu diesen Regeln finden Sie unter Regeln, wie CMD.EXE Zahlen analysiert


Für alle, die die Parsing-Regeln für cmd.exe verbessern möchten, gibt es im DosTips-Forum ein Diskussionsthema, in dem Probleme gemeldet und Vorschläge gemacht werden können.

Hoffe es hilft
Jan Erik (jeb) - Ursprünglicher Autor und Entdecker der Phasen
Dave Benham (dbenham) - Viel zusätzlicher Inhalt und Bearbeitung


4
Hallo jeb, danke für deinen Einblick ... Es mag schwer zu verstehen sein, aber ich werde versuchen, es durchzudenken! Sie scheinen viele Tests durchgeführt zu haben! Vielen Dank für die Übersetzung ( Administrator.de/… )
Benoit

2
Batch-Phase 5) - %% a wurde bereits in Phase 1 in% a geändert, sodass die For-Loop-Erweiterung% a wirklich erweitert. Außerdem habe ich in einer Antwort unten eine detailliertere Erklärung der
Stapelphase

3
Jeb - vielleicht könnte Phase 0 verschoben und mit Phase 6 kombiniert werden? Das macht für mich mehr Sinn, oder gibt es einen Grund, warum sie so getrennt sind?
Dbenham

1
@aschipfl - Ich habe diesen Abschnitt aktualisiert. Das )funktioniert wirklich fast wie ein REMBefehl, wenn der Klammerzähler 0 ist. Versuchen Sie beide über die Befehlszeile : ) Ignore this, undecho OK & ) Ignore this
dbenham

1
@aschipfl ja das stimmt, deshalb siehst du manchmal 'set "var =% expr%"! 'Das letzte Ausrufezeichen wird entfernt, erzwingt aber Phase 5
jeb

62

Wenn Sie einen Befehl über ein Befehlsfenster aufrufen, erfolgt die Tokenisierung der Befehlszeilenargumente nicht durch cmd.exe(auch als "Shell" bezeichnet). Meistens erfolgt die Tokenisierung über die C / C ++ - Laufzeit der neu gebildeten Prozesse. Dies ist jedoch nicht unbedingt der Fall - beispielsweise, wenn der neue Prozess nicht in C / C ++ geschrieben wurde oder wenn der neue Prozess ignoriert argvund verarbeitet die rohe Kommandozeile für sich selbst (zB mit GetCommandLine ()). Auf Betriebssystemebene übergibt Windows Befehlszeilen, die nicht als einzelne Zeichenfolge gekennzeichnet sind, an neue Prozesse. Dies steht im Gegensatz zu den meisten * nix-Shells, bei denen die Shell Argumente auf konsistente, vorhersehbare Weise tokenisiert, bevor sie an den neu gebildeten Prozess übergeben werden. All dies bedeutet, dass das Verhalten der Tokenisierung von Argumenten in verschiedenen Programmen unter Windows sehr unterschiedlich sein kann, da einzelne Programme die Tokenisierung von Argumenten häufig selbst in die Hand nehmen.

Wenn es nach Anarchie klingt, ist es das auch. Da jedoch eine große Anzahl von Windows - Programmen zu tun , die Microsoft C / C ++ Laufzeit des nutzen argv, kann es in der Regel sinnvoll sein , zu verstehen , wie die MSVCRT tokenizes Argumente. Hier ist ein Auszug:

  • Argumente werden durch Leerzeichen begrenzt, die entweder ein Leerzeichen oder eine Registerkarte sind.
  • Eine Zeichenfolge, die von doppelten Anführungszeichen umgeben ist, wird unabhängig vom darin enthaltenen Leerraum als einzelnes Argument interpretiert. Eine Zeichenfolge in Anführungszeichen kann in ein Argument eingebettet werden. Beachten Sie, dass das Caret (^) nicht als Escapezeichen oder Trennzeichen erkannt wird.
  • Ein doppeltes Anführungszeichen mit vorangestelltem Backslash "" wird als wörtliches doppeltes Anführungszeichen (") interpretiert.
  • Backslashes werden wörtlich interpretiert, es sei denn, sie stehen unmittelbar vor einem doppelten Anführungszeichen.
  • Wenn auf eine gerade Anzahl von Backslashes ein doppeltes Anführungszeichen folgt, wird für jedes Paar von Backslashes (\) ein Backslash () in das argv-Array eingefügt, und das doppelte Anführungszeichen (") wird als Zeichenfolgenbegrenzer interpretiert.
  • Wenn auf eine ungerade Anzahl von Backslashes ein doppeltes Anführungszeichen folgt, wird für jedes Paar von Backslashes (\) ein Backslash () in das argv-Array eingefügt, und das doppelte Anführungszeichen wird vom verbleibenden Backslash als Escape-Sequenz interpretiert ein wörtliches doppeltes Anführungszeichen (") in argv.

Die "Batch-Sprache" ( .bat) von Microsoft ist keine Ausnahme von dieser anarchischen Umgebung und hat eigene eindeutige Regeln für die Tokenisierung und das Escapezeichen entwickelt. Es sieht auch so aus, als würde die Eingabeaufforderung von cmd.exe das Befehlszeilenargument vorverarbeiten (hauptsächlich zum Ersetzen und Escapezeichen von Variablen), bevor das Argument an den neu ausgeführten Prozess übergeben wird. Weitere Informationen zu den Details der Batch-Sprache und des Cmd auf niedriger Ebene finden Sie in den hervorragenden Antworten von jeb und dbenham auf dieser Seite.


Lassen Sie uns ein einfaches Befehlszeilenprogramm in C erstellen und sehen, was es über Ihre Testfälle aussagt:

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

(Hinweise: argv [0] ist immer der Name der ausführbaren Datei und wird der Kürze halber unten weggelassen. Getestet unter Windows XP SP3. Kompiliert mit 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]

Und einige meiner eigenen 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]

Vielen Dank für Ihre Antwort. Es verwirrt mich noch mehr zu sehen, dass TinyPerl nicht ausgibt, was Ihr Programm ausgibt, und ich habe Schwierigkeiten zu verstehen, wie [a "b" c]es zur [a "b] [c]Nachbearbeitung kommen kann.
Benoit

Nun, da ich darüber nachdenke, wird diese Tokenisierung der Befehlszeile wahrscheinlich vollständig von der C-Laufzeit durchgeführt. Eine ausführbare Datei könnte so geschrieben werden, dass sie nicht einmal die C-Laufzeit verwendet. In diesem Fall müsste sie sich meiner Meinung nach wörtlich mit der Befehlszeile befassen und für ihre eigene Tokenisierung verantwortlich sein (wenn dies gewünscht wird). Oder sogar Wenn Ihre Anwendung die C-Laufzeit verwendet, können Sie argc und argv ignorieren und einfach die unformatierte Befehlszeile über z GetCommandLine. B. Win32 abrufen . Vielleicht ignoriert TinyPerl argv und markiert die rohe Befehlszeile einfach nach eigenen Regeln.
Mike Clark

4
"Denken Sie daran, dass aus Sicht von Win32 die Befehlszeile nur eine Zeichenfolge ist, die in den Adressraum des neuen Prozesses kopiert wird. Wie der Startprozess und der neue Prozess diese Zeichenfolge interpretieren, wird nicht von Regeln, sondern von Konventionen bestimmt." -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark

2
Vielen Dank für diese wirklich schöne Antwort. Das erklärt meiner Meinung nach viel. Und das erklärt auch, warum ich es manchmal wirklich beschissen finde, mit Windows zu arbeiten…
Benoit

Ich fand dies in Bezug auf Backslashes und Anführungszeichen während der Umwandlung von der Befehlszeile in argv für Win32 C ++ - Programme. Die Anzahl der Backslashes wird nur durch zwei geteilt, wenn auf den letzten Backslash ein dblquote folgt, und das dblquote beendet eine Zeichenfolge, wenn zuvor eine gerade Anzahl von Backslashes vorhanden ist.
Benoit

47

Prozent Expansionsregeln

Hier ist eine erweiterte Erklärung von Phase 1 in Jebs Antwort (gültig sowohl für den Batch-Modus als auch für den Befehlszeilenmodus).

Phase 1) Prozentuale Erweiterung Scannen Sie von links nach jedem Zeichen nach %oder <LF>. Wenn dann gefunden

  • 1,05 (Linie abschneiden bei <LF>)
    • Wenn der Charakter <LF>dann ist
      • Lassen Sie den Rest der Zeile von nun <LF>an fallen (ignorieren)
      • Gehe zu Phase 1.5 (Streifen <CR>)
    • Andernfalls muss der Charakter sein %, fahren Sie also mit 1.1 fort
  • 1.1 (Escape %) wird im Befehlszeilenmodus übersprungen
    • Im Batch-Modus und gefolgt von einem anderen, %dann durch Single
      ersetzen und den Scan fortsetzen%%%
  • 1.2 (Argument erweitern) wurde im Befehlszeilenmodus übersprungen
    • Sonst wenn Batch-Modus dann
      • Wenn gefolgt von *und Befehlserweiterungen aktiviert sind,
        ersetzen Sie %*durch den Text aller Befehlszeilenargumente (Ersetzen durch nichts, wenn keine Argumente vorhanden sind) und fahren Sie mit dem Scannen fort.
      • Wenn gefolgt Else von <digit>dann
        Ersetzen %<digit>mit Argumentwert (mit nichts ersetzen , wenn nicht definiert) und Scan fortsetzen.
      • Andernfalls sind ~Befehlserweiterungen aktiviert, wenn gefolgt von und
        • Wenn durch optionale gültige Liste von Argumenten Modifikatoren gefolgt von erforderlichen <digit>dann
          Ersetzen %~[modifiers]<digit>mit modifizierten Argument - Wert (mit nichts ersetzen , wenn nicht definiert oder wenn $ PATH angegeben: Modifikator nicht definiert ist ) und Scan fortsetzen.
          Hinweis: Modifikatoren unterscheiden nicht zwischen Groß- und Kleinschreibung und können in beliebiger Reihenfolge mehrmals angezeigt werden, mit Ausnahme von $ PATH: Der Modifikator kann nur einmal angezeigt werden und muss der letzte Modifikator vor dem sein<digit>
        • Andernfalls führt eine ungültige Syntax für geänderte Argumente zu einem schwerwiegenden Fehler: Alle analysierten Befehle werden abgebrochen, und die Stapelverarbeitung wird abgebrochen, wenn sie sich im Stapelmodus befinden!
  • 1.3 (Variable erweitern)
    • Andernfalls, wenn Befehlserweiterungen deaktiviert sind,
      schauen Sie sich die nächste Zeichenfolge an, die vor %oder am Ende des Puffers unterbrochen wird , und nennen Sie sie VAR (möglicherweise eine leere Liste).
      • Wenn das nächste Zeichen %dann ist
        • Wenn VAR definiert ist,
          ersetzen Sie %VAR%durch VAR und setzen Sie den Scan fort
        • Andernfalls im Batch-Modus
          entfernen %VAR%und den Scan fortsetzen
        • Sonst gehe zu 1.4
      • Sonst gehe zu 1.4
    • Andernfalls, wenn Befehlserweiterungen aktiviert sind,
      schauen Sie sich die nächste Zeichenfolge an, die vor % :oder am Ende des Puffers unterbrochen wird , und nennen Sie sie VAR (möglicherweise eine leere Liste). Wenn VAR Pausen vor :und die nachfolgenden Zeichen %umfassen dann :als letztes Zeichen in VAR und Pause vor %.
      • Wenn das nächste Zeichen %dann ist
        • Wenn VAR definiert ist,
          ersetzen Sie %VAR%durch VAR und setzen Sie den Scan fort
        • Andernfalls im Batch-Modus
          entfernen %VAR%und den Scan fortsetzen
        • Sonst gehe zu 1.4
      • Sonst, wenn das nächste Zeichen :dann ist
        • Wenn VAR undefiniert ist, dann
          • Im Batch-Modus dann
            entfernen %VAR:und den Scan fortsetzen.
          • Sonst gehe zu 1.4
        • Sonst, wenn das nächste Zeichen ~dann ist
          • Wenn nächste Zeichenkette Muster entspricht die [integer][,[integer]]%dann
            Ersetzen %VAR:~[integer][,[integer]]%mit String Wert von VAR (möglicherweise in leeren String führt) und Scan fortsetzen.
          • Sonst gehe zu 1.4
        • Wenn Else gefolgt von =oder *=dann
          Unzulässige Variable Suchen und Ersetzen - Syntax werfen fatale Fehler: Alle analysierten Befehle werden abgebrochen, und die Stapelverarbeitung wird abgebrochen , wenn im Batch - Modus!
        • Andernfalls, wenn die nächste Zeichenfolge mit dem Muster von übereinstimmt [*]search=[replace]%, wobei die Suche einen beliebigen Zeichensatz außer =und das Ersetzen einen beliebigen Zeichensatz außer enthalten kann %,
          ersetzen Sie %VAR:[*]search=[replace]%nach dem Suchen und Ersetzen durch den Wert VAR (was möglicherweise zu einer leeren Zeichenfolge führt) und fahren Sie fort Scan
        • Sonst gehe zu 1.4
  • 1,4 (Streifen%)
    • Sonst Wenn Batch-Modus, dann
      Entfernen Sie den %Scan und setzen Sie ihn fort, beginnend mit dem nächsten Zeichen nach dem%
    • Andernfalls behalten Sie die Führung bei %und setzen den Scan fort, beginnend mit dem nächsten Zeichen nach der erhaltenen Führung%

Das Obige hilft zu erklären, warum diese Charge

@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

Gibt diese Ergebnisse:

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

Anmerkung 1 - Phase 1 erfolgt vor der Erkennung von REM-Anweisungen. Dies ist sehr wichtig, da selbst eine Bemerkung einen schwerwiegenden Fehler verursachen kann, wenn sie eine ungültige Argumenterweiterungssyntax oder eine ungültige Such- und Ersetzungssyntax für Variablen aufweist!

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

Hinweis 2 - Eine weitere interessante Konsequenz der% parsing-Regeln: Variablen, die: im Namen enthalten, können definiert, aber nur erweitert werden, wenn die Befehlserweiterungen deaktiviert sind. Es gibt eine Ausnahme: Ein Variablenname, der am Ende einen einzelnen Doppelpunkt enthält, kann erweitert werden, während Befehlserweiterungen aktiviert sind. Sie können jedoch keine Teilzeichenfolgen oder Such- und Ersetzungsvorgänge für Variablennamen ausführen, die mit einem Doppelpunkt enden. Die folgende Batch-Datei (mit freundlicher Genehmigung von jeb) zeigt dieses Verhalten

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

Anmerkung 3 - Ein interessantes Ergebnis der Reihenfolge der Parsing-Regeln, die jeb in seinem Beitrag festlegt: Wenn Sie Suchen und Ersetzen mit verzögerter Erweiterung ausführen, müssen Sonderzeichen sowohl in den Begriffen Suchen als auch Ersetzen maskiert oder in Anführungszeichen gesetzt werden. Bei der prozentualen Expansion ist die Situation jedoch anders - der Suchbegriff darf nicht maskiert werden (obwohl er zitiert werden kann). Die prozentuale Ersetzungszeichenfolge erfordert je nach Ihrer Absicht möglicherweise ein Escape- oder Anführungszeichen.

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

Verzögerte Erweiterungsregeln

Hier ist eine erweiterte und genauere Erklärung von Phase 5 in Jebs Antwort (gültig sowohl für den Batch-Modus als auch für den Befehlszeilenmodus).

Phase 5) Verzögerte Expansion

Diese Phase wird übersprungen, wenn eine der folgenden Bedingungen zutrifft:

  • Die verzögerte Erweiterung ist deaktiviert.
  • Der Befehl befindet sich in einem Block in Klammern auf beiden Seiten einer Pipe.
  • Der ankommende Befehl Token ist ein „nackter“ Batch - Skript, das heißt , es nicht zugeordnet ist CALL, geklammerten Block, jede Form von Befehlsverkettung ( &, &&oder ||) oder ein Rohr |.

Der verzögerte Expansionsprozess wird unabhängig auf Token angewendet. Ein Befehl kann mehrere Token haben:

  • Das Befehlstoken. Bei den meisten Befehlen ist der Befehlsname selbst ein Token. Einige Befehle haben jedoch spezielle Regionen, die als TOKEN für Phase 5 gelten.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, In dem Vergleich ein von ==, equ, neq, lss, leq, gtr, odergeq
  • Die Argumente Token
  • Das Ziel-Token der Umleitung (eines pro Umleitung)

Token, die keine enthalten, werden nicht geändert !.

!Scannen Sie für jedes Token, das mindestens eines enthält , jedes Zeichen von links nach rechts nach ^oder !, und falls gefunden, dann

  • 5.1 (Caret Escape) Erforderlich für !oder ^Literale
    • Wenn Charakter ein Caret ist, ^dann
      • Entferne das ^
      • Scannen Sie das nächste Zeichen und bewahren Sie es als Literal auf
      • Setzen Sie den Scan fort
  • 5.2 (Variable erweitern)
    • Wenn Charakter ist !, dann
      • Wenn Befehlserweiterungen deaktiviert sind,
        schauen Sie sich die nächste Zeichenfolge an, die vor !oder unterbrochen wird <LF>, und nennen Sie sie VAR (möglicherweise eine leere Liste).
        • Wenn das nächste Zeichen !dann ist
          • Wenn VAR definiert ist,
            ersetzen Sie !VAR!durch VAR und setzen Sie den Scan fort
          • Sonst wenn Batch-Modus dann
            entfernen !VAR!und den Scan fortsetzen
          • Sonst gehe zu 5.2.1
        • Sonst gehe zu 5.2.1
      • Else Wenn die Befehlserweiterungen aktiviert sind dann
        anschauen nächste Folge von Zeichen, brechen vor !, :oder <LF>, und nennen sie VAR (kann eine leere Liste sein). Wenn VAR Pausen vor :und die nachfolgenden Zeichen !umfassen dann :als letztes Zeichen in VAR und Pause vor!
        • Wenn das nächste Zeichen ist !dann ist
          • Wenn VAR vorhanden ist,
            ersetzen Sie!VAR! durch den Wert VAR und fahren Sie mit dem Scannen fort
          • Andernfalls, wenn Batch-Modus, dann
            Entfernen!VAR! und den Scan fortsetzen
          • Sonst gehe zu 5.2.1
        • Sonst, wenn das nächste Zeichen ist :dann ist
          • Wenn VAR undefiniert ist, dann
            • Im Batch-Modus dann
              Entfernen!VAR: und den Scan fortsetzen
            • Sonst gehe zu 5.2.1
          • Sonst, wenn das nächste Zeichen ist ~dann ist
            • Wenn die nächste Zeichenfolge mit dem Muster von übereinstimmt, [integer][,[integer]]!ersetzen Sie sie !VAR:~[integer][,[integer]]!durch eine Teilzeichenfolge mit dem Wert VAR (was möglicherweise zu einer leeren Zeichenfolge führt) und fahren Sie mit dem Scannen fort.
            • Sonst gehe zu 5.2.1
          • Andernfalls, wenn die nächste Zeichenfolge mit dem Muster von übereinstimmt [*]search=[replace]!, wobei die Suche einen beliebigen Zeichensatz außer =und das Ersetzen einen beliebigen Zeichensatz außer enthalten kann !, dann
            Ersetzen!VAR:[*]search=[replace]! nach dem Suchen und durch den Wert VAR (was möglicherweise zu einer leeren Zeichenfolge führt) und Scan fortsetzen
          • Sonst gehe zu 5.2.1
        • Sonst gehe zu 5.2.1
      • 5.2.1
        • Im Batch-Modus entfernen Sie das führende. !
          Andernfalls behalten Sie das führende bei!
        • Setzen Sie den Scan fort, beginnend mit dem nächsten Zeichen nach der erhaltenen Überschrift !

3
+1, Hier fehlen nur die Doppelpunktsyntax und die Regeln für %definedVar:a=b%vs %undefinedVar:a=b%und die %var:~0x17,-010%Formulare
jeb

2
Guter Punkt - Ich habe den Abschnitt zur variablen Erweiterung erweitert, um Ihre Bedenken auszuräumen. Ich habe auch den Abschnitt zur Argumenterweiterung erweitert, um einige fehlende Details zu ergänzen.
Dbenham

2
Nachdem ich einige zusätzliche private Rückmeldungen von jeb erhalten hatte, fügte ich eine Regel für Variablennamen hinzu, die mit Doppelpunkt enden, und fügte Anmerkung 2 hinzu. Ich fügte auch Anmerkung 3 hinzu, nur weil ich es für interessant und wichtig hielt.
Dbenham

1
@aschipfl - Ja, ich habe darüber nachgedacht, näher darauf einzugehen, wollte aber nicht in dieses Kaninchenloch gehen. Ich war absichtlich unverbindlich, als ich den Begriff [Ganzzahl] verwendete. Weitere Informationen dazu, wie CMD.EXE Zahlen analysiert, finden Sie unter Regeln .
Dbenham

1
Ich vermisse die Erweiterungsregeln für den cmd Kontext, wie , dass es keine reservierten Zeichen für das erste Zeichen des Variablennamen wie sind %<digit>, %*oder %~. Und das Verhalten ändert sich für undefinierte Variablen. Vielleicht müssen Sie eine zweite Antwort öffnen
jeb

7

Wie bereits erwähnt, wird Befehlen die gesamte Argumentzeichenfolge in μSoft Land übergeben, und es liegt an ihnen, diese für ihren eigenen Gebrauch in separate Argumente zu zerlegen. Es gibt keine Übereinstimmung zwischen verschiedenen Programmen, und daher gibt es keine Regeln, die diesen Prozess beschreiben. Sie müssen wirklich jeden Eckfall auf die von Ihrem Programm verwendete C-Bibliothek überprüfen.

Was die Systemdateien .batbetrifft, ist hier dieser 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

Jetzt können wir einige Tests durchführen. Sehen Sie, ob Sie herausfinden können, was μSoft versucht:

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

Bis jetzt gut. (Ich werde das Uninteressante weglassen %cmdcmdline%und %0von nun an.)

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

Keine Dateinamenerweiterung.

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

Kein Entfernen von Anführungszeichen, obwohl Anführungszeichen das Aufteilen von Argumenten verhindern.

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

Aufeinanderfolgende doppelte Anführungszeichen führen dazu, dass sie ihre speziellen Analysefähigkeiten verlieren. @ Beniots Beispiel:

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

Quiz: Wie übergeben Sie den Wert einer Umgebungsvariablen als einzelnes Argument (dh als %1) an eine Bat-Datei?

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!

Vernünftiges Parsen scheint für immer gebrochen zu sein.

Für Ihre Unterhaltung, versuchen Sie , verschiedene ^, \, ', &(usw.) Zeichen auf diese Beispiele.


Um% t% als einzelnes Argument zu übergeben, können Sie "% t:" = \ "%" verwenden. Verwenden Sie also die% VAR: str = Ersetzung% -Syntax für die Variablenerweiterung. Shell Metazeichen wie | und & in der Variablen Inhalt kann immer noch ausgesetzt werden und die Shell durcheinander bringen, es sei denn, Sie entkommen ihnen wieder ....
Toughy

@Toughy Also, in meinem Beispiel tist a "b c. Haben Sie ein Rezept haben diese 6 Zeichen für immer ( a2 × Raum, ", b, und c) wie zu erscheinen %1innerhalb einer .cmd? Ich mag dein Denken. args "%t:"=""%"ist ziemlich nah :-)
Bobbogo

5

Sie haben oben bereits einige gute Antworten, aber um einen Teil Ihrer Frage zu beantworten:

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

Was dort passiert ist, dass, weil Sie ein Leerzeichen vor dem = haben, eine Variable erstellt wird, die %a<space>% so genannt wird , wenn Sie echo %a %korrekt ausgewertet werden als b.

Der verbleibende Teil b% c%wird dann als einfacher Text + eine undefinierte Variable ausgewertet % c%, die für mich echo %a %b% c%zurückgegeben werden solltebb% c%

Ich vermute, dass die Möglichkeit, Leerzeichen in Variablennamen aufzunehmen, eher ein Versehen als ein geplantes "Feature" ist.


0

Bearbeiten: Siehe akzeptierte Antwort. Das Folgende ist falsch und erklärt nur, wie eine Befehlszeile an TinyPerl übergeben wird.


In Bezug auf Zitate habe ich das Gefühl, dass das Verhalten wie folgt ist:

  • Wenn a "gefunden wird, beginnt das Globbing der Zeichenfolge
  • Wenn ein String-Globbing auftritt:
    • Jedes Zeichen, das kein a "ist, ist global
    • wenn a "gefunden wird:
      • Wenn darauf folgt ""(also ein Dreifach "), wird der Zeichenfolge ein doppeltes Anführungszeichen hinzugefügt
      • Wenn darauf folgt "(also ein Doppel "), wird dem String ein doppeltes Anführungszeichen hinzugefügt und das Ende des String-Globbings
      • Ist dies nicht der "Fall, endet das String-Globbing
    • Wenn die Linie endet, endet das String-Globbing.

Zusamenfassend:

"a """ b "" c"""besteht aus zwei Zeichenfolgen: a " b "undc"

"a"", "a"""und "a""""sind alle die gleiche Zeichenfolge, wenn am Ende einer Zeile


Der Tokenizer und das String-Globbing hängen vom Befehl ab! Ein "Set" funktioniert anders als ein "Call" oder sogar ein "If"
jeb

Ja, aber was ist mit externen Befehlen? Ich denke, cmd.exe übergibt ihnen immer die gleichen Argumente?
Benoit

1
cmd.exe übergibt das Erweiterungsergebnis immer als Zeichenfolge und nicht als Token an einen externen Befehl. Es hängt vom externen Befehl ab, wie man es entkommt und tokenisiert. Findstr verwendet Backslash, der nächste kann etwas anderes verwenden
jeb

0

Beachten Sie, dass Microsoft den Quellcode seines Terminals veröffentlicht hat. Es funktioniert möglicherweise ähnlich wie die Befehlszeile in Bezug auf die Syntaxanalyse. Vielleicht ist jemand daran interessiert, die rückentwickelten Parsing-Regeln gemäß den Parsing-Regeln des Terminals zu testen.

Link zum Quellcode.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.