W jaki sposób interpreter poleceń systemu Windows (CMD.EXE) analizuje skrypty?


142

Natrafiłem na ss64.com, który zapewnia dobrą pomoc dotyczącą pisania skryptów wsadowych, które będzie uruchamiał interpreter poleceń systemu Windows.

Jednak nie udało mi się znaleźć dobrego wyjaśnienia gramatyki skryptów wsadowych, tego, jak rzeczy się rozszerzają lub nie, ani jak uciec od rzeczy.

Oto przykładowe pytania, których nie udało mi się rozwiązać:

  • Jak zarządzany jest system ofert? Zrobiłem TinyPerl skryptu
    ( foreach $i (@ARGV) { print '*' . $i ; }), opracowano go i nazwał go w ten sposób:
    • my_script.exe "a ""b"" c" → wyjście jest *a "b*c
    • my_script.exe """a b c""" → wyślij to *"a*b*c"
  • Jak działa wewnętrzne echopolecenie? Co jest rozwinięte w tym poleceniu?
  • Dlaczego muszę używać for [...] %%Iw skryptach plików, ale for [...] %Iw sesjach interaktywnych?
  • Jakie są postacie ucieczki i w jakim kontekście? Jak uciec przed znakiem procentu? Na przykład, jak mogę %PROCESSOR_ARCHITECTURE%dosłownie echo ? Okazało się, że echo.exe %""PROCESSOR_ARCHITECTURE%działa, czy jest lepsze rozwiązanie?
  • Jak %dopasowują się pary ? Przykład:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • Jak upewnić się, że zmienna jest przekazywana do polecenia jako pojedynczy argument, jeśli ta zmienna zawiera podwójne cudzysłowy?
  • Jak przechowywane są zmienne podczas korzystania z setpolecenia? Na przykład, jeśli to zrobię set a=a" bi echo.%a%otrzymam a" b. Jeśli jednak skorzystam echo.exez UnxUtils, otrzymam a b. Jak to się %a%rozwija w inny sposób?

Dziękuję za wasze światła.


Odpowiedzi:


200

Przeprowadziliśmy eksperymenty, aby zbadać gramatykę skryptów wsadowych. Zbadaliśmy również różnice między trybem wsadowym a trybem wiersza poleceń.

Parser wiersza wsadu:

Oto krótki przegląd faz w parserze linii pliku wsadowego:

Faza 0) Linia odczytu:

Faza 1) Procentowa ekspansja:

Faza 2) Przetwarzaj znaki specjalne, tokenizuj i twórz buforowany blok poleceń: jest to złożony proces, na który wpływają takie rzeczy, jak cudzysłowy, znaki specjalne, ograniczniki tokenów i znaki specjalne.

Faza 3) Powtórz przeanalizowane polecenie (polecenia) Tylko wtedy, gdy blok poleceń nie rozpoczął się @, a funkcja ECHO była WŁĄCZONA na początku poprzedniego kroku.

Faza 4) %XRozszerzenie zmiennej FOR : Tylko wtedy, gdy polecenie FOR jest aktywne i polecenia po DO są przetwarzane.

Faza 5) Opóźniona ekspansja: Tylko jeśli opóźniona ekspansja jest włączona

Faza 5.3) Przetwarzanie potoku : tylko wtedy, gdy polecenia są po obu stronach rury

Faza 5.5) Wykonaj przekierowanie:

Faza 6) Przetwarzanie wywołania / podwojenie karetki: Tylko jeśli token polecenia to CALL

Faza 7) Wykonaj: Polecenie jest wykonywane


Oto szczegóły dla każdej fazy:

Zauważ, że fazy opisane poniżej to tylko model działania parsera wsadowego. Rzeczywiste wewnętrzne funkcje cmd.exe mogą nie odzwierciedlać tych faz. Ale ten model jest skuteczny w przewidywaniu zachowania skryptów wsadowych.

Faza 0) Czytaj linię: Odczytaj najpierw linię wejściową <LF>.

  • Podczas odczytywania wiersza do przeanalizowania jako polecenia, <Ctrl-Z>(0x1A) jest odczytywane jako <LF>(LineFeed 0x0A)
  • Gdy GOTO lub CALL odczytuje linie podczas skanowania przez: etykiety <Ctrl-Z>, działa jako samo - to nie przekształca się<LF>

Faza 1) Procentowa ekspansja:

  • Podwójny %%zostaje zastąpiony pojedynczym%
  • Rozbudowa argumentów ( %*, %1, %2, itd.)
  • Ekspansja %var% , jeśli var nie istnieje, zastąp je niczym
  • Linia jest początkowo obcięta, a <LF>nie wewnątrz%var% rozwijania
  • Aby uzyskać pełne wyjaśnienie, przeczytaj pierwszą połowę tego z dbenham Ten sam wątek: Percent Phase

Faza 2) Przetwarzaj znaki specjalne, tokenizuj i stwórz buforowany blok poleceń: jest to złożony proces, na który wpływają takie rzeczy, jak cudzysłowy, znaki specjalne, ograniczniki tokenów i znaki daszka. Poniżej przedstawiono przybliżenie tego procesu.

Istnieją koncepcje, które są ważne na tym etapie.

  • Token to po prostu ciąg znaków, który jest traktowany jako jednostka.
  • Tokeny są oddzielone ogranicznikami znaczników. Standardowymi ogranicznikami tokenów są <space> <tab> ; , = <0x0B> <0x0C>i<0xFF>
    kolejne ograniczniki tokenów są traktowane jako jeden - między ogranicznikami tokenów nie ma pustych tokenów
  • W ciągu ujętym w cudzysłów nie ma ograniczników znaczników. Cały cytowany ciąg jest zawsze traktowany jako część pojedynczego tokenu. Pojedynczy token może składać się z kombinacji ciągów znaków w cudzysłowie i znaków niecytowanych.

Następujące znaki mogą mieć specjalne znaczenie w tej fazie, w zależności od kontekstu: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

Spójrz na każdą postać od lewej do prawej:

  • Jeśli <CR>to usuń go, jakby nigdy go tam nie było (z wyjątkiem dziwnego zachowania przekierowania )
  • Jeśli daszek ( ^), następny znak jest zmieniany, a uciekający znak jest usuwany. Postacie, którym uciekł, tracą wszelkie specjalne znaczenie (z wyjątkiem <LF>).
  • Jeśli quote ( "), przełącz flagę quote. Jeśli flaga cudzysłowu jest aktywna, to tylko "i<LF> są specjalne. Wszystkie inne znaki tracą swoje specjalne znaczenie, dopóki następny cytat nie wyłączy flagi cytatu. Nie można uciec od notowania zamykającego. Wszystkie cytowane znaki zawsze znajdują się w tym samym tokenie.
  • <LF>zawsze wyłącza flagę cytatu. Inne zachowania różnią się w zależności od kontekstu, ale cytaty nigdy nie zmieniają zachowania<LF> .
    • Uciekł <LF>
      • <LF> jest pozbawiony
      • Następna postać jest uciekana. Jeśli bufor znajduje się na końcu linii, to następna linia jest odczytywana i przetwarzana przez fazy 1 i 1.5 i dołączana do bieżącej przed przejściem na następny znak. Jeśli następny znak to <LF>, to jest traktowany jako literał, co oznacza, że ​​ten proces nie jest rekurencyjny.
    • Bez znaku zmiany znaczenia <LF>nie w nawiasach
      • <LF> jest usuwany i analizowanie bieżącej linii jest przerywane.
      • Wszelkie pozostałe znaki w buforze linii są po prostu ignorowane.
    • Bez znaku zmiany znaczenia <LF>w bloku FOR IN w nawiasach
      • <LF> jest konwertowany na <space>
      • Jeśli na końcu bufora linii, to następna linia jest odczytywana i dołączana do bieżącej.
    • Bez znaku zmiany znaczenia <LF>w bloku poleceń w nawiasach
      • <LF>jest konwertowany na <LF><space>, a <space>jest traktowany jako część następnego wiersza bloku poleceń.
      • Jeśli na końcu bufora linii, to następna linia jest odczytywana i dodawana do spacji.
  • Jeśli jeden ze znaków specjalnych & | <lub >, podziel linię w tym miejscu, aby obsłużyć potoki, konkatenację poleceń i przekierowanie.
    • W przypadku rury (| ) każda strona jest oddzielnym poleceniem (lub blokiem poleceń), które otrzymuje specjalną obsługę w fazie 5.3
    • W przypadku &, &&lub ||polecenia konkatenacji z każdej strony łączenie jest traktowany jako osobna polecenia.
    • W przypadku <, <<, >lub >>przekierowania klauzula przekierowania jest analizowany czasowo usunięty, a następnie dodawana na końcu zadanego prądu. Klauzula przekierowania składa się z opcjonalnej cyfry uchwytu pliku, operatora przekierowania i tokenu miejsca docelowego przekierowania.
      • Jeśli token poprzedzający operator przekierowania jest pojedynczą cyfrą bez znaku zmiany znaczenia, wówczas cyfra określa uchwyt pliku, który ma zostać przekierowany. Jeśli token uchwytu nie zostanie znaleziony, przekierowanie wyjścia jest domyślnie ustawione na 1 (stdout), a przekierowanie wejścia domyślnie na 0 (stdin).
  • Jeśli pierwszy token tego polecenia (przed przeniesieniem przekierowania na koniec) zaczyna się od @, to @ma specjalne znaczenie. ( @nie jest wyjątkowy w żadnym innym kontekście)
    • Oferta specjalna @została usunięta.
    • Jeśli ECHO jest włączone, to polecenie, wraz z następującymi po nim połączonymi poleceniami w tej linii, jest wykluczone z echa fazy 3. Jeśli @jest przed otwarciem (, cały blok umieszczony w nawiasach jest wykluczony z echa fazy 3.
  • Nawias procesowy (zawiera instrukcje złożone w wielu wierszach):
    • Jeśli parser nie szuka tokenu polecenia, (to nie jest specjalny.
    • Jeśli parser szuka tokenu polecenia i znajdzie (, uruchom nową instrukcję złożoną i zwiększ licznik nawiasów
    • Jeśli licznik nawiasów jest> 0, )kończy instrukcję złożoną i zmniejsza licznik nawiasów.
    • Jeśli osiągnięto koniec linii, a licznik nawiasów jest> 0, to następna linia zostanie dołączona do instrukcji złożonej (zaczyna się ponownie od fazy 0)
    • Jeśli licznik nawiasów wynosi 0, a parser szuka polecenia, wówczas )działa podobnie do aREM instrukcji, o ile bezpośrednio następuje po niej ogranicznik tokenu, znak specjalny, nowa linia lub koniec pliku
      • Wszystkie znaki specjalne tracą znaczenie z wyjątkiem ^(możliwe jest łączenie wierszy)
      • Po osiągnięciu końca linii logicznej całe „polecenie” jest odrzucane.
  • Każde polecenie jest przetwarzane na serię tokenów. Pierwszy token jest zawsze traktowany jako żeton polecenia (po @usunięciu elementu specjalnego i przeniesieniu przekierowania na koniec).
    • Wiodące ograniczniki znaczników przed znacznikiem polecenia są usuwane
    • Podczas analizowania tokenu polecenia (działa jako ogranicznik tokenu polecenia, oprócz standardowych ograniczników tokenu
    • Obsługa kolejnych tokenów zależy od polecenia.
  • Większość poleceń po prostu łączy wszystkie argumenty po tokenie polecenia w token z pojedynczym argumentem. Wszystkie ograniczniki tokenu argumentów są zachowywane. Opcje argumentów zwykle nie są analizowane do fazy 7.
  • Trzy polecenia mają specjalną obsługę - IF, FOR i REM
    • IF jest podzielone na dwie lub trzy odrębne części, które są przetwarzane niezależnie. Błąd składni w konstrukcji IF spowoduje fatalny błąd składniowy.
      • Operacja porównania to rzeczywiste polecenie, które przechodzi przez całą drogę do fazy 7
        • Wszystkie opcje IF są w pełni analizowane w fazie 2.
        • Kolejne ograniczniki znaczników zwijają się w jedno miejsce.
        • W zależności od operatora porównania zostanie zidentyfikowany jeden lub dwa tokeny wartości.
      • Blok poleceń True to zestaw poleceń występujących po warunku i jest przetwarzany tak, jak każdy inny blok poleceń. Jeśli ma być używane ELSE, wówczas blok True musi być umieszczony w nawiasach.
      • Opcjonalny blok poleceń False to zestaw poleceń występujących po ELSE. Ponownie, ten blok poleceń jest analizowany normalnie.
      • Bloki poleceń Prawda i Fałsz nie przechodzą automatycznie do kolejnych faz. Ich dalsze przetwarzanie jest kontrolowane przez fazę 7.
    • FOR jest podzielone na dwie części po DO. Błąd składniowy w konstrukcji FOR spowoduje fatalny błąd składniowy.
      • Część przez DO jest rzeczywistym poleceniem iteracji FOR, które przepływa przez całą fazę 7
        • Wszystkie opcje FOR są w pełni analizowane w fazie 2.
        • Klauzula IN w nawiasach jest traktowana <LF>jako <space>. Po przeanalizowaniu klauzuli IN wszystkie tokeny są łączone w celu utworzenia pojedynczego tokenu.
        • Kolejne separatory znaczników bez znaku zmiany znaczenia / bez cudzysłowu zwijają się w jedno miejsce w całym poleceniu FOR poprzez DO.
      • Część po DO to blok poleceń, który jest analizowany normalnie. Późniejsze przetwarzanie bloku poleceń DO jest kontrolowane przez iterację w fazie 7.
    • REM wykryty w fazie 2 jest traktowany dramatycznie inaczej niż wszystkie inne polecenia.
      • Tylko jeden token argumentu jest analizowany - parser ignoruje znaki po pierwszym tokenie argumentu.
      • Polecenie REM może pojawić się na wyjściu fazy 3, ale polecenie nigdy nie jest wykonywane, a oryginalny tekst argumentu jest powtarzany - znaki ucieczki nie są usuwane, z wyjątkiem ...
        • Jeśli istnieje tylko jeden token argumentu, który kończy się znakiem bez zmiany znaczenia, ^który kończy wiersz, token argumentu jest odrzucany, a kolejny wiersz jest analizowany i dołączany do REM. Powtarza się, dopóki nie będzie więcej niż jeden żeton lub nie będzie ostatniego znaku ^.
  • Jeśli żeton polecenia zaczyna się od :i jest to pierwsza runda fazy 2 (nie jest to restart z powodu CALL w fazie 6), to
    • Token jest zwykle traktowany jako niezrealizowana etykieta .
      • Pozostała część linii jest analizowany jednak ), <, >, &i |nie ma specjalnego znaczenia. Cała pozostała część wiersza jest traktowana jako część etykiety „polecenie”.
      • ^Nadal jest wyjątkowy, co oznacza, że linia kontynuacja może być używany do dołączania późniejszą linię do etykiety.
      • Niezrealizowanej Label w nawiasach bloku spowoduje śmiertelny błąd składni, chyba że następuje bezpośrednio poleceniem lub Wykonane Etykieta w następnym wierszu.
        • (nie ma już specjalnego znaczenia dla pierwszego polecenia, które następuje po niewykonanej etykiecie .
      • Polecenie jest przerywane po zakończeniu analizowania etykiet. Kolejne fazy nie odbywają się w przypadku etykiety
    • Istnieją trzy wyjątki, które mogą spowodować, że etykieta znaleziona w fazie 2 będzie traktowana jako etykieta wykonana, która kontynuuje analizę w fazie 7.
      • Jest przekierowania, które poprzedza etykieta pozornych i jest |rura lub &, &&czy ||polecenie konkatenacji na linii.
      • Istnieje przekierowanie, które poprzedza token etykiety, a polecenie znajduje się w bloku w nawiasach.
      • Token etykiety jest pierwszym poleceniem w wierszu w bloku umieszczonym w nawiasach, a powyższy wiersz kończy się niewykonaną etykietą .
    • Poniższe zdarzenia mają miejsce, gdy wykonywana etykieta zostanie odkryta w fazie 2
      • Etykieta, jej argumenty i przekierowanie są wyłączone z jakiegokolwiek wyjścia echa w fazie 3
      • Wszelkie kolejne połączone polecenia w wierszu są w pełni analizowane i wykonywane.
    • Więcej informacji na temat wykonanych etykiet i niezrealizowanych etykiet można znaleźć na stronie https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Faza 3) Powtórz przeanalizowane polecenie (polecenia) Tylko wtedy, gdy blok poleceń nie rozpoczął się @, a funkcja ECHO była WŁĄCZONA na początku poprzedniego kroku.

Faza 4) %XRozszerzenie zmiennej FOR : Tylko wtedy, gdy polecenie FOR jest aktywne i polecenia po DO są przetwarzane.

  • W tym momencie faza 1 przetwarzania wsadowego będzie już konwertować zmienną FOR, taką jak %%Xna %X. W wierszu poleceń obowiązują różne reguły rozszerzania procentowego dla fazy 1. Jest to powód, dla którego w wierszach poleceń używane są zmienne FOR, %Xale pliki wsadowe %%X.
  • W nazwach zmiennych FOR rozróżniana jest wielkość liter, ale ~modifiersnie.
  • ~modifiersmają pierwszeństwo przed nazwami zmiennych. Jeśli następujący po nim znak ~jest zarówno modyfikatorem, jak i prawidłową nazwą zmiennej FOR, a istnieje kolejny znak, który jest aktywną nazwą zmiennej FOR, to znak jest interpretowany jako modyfikator.
  • Nazwy zmiennych FOR są globalne, ale tylko w kontekście klauzuli DO. Jeśli procedura jest wywoływana z klauzuli FOR DO, wówczas zmienne FOR nie są rozwijane w ramach procedury CALLed. Ale jeśli procedura ma swoje własne polecenie FOR, wówczas wszystkie aktualnie zdefiniowane zmienne FOR są dostępne dla wewnętrznych poleceń DO.
  • Nazwy zmiennych FOR mogą być ponownie używane w zagnieżdżonych FORach. Wewnętrzna wartość FOR ma pierwszeństwo, ale po zamknięciu INNER FOR przywracana jest zewnętrzna wartość FOR.
  • Jeśli ECHO było włączone na początku tej fazy, to faza 3) jest powtarzana, aby pokazać przeanalizowane polecenia DO po rozwinięciu zmiennych FOR.

---- Od tego momentu każde polecenie zidentyfikowane w fazie 2 jest przetwarzane oddzielnie.
---- Fazy od 5 do 7 są zakończone dla jednej komendy przed przejściem do następnej.

Faza 5) Opóźnione rozwinięcie: Tylko jeśli opóźnione rozwijanie jest włączone, polecenie nie znajduje się w bloku w nawiasach po żadnej stronie potoku , a polecenie nie jest „nagim” skryptem wsadowym (nazwa skryptu bez nawiasów, CALL, konkatenacja poleceń, lub rura).

  • Każdy token polecenia jest niezależnie analizowany pod kątem opóźnionego rozwinięcia.
    • Większość poleceń analizuje dwa lub więcej tokenów - token polecenia, token argumentów i każdy token miejsca docelowego przekierowania.
    • Polecenie FOR analizuje tylko token klauzuli IN.
    • Polecenie JEŻELI analizuje tylko wartości porównania - jedną lub dwie, w zależności od operatora porównania.
  • Dla każdego przeanalizowanego tokenu najpierw sprawdź, czy jakikolwiek zawiera !. Jeśli nie, to token nie jest analizowany - ważne dla ^postaci. Jeśli token zawiera !, zeskanuj każdy znak od lewej do prawej:
    • Jeśli jest to daszek ( ^), następny znak nie ma specjalnego znaczenia, sam daszek jest usuwany
    • Jeśli jest to wykrzyknik, poszukaj następnego wykrzyknika (daszki nie są już obserwowane), rozwiń do wartości zmiennej.
      • Kolejne otwarcia !są zwinięte w jeden!
      • Wszelkie pozostałe niesparowane !zostaną usunięte
    • Rozszerzanie zmiennych na tym etapie jest „bezpieczne”, ponieważ znaki specjalne nie są już wykrywane (nawet <CR>lub <LF>)
    • Aby uzyskać pełniejsze wyjaśnienie, przeczytaj drugą połowę tego z tego samego wątku dbenham - Exclamation Point Phase

Faza 5.3) Przetwarzanie potoku : tylko wtedy, gdy polecenia znajdują się po obu stronach potoku
Każda strona potoku jest przetwarzana niezależnie i asynchronicznie.

  • Jeśli polecenie jest wewnętrzne dla cmd.exe, jest to plik wsadowy lub jeśli jest to blok poleceń w nawiasach, to jest wykonywane w nowym wątku cmd.exe przez %comspec% /S /D /c" commandBlock" , więc blok poleceń otrzymuje ponownie fazę, ale tym razem w trybie wiersza poleceń.
    • Jeśli blok poleceń ujętych w nawiasy, wszystkie <LF>z poleceniem przed i po są konwertowane na <space>&. Inni <LF>są pozbawieni.
  • To jest koniec przetwarzania poleceń potoku.
  • Zobacz Dlaczego opóźnione rozwinięcie kończy się niepowodzeniem, gdy znajduje się w bloku kodu potokowym? aby uzyskać więcej informacji na temat analizy i przetwarzania potoków

Faza 5.5) Wykonywanie przekierowania: Każde przekierowanie wykryte w fazie 2 jest teraz wykonywane.

Faza 6) Przetwarzanie CALL / podwajanie Caret: Tylko jeśli token polecenia to CALL lub jeśli tekst przed pierwszym występującym standardowym ogranicznikiem tokena to CALL. Jeśli CALL jest analizowany z większego tokenu polecenia, to nieużywana część jest dodawana do tokenu argumentów przed kontynuowaniem.

  • Przeskanuj token argumentów w poszukiwaniu niecytowanego /?. Jeśli zostanie znaleziony w dowolnym miejscu na żetonach, przerwij fazę 6 i przejdź do fazy 7, gdzie zostanie wydrukowana POMOC dla WEZWANIA.
  • Usuń pierwszą CALL, aby można było ułożyć wiele połączeń CALL
  • Podwój wszystkie karety
  • Zrestartuj fazy 1, 1.5 i 2, ale nie kontynuuj do fazy 3
    • Wszelkie podwójne karetki są redukowane z powrotem do jednego daszka, o ile nie są cytowane. Ale niestety cytowane karetki pozostają podwojone.
    • Faza 1 trochę się zmienia
      • Błędy rozszerzenia w kroku 1.2 lub 1.3 przerywają wywołanie, ale błąd nie jest krytyczny - przetwarzanie wsadowe jest kontynuowane.
    • Zadania fazy 2 zostały nieco zmienione
      • Każde nowo pojawiające się przekierowanie bez cytowania, bez zmiany znaczenia, które nie zostało wykryte w pierwszej rundzie fazy 2, jest wykrywane, ale jest usuwane (łącznie z nazwą pliku) bez faktycznego wykonywania przekierowania
      • Każdy nowo pojawiający się niecytowany, nieskrócony daszek na końcu wiersza jest usuwany bez wykonywania kontynuacji wiersza
      • CALL jest przerywane bez błędu, jeśli zostanie wykryta którakolwiek z poniższych sytuacji
        • Nowo pojawiające się bez cytowania, bez zmiany znaczenia &lub|
        • Wynikowy token polecenia zaczyna się od niecytowanego, bez znaku zmiany znaczenia (
        • Pierwszy token po usuniętym CALL zaczął się od @
      • Jeśli wynikowe polecenie jest pozornie prawidłowym IF lub FOR, to wykonanie zakończy się niepowodzeniem z błędem stwierdzającym, że IFlubFOR nie jest rozpoznawane jako polecenie wewnętrzne lub zewnętrzne.
      • Oczywiście CALL nie jest przerywany w tej drugiej rundzie fazy 2, jeśli wynikowy token polecenia jest etykietą zaczynającą się od :.
  • Jeśli wynikowym tokenem polecenia jest CALL, zrestartuj fazę 6 (powtarza się, aż nie będzie więcej CALL)
  • Jeśli wynikowym tokenem polecenia jest skrypt wsadowy lub etykieta:, wówczas wykonanie CALL jest w pełni obsługiwane przez pozostałą część Fazy 6.
    • Umieść bieżącą pozycję pliku skryptów wsadowych na stosie wywołań, aby wykonanie mogło zostać wznowione z właściwej pozycji po zakończeniu CALL.
    • Skonfiguruj tokeny argumentów% 0,% 1,% 2, ...% N i% * dla CALL, używając wszystkich wynikowych tokenów
    • Jeśli token polecenia jest etykietą rozpoczynającą się od : , to
      • Uruchom ponownie fazę 5. Może to mieć wpływ na to, co: etykieta jest WYWOŁANA. Ale ponieważ tokeny% 0 itd. Zostały już ustawione, nie zmieni to argumentów, które są przekazywane do procedury CALLed.
      • Wykonaj etykietę GOTO, aby ustawić wskaźnik pliku na początku podprogramu (zignoruj ​​inne tokeny, które mogą występować po: etykiecie). Zobacz Faza 7, aby zapoznać się z zasadami działania GOTO.
    • W przeciwnym razie przenieś kontrolę do określonego skryptu wsadowego.
    • Wykonywanie etykiety lub skryptu CALLed: jest kontynuowane do momentu osiągnięcia EXIT / B lub końca pliku, w którym to momencie stos CALL jest zdejmowany i wykonywanie jest wznawiane od zapisanej pozycji pliku.
      Faza 7 nie jest wykonywana dla skryptów CALLed lub: etykiet.
  • W przeciwnym razie wynik fazy 6 przechodzi do fazy 7 w celu wykonania.

Faza 7) Wykonaj: Polecenie jest wykonywane

  • 7.1 - Wykonaj polecenie wewnętrzne - jeśli token polecenia jest umieszczony w cudzysłowie, pomiń ten krok. W przeciwnym razie spróbuj przeanalizować wewnętrzne polecenie i wykonać.
    • Poniższe testy są wykonywane w celu ustalenia, czy niecytowany token polecenia reprezentuje polecenie wewnętrzne:
      • Jeśli token polecenia dokładnie pasuje do polecenia wewnętrznego, wykonaj je.
      • W przeciwnym razie podziel token polecenia przed pierwszym wystąpieniem + / [ ] <space> <tab> , ;lub =
        Jeśli poprzedzający tekst jest poleceniem wewnętrznym, zapamiętaj to polecenie
        • Jeśli w trybie wiersza poleceń lub jeśli polecenie pochodzi z bloku w nawiasach, jeśli prawda lub fałsz, blok poleceń, blok polecenia FOR DO lub jest związane z konkatenacją poleceń, wykonaj polecenie wewnętrzne
        • W przeciwnym razie (musi być samodzielnym poleceniem w trybie wsadowym) przeskanuj bieżący folder i ŚCIEŻKĘ w poszukiwaniu pliku .COM, .EXE, .BAT lub .CMD, którego nazwa podstawowa jest zgodna z oryginalnym tokenem polecenia
          • Jeśli pierwszy pasujący plik to .BAT lub .CMD, przejdź do 7.3.exec i wykonaj ten skrypt
          • W przeciwnym razie (nie znaleziono dopasowania lub pierwsze dopasowanie to .EXE lub .COM) wykonaj zapamiętane polecenie wewnętrzne
      • W przeciwnym razie podziel token polecenia przed pierwszym wystąpieniem . \lub :
        Jeśli poprzedzający tekst nie jest poleceniem wewnętrznym, to goto 7.2 W
        przeciwnym razie poprzedzający tekst może być poleceniem wewnętrznym. Zapamiętaj to polecenie.
      • Złam token polecenia przed pierwszym wystąpieniem + / [ ] <space> <tab> , ;lub =
        Jeśli poprzedzający tekst jest ścieżką do istniejącego pliku, przejdź do 7.2 W przeciwnym razie
        wykonaj zapamiętane polecenie wewnętrzne.
    • Jeśli polecenie wewnętrzne jest analizowane z większego tokenu polecenia, nieużywana część tokenu polecenia jest uwzględniana na liście argumentów
    • Tylko dlatego, że token polecenia jest analizowany jako polecenie wewnętrzne, nie oznacza, że ​​zostanie wykonany pomyślnie. Każde polecenie wewnętrzne ma własne zasady dotyczące sposobu analizowania argumentów i opcji oraz dozwolonej składni.
    • Wszystkie polecenia wewnętrzne, jeśli /?zostaną wykryte, wyświetlą pomoc zamiast wykonywać swoją funkcję . Większość rozpoznaje, /?czy pojawia się gdziekolwiek w argumentach. Ale kilka poleceń, takich jak ECHO i SET, wyświetla pomoc tylko wtedy, gdy pierwszy argument token zaczyna się od /?.
    • SET ma kilka interesujących semantyki:
      • Jeśli polecenie SET ma cudzysłów przed nazwą zmiennej i włączonymi rozszerzeniami
        set "name=content" ignored -> wartość =content
        to tekst między pierwszym znakiem równości a ostatnim cudzysłowem jest używany jako treść (z wyłączeniem pierwszego równego i ostatniego cudzysłowu). Tekst po ostatnim cudzysłowie jest ignorowany. Jeśli po znaku równości nie ma cudzysłowu, to reszta wiersza jest używana jako treść.
      • Jeśli polecenie SET nie ma cudzysłowu przed nazwą
        set name="content" not ignored -> wartość =, "content" not ignored
        cała pozostała część wiersza po równości jest używana jako treść, w tym wszelkie możliwe cudzysłowy.
    • Oceniane jest porównanie JEŻELI iw zależności od tego, czy warunek jest prawdziwy, czy fałszywy, przetwarzany jest odpowiedni już przeanalizowany zależny blok poleceń, zaczynając od fazy 5.
    • Klauzula IN polecenia FOR jest odpowiednio iterowana.
      • Jeśli jest to FOR / F, który iteruje wyjście bloku poleceń, to:
        • Klauzula IN jest wykonywana w nowym procesie cmd.exe za pośrednictwem CMD / C.
        • Blok poleceń musi przejść przez cały proces analizy po raz drugi, ale tym razem w kontekście wiersza poleceń
        • ECHO zacznie działać WŁĄCZONY, a opóźnione rozszerzanie będzie zwykle wyłączane (w zależności od ustawienia rejestru)
        • Wszystkie zmiany środowiska wprowadzone przez blok poleceń klauzuli IN zostaną utracone po zakończeniu procesu potomnego cmd.exe
      • Dla każdej iteracji:
        • Zdefiniowano wartości zmiennych FOR
        • Już przeanalizowany blok poleceń DO jest następnie przetwarzany, zaczynając od fazy 4.
    • GOTO używa następującej logiki do zlokalizowania: etykieta
      • Etykieta jest analizowana z pierwszego tokenu argumentu
      • Skrypt jest skanowany pod kątem następnego wystąpienia etykiety
        • Skanowanie rozpocznie się od aktualnej pozycji pliku
        • Po osiągnięciu końca pliku skanowanie wraca do początku pliku i jest kontynuowane do pierwotnego punktu początkowego.
      • Skanowanie zatrzymuje się przy pierwszym wystąpieniu znalezionej etykiety, a wskaźnik pliku jest ustawiany na linię znajdującą się bezpośrednio po etykiecie. Wykonywanie skryptu jest wznawiane od tego momentu. Zauważ, że udane prawdziwe GOTO natychmiast przerwie każdy przeanalizowany blok kodu, w tym pętle FOR.
      • Jeśli etykieta nie zostanie znaleziona lub brakuje tokenu etykiety, operacja GOTO kończy się niepowodzeniem, drukowany jest komunikat o błędzie, a stos wywołań jest przerywany. To skutecznie działa jako EXIT / B, z wyjątkiem wszystkich już przeanalizowanych poleceń w bieżącym bloku poleceń, które następują po GOTO, są nadal wykonywane, ale w kontekście CALLer (kontekst, który istnieje po EXIT / B)
      • Zobacz https://www.dostips.com/forum/viewtopic.php?f=3&t=3803, aby uzyskać dokładniejszy opis reguł używanych do analizowania etykiet.
    • Zarówno RENAME, jak i COPY akceptują symbole wieloznaczne dla ścieżki źródłowej i docelowej. Ale Microsoft wykonuje okropną robotę dokumentując, jak działają symbole wieloznaczne, szczególnie w przypadku ścieżki docelowej. Przydatny zestaw reguł dotyczących symboli wieloznacznych można znaleźć w artykule Jak polecenie RENAME systemu Windows interpretuje symbole wieloznaczne?
  • 7.2 - Wykonaj zmianę głośności - W przeciwnym razie, jeśli token polecenia nie zaczyna się od cudzysłowu, ma dokładnie dwa znaki, a drugi znak to dwukropek, zmień głośność
    • Wszystkie tokeny argumentów są ignorowane
    • Jeśli nie można znaleźć woluminu określonego przez pierwszy znak, przerwij działanie z błędem
    • Token polecenia ::zawsze spowoduje błąd, chyba że SUBST jest używany do zdefiniowania woluminu dla. ::
      Jeśli SUBST jest używany do zdefiniowania woluminu dla ::, to wolumin zostanie zmieniony i nie będzie traktowany jako etykieta.
  • 7.3 - Wykonaj polecenie zewnętrzne - w przeciwnym razie spróbuj traktować polecenie jako polecenie zewnętrzne.
    • Jeśli w trybie linii dowodzenia i sterowania nie jest podane, a nie rozpoczyna specyfikacji objętościowego białego przestrzeni, ,, ;, =i +następnie przerwać polecenie Token na pierwsze wystąpienie <space> , ;lub =i poprzedzić resztę do argumentu tokenu (s).
    • Jeśli drugim znakiem tokenu polecenia jest dwukropek, sprawdź, czy można znaleźć wolumin określony przez pierwszy znak.
      Jeśli nie można znaleźć woluminu, przerwij z błędem.
    • Jeśli w trybie wsadowym token polecenia zaczyna się od :, to goto 7.4
      Zauważ, że jeśli token etykiety zaczyna się od ::, to nie zostanie osiągnięty, ponieważ poprzedni krok zostanie przerwany z błędem, chyba że SUBST zostanie użyty do zdefiniowania woluminu dla ::.
    • Zidentyfikuj polecenie zewnętrzne do wykonania.
      • Jest to złożony proces, który może obejmować bieżący wolumin, bieżący katalog, zmienną PATH, zmienną PATHEXT i / lub skojarzenia plików.
      • Jeśli nie można zidentyfikować prawidłowego polecenia zewnętrznego, przerwij z błędem.
    • Jeśli w trybie wiersza poleceń, a token polecenia zaczyna się od :, to goto 7.4
      Zauważ, że rzadko jest to osiągane, ponieważ poprzedni krok zostanie przerwany z błędem, chyba że token polecenia zaczyna się od ::, a SUBST jest używany do zdefiniowania woluminu dla:: , a cały token polecenia jest prawidłową ścieżką do polecenia zewnętrznego.
    • 7.3.exec - Wykonaj polecenie zewnętrzne.
  • 7.4 - Ignoruj ​​etykietę - Zignoruj ​​polecenie i wszystkie jego argumenty, jeśli token polecenia zaczyna się od :.
    Zasady zawarte w 7.2 i 7.3 mogą uniemożliwić etykiecie osiągnięcie tego punktu.

Parser wiersza poleceń:

Działa jak BatchLine-Parser, z wyjątkiem:

Faza 1) Procentowa ekspansja:

  • Nie %*, %1itp. Rozwijanie argumentów
  • Jeśli var jest niezdefiniowane, to %var%pozostaje niezmieniona.
  • Brak specjalnego traktowania %%. Jeśli var = content, to %%var%%rozwija się do %content%.

Faza 3) Powtórz przeanalizowane polecenie (a)

  • Nie jest to wykonywane po fazie 2. Jest wykonywane tylko po fazie 4 dla bloku poleceń FOR DO.

Faza 5) Opóźniona rozbudowa: tylko wtedy, gdy włączona jest opcja Opóźniona rozbudowa

  • Jeśli zmienna jest niezdefiniowana, !var!pozostaje niezmieniona.

Faza 7) Wykonaj polecenie

  • Próby CALL lub GOTO a: etykieta powodują błąd.
  • Jak już udokumentowano w fazie 7, wykonana etykieta może spowodować błąd w różnych scenariuszach.
    • Etykiety wykonywane wsadowo mogą powodować błąd tylko wtedy, gdy zaczynają się od ::
    • Etykiety wykonane w wierszu poleceń prawie zawsze powodują błąd

Parsowanie wartości całkowitych

Istnieje wiele różnych kontekstów, w których cmd.exe analizuje wartości całkowite z ciągów, a reguły są niespójne:

  • SET /A
  • IF
  • %var:~n,m% (rozwinięcie zmiennej podciąg)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Szczegóły dotyczące tych reguł można znaleźć w sekcji Zasady dotyczące sposobu analizowania liczb przez program CMD.EXE


Dla każdego, kto chce ulepszyć reguły parsowania cmd.exe, na forum DosTips znajduje się temat do dyskusji, w którym można zgłaszać problemy i proponować.

Mam nadzieję, że to pomaga
Jan Erik (jeb) - Oryginalny autor i odkrywca faz
Dave Benham (dbenham) - Wiele dodatkowych treści i edycji


4
Cześć jeb, dziękuję za wgląd… Może to być trudne do zrozumienia, ale spróbuję to przemyśleć! Wygląda na to, że wykonałeś wiele testów! Dziękuję za przetłumaczenie ( administrator.de/… )
Benoit

2
Faza wsadowa 5) - %% a zostanie już zmieniony na% a w fazie 1, więc rozszerzenie pętli for naprawdę rozszerza% a. Dodałem również bardziej szczegółowe wyjaśnienie fazy 1 partii w odpowiedzi poniżej (nie mam uprawnień do edycji)
dbenham

3
Jeb - być może fazę 0 można by przenieść i połączyć z fazą 6? To ma dla mnie więcej sensu, czy też jest powód, dla którego są tak rozdzieleni?
dbenham

1
@aschipfl - zaktualizowałem tę sekcję. )Naprawdę funkcję prawie jak REMkomendy, gdy licznik nawias 0. Spróbuj zarówno tych z wiersza polecenia: ) Ignore thisiecho OK & ) Ignore this
dbenham

1
@aschipfl tak, zgadza się, dlatego czasami widzisz 'set "var =% expr%"! 'ostatni wykrzyknik zostanie usunięty, ale wymusza fazę 5
jeb

62

Podczas wywoływania polecenia z okna poleceń, tokenizacja argumentów wiersza poleceń nie jest wykonywana przez cmd.exe(inaczej „powłokę”). Najczęściej tokenizacja jest wykonywana przez środowisko uruchomieniowe C / C ++ nowo utworzonych procesów, ale niekoniecznie tak jest - na przykład, jeśli nowy proces nie został napisany w C / C ++ lub jeśli nowy proces zdecyduje się zignorować argvi przetworzyć surowe polecenie dla siebie (np. z GetCommandLine ()). Na poziomie systemu operacyjnego Windows przekazuje wiersze poleceń pozbawione tokenów jako pojedynczy ciąg do nowych procesów. Jest to przeciwieństwo większości powłok * nix, w których powłoka tokenizuje argumenty w spójny, przewidywalny sposób przed przekazaniem ich do nowo utworzonego procesu. Wszystko to oznacza, że ​​możesz doświadczyć bardzo rozbieżnych zachowań tokenizacji argumentów w różnych programach w systemie Windows, ponieważ poszczególne programy często biorą tokenizację argumentów w swoje ręce.

Jeśli to brzmi jak anarchia, to w pewnym sensie tak jest. Jednakże, ponieważ duża liczba programów Windows zrobić wykorzystują Microsoft C / C ++ Runtime argvmoże być ogólnie przydatne do zrozumienia , w jaki sposób MSVCRT tokenizes argumenty. Oto fragment:

  • Argumenty są rozdzielane spacją, która jest spacją lub tabulatorem.
  • Ciąg otoczony podwójnymi cudzysłowami jest interpretowany jako pojedynczy argument, niezależnie od znajdującego się w nim spacji. Łańcuch w cudzysłowie może być osadzony w argumencie. Zauważ, że daszek (^) nie jest rozpoznawany jako znak zmiany znaczenia ani separator.
  • Podwójny cudzysłów poprzedzony lewym ukośnikiem \ "jest interpretowany jako dosłowny podwójny cudzysłów (").
  • Ukośniki odwrotne są interpretowane dosłownie, chyba że bezpośrednio poprzedzają podwójny cudzysłów.
  • Jeśli po parzystej liczbie ukośników odwrotnych następuje podwójny cudzysłów, to jeden ukośnik odwrotny () jest umieszczany w tablicy argv na każdą parę ukośników odwrotnych (\), a podwójny cudzysłów (") jest interpretowany jako separator ciągu.
  • Jeśli po nieparzystej liczbie ukośników odwrotnych następuje podwójny cudzysłów, to jeden ukośnik odwrotny () jest umieszczany w tablicy argv na każdą parę ukośników odwrotnych (\), a znak podwójnego cudzysłowu jest interpretowany jako sekwencja ucieczki przez pozostały ukośnik odwrotny, powodując dosłowny podwójny cudzysłów (") do umieszczenia w argv.

„Język wsadowy” firmy Microsoft ( .bat) nie jest wyjątkiem w tym anarchicznym środowisku i opracował własne unikalne reguły tokenizacji i ucieczki. Wygląda również na to, że wiersz poleceń cmd.exe wykonuje pewne wstępne przetwarzanie argumentu wiersza poleceń (głównie w celu podstawienia zmiennych i ucieczki) przed przekazaniem argumentu do nowo wykonywanego procesu. Możesz przeczytać więcej o niskopoziomowych szczegółach języka wsadowego i ucieczce cmd w doskonałych odpowiedziach jeb i dbenham na tej stronie.


Zbudujmy proste narzędzie wiersza poleceń w C i zobaczmy, co mówi o twoich przypadkach testowych:

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

(Uwagi: argv [0] jest zawsze nazwą pliku wykonywalnego i jest pomijane poniżej ze względu na zwięzłość. Testowane w systemie Windows XP SP3. Skompilowane za pomocą programu 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]

I kilka moich własnych testów:

> 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]

Dziękuję za Twoją odpowiedź. Jeszcze bardziej intryguje mnie fakt, że TinyPerl nie wypisze tego, co generuje twój program, i mam trudności ze zrozumieniem, jak [a "b" c]mogłoby się stać [a "b] [c]przetwarzanie końcowe .
Benoit

Teraz, kiedy o tym myślę, ta tokenizacja wiersza poleceń jest prawdopodobnie wykonywana w całości przez środowisko wykonawcze C. Plik wykonywalny mógłby zostać napisany w taki sposób, że nie używa nawet środowiska uruchomieniowego C, w którym to przypadku myślę, że musiałby zajmować się dosłownie wierszem poleceń i byłby odpowiedzialny za wykonanie własnej tokenizacji (gdyby chciał). Lub nawet jeśli twoja aplikacja używa środowiska uruchomieniowego C, możesz zignorować argc i argv i po prostu pobrać surową linię poleceń za pośrednictwem np GetCommandLine. Win32 . Być może TinyPerl ignoruje argv i po prostu tokenizuje surową linię poleceń według własnych reguł.
Mike Clark

4
„Pamiętaj, że z punktu widzenia Win32 wiersz poleceń jest po prostu ciągiem, który jest kopiowany do przestrzeni adresowej nowego procesu. Sposób, w jaki proces uruchamiania i nowy proces interpretują ten ciąg, nie jest określony przez reguły, ale przez konwencję”. -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark

2
Dziękuję za naprawdę miłą odpowiedź. To moim zdaniem wiele wyjaśnia. To wyjaśnia również, dlaczego czasami praca z systemem Windows jest naprawdę kiepska…
Benoit

Znalazłem to w odniesieniu do odwrotnych ukośników i cudzysłowów podczas transformacji z wiersza poleceń do argv, dla programów Win32 C ++. Liczba ukośników odwrotnych jest dzielona przez dwa tylko wtedy, gdy po ostatnim ukośniku odwrotnym następuje dwukrotny cudzysłów, a znak dblquote kończy ciąg, jeśli przed nim znajduje się parzysta liczba ukośników odwrotnych.
Benoit

47

Zasady rozszerzenia procentowego

Oto rozszerzone wyjaśnienie Fazy 1 w odpowiedzi jeba (ważne zarówno dla trybu wsadowego, jak i trybu wiersza poleceń).

Faza 1) Ekspansja procentowa Zaczynając od lewej strony, przeskanuj każdy znak w poszukiwaniu %lub <LF>. Jeśli zostanie znaleziony

  • 1.05 (obetnij linię w <LF>)
    • Jeśli postać jest <LF> wtedy
      • Drop (ignoruj) resztę linii z <LF>dalszą
      • Goto Phase 1.5 (Strip <CR>)
    • W przeciwnym razie postać musi być %, więc przejdź do 1.1
  • 1.1 (escape %) pomijane w trybie wiersza poleceń
    • Jeśli trybie wsadowym, a następnie kolejna %następnie
      wymienić %%z pojedynczym %i kontynuować skanowanie
  • 1.2 (argument rozwinięcia) pominięty w trybie wiersza poleceń
    • W przeciwnym razie, jeśli tryb wsadowy to
      • Jeśli następuje po nim *i rozszerzenia poleceń są włączone, wówczas
        Zamień %*na tekst wszystkich argumentów wiersza poleceń (Zamień na nic, jeśli nie ma żadnych argumentów) i kontynuuj skanowanie.
      • Else if następuje <digit>wtedy
        wymienić%<digit> o wartości argumentu (zastąpić niczym jeśli niezdefiniowany) i kontynuować skanowanie.
      • W przeciwnym razie, jeśli następuje po nim ~i rozszerzenia poleceń są wtedy włączone
        • Jeśli po nim następuje opcjonalna poprawna lista modyfikatorów argumentów, po której następuje wymagane, <digit>to
          Zamień na %~[modifiers]<digit>zmodyfikowaną wartość argumentu (zamień na nic, jeśli nie jest zdefiniowany lub jeśli określono $ PATH: modyfikator nie jest zdefiniowany) i kontynuuj skanowanie.
          Uwaga: modyfikatory nie uwzględniają wielkości liter i mogą pojawiać się wiele razy w dowolnej kolejności, z wyjątkiem $ PATH: modyfikator może pojawić się tylko raz i musi być ostatnim modyfikatorem przed<digit>
        • W przeciwnym razie niepoprawna zmodyfikowana składnia argumentu powoduje błąd krytyczny: wszystkie przeanalizowane polecenia są przerywane, a przetwarzanie wsadowe jest przerywane, jeśli jest w trybie wsadowym!
  • 1.3 (zmienna rozwijana)
    • W przeciwnym razie, jeśli rozszerzenia poleceń są wyłączone,
      spójrz na następny ciąg znaków, łamiąc przed %lub koniec bufora i nazwij je VAR (może to być pusta lista)
      • Jeśli następny znak jest %następnie
        • Jeśli zdefiniowano WARIANCJA,
          zamień na %VAR%wartość WARIANCJA i kontynuuj skanowanie
        • W przeciwnym razie, jeśli tryb wsadowy, następnie
          Usuń %VAR%i kontynuuj skanowanie
        • Inaczej goto 1.4
      • Inaczej goto 1.4
    • W przeciwnym razie, jeśli rozszerzenia poleceń są włączone,
      spójrz na następny ciąg znaków, łamiąc przed % :lub koniec bufora i nazwij je VAR (może to być pusta lista). Jeśli WARIANCJA zostanie przerwana przed, :a kolejny znak zostanie %dołączony :jako ostatni znak zmiennej WARIANCJA i przerwij przed %.
      • Jeśli następny znak jest %następnie
        • Jeśli zdefiniowano WARIANCJA, wówczas
          Zamień%VAR% wartość WARIANCJA i kontynuuj skanowanie
        • W przeciwnym razie, jeśli tryb wsadowy, następnie
          Usuń %VAR%i kontynuuj skanowanie
        • Inaczej goto 1.4
      • W przeciwnym razie, jeśli następny znak to :wtedy
        • Jeśli WARIANCJA jest niezdefiniowana, to
          • W trybie wsadowym
            Usuń %VAR:i kontynuuj skanowanie.
          • Inaczej goto 1.4
        • W przeciwnym razie, jeśli następny znak to ~wtedy
          • Jeśli następny ciąg znaków pasuje do wzorca [integer][,[integer]]%to
            Zastąp %VAR:~[integer][,[integer]]%podciągiem wartości VAR (co może skutkować pustym ciągiem) i kontynuuj skanowanie.
          • Inaczej goto 1.4
        • Else if a następnie =lub *=następnie
          wyszukiwania Nieprawidłowy zmienna i zastąpić składni podnosi błąd krytyczny: Wszystkie analizowane polecenia są przerywane, i przerywa przetwarzania wsadowego gdyby w trybie wsadowym!
        • W przeciwnym razie, jeśli następny ciąg znaków pasuje do wzorca [*]search=[replace]%, gdzie wyszukiwanie może obejmować dowolny zestaw znaków z wyjątkiem =, a zamiana może obejmować dowolny zestaw znaków z wyjątkiem %, a następnie
          Zamień%VAR:[*]search=[replace]% wartość VAR po wykonaniu wyszukiwania i zamiany (prawdopodobnie skutkując pustym ciągiem) i kontynuuj skanowanie
        • Inaczej goto 1.4
  • 1,4 (pasek%)
    • W przeciwnym razie Jeśli tryb wsadowy, następnie
      Usuń %i kontynuuj skanowanie, zaczynając od następnego znaku po%
    • W przeciwnym razie zachowaj interlinię %i kontynuuj skanowanie, zaczynając od następnego znaku po zachowanej interlinii%

Powyższe pomaga wyjaśnić, dlaczego ta partia

@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

Daje następujące wyniki:

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

Uwaga 1 - Faza 1 następuje przed uznaniem instrukcji REM. Jest to bardzo ważne, ponieważ oznacza, że ​​nawet uwaga może generować błąd krytyczny, jeśli ma nieprawidłową składnię rozwijania argumentów lub nieprawidłowe wyszukiwanie zmiennych i zastępowanie składni!

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

Uwaga 2 - Kolejna interesująca konsekwencja reguł parsowania%: Zmienne zawierające: w nazwie można zdefiniować, ale nie można ich rozwinąć, chyba że zostaną wyłączone rozszerzenia poleceń. Jest jeden wyjątek - nazwę zmiennej zawierającą pojedynczy dwukropek na końcu można rozwinąć, gdy rozszerzenia poleceń są włączone. Nie można jednak wykonywać podciągów ani operacji wyszukiwania i zamiany na nazwach zmiennych kończących się dwukropkiem. Poniższy plik wsadowy (dzięki uprzejmości jeb) demonstruje takie zachowanie

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

Uwaga 3 - Interesujący wynik kolejności reguł analizowania, które jeb przedstawia w swoim poście: Podczas wykonywania funkcji znajdź i zamień z opóźnionym rozszerzaniem, znaki specjalne zarówno w terminach znajdź i zamień muszą zostać pominięte lub zacytowane. Ale sytuacja jest inna w przypadku ekspansji procentowej - nie można pominąć terminu znajdującego (chociaż można go cytować). Łańcuch zamiany procentu może, ale nie musi, wymagać zmiany znaczenia lub cudzysłowu, w zależności od intencji.

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

Zasady opóźnionego rozszerzenia

Oto rozszerzone i dokładniejsze wyjaśnienie fazy 5 w odpowiedzi jeb (ważne zarówno dla trybu wsadowego, jak i trybu wiersza poleceń)

Faza 5) Opóźniona ekspansja

Ta faza jest pomijana, jeśli zachodzi którykolwiek z poniższych warunków:

  • Opóźnione rozszerzenie jest wyłączone.
  • Polecenie znajduje się w bloku umieszczonym w nawiasach po obu stronach rury.
  • Przychodzące polecenie znacznik jest „nagą” skrypt wsadowego, co oznacza, że nie jest związane z CALL, w nawiasach, każdy blok postać poleceń (łączenie &, &&lub ||) lub rury |.

Opóźniony proces rozszerzania jest stosowany do tokenów niezależnie. Polecenie może mieć wiele tokenów:

  • Token polecenia. W przypadku większości poleceń sama nazwa polecenia jest tokenem. Ale kilka komend ma wyspecjalizowane regiony, które są uważane za ŻETONY w fazie 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, Gdzie porównanie jest jednym z ==, equ, neq, lss, leq, gtr, lubgeq
  • Token argumentów
  • Docelowy token przekierowania (jeden na przekierowanie)

Żadna zmiana nie jest dokonywana w tokenach, które nie zawierają !.

Dla każdego tokena, który zawiera przynajmniej jeden !, przeskanuj każdy znak od lewej do prawej w poszukiwaniu ^lub !, a jeśli zostanie znaleziony, to

  • 5.1 (ucieczka karetki) Potrzebne !lub ^dosłowne
    • Jeśli znak jest daszkiem, ^to
      • Usunąć ^
      • Zeskanuj następną postać i zachowaj ją jako literał
      • Kontynuuj skanowanie
  • 5.2 (zmienna rozwijana)
    • Jeśli znak jest !, to
      • Jeśli rozszerzenia poleceń są wyłączone,
        spójrz na następny ciąg znaków, łamiący przed !lub <LF>, i nazwij je VAR (może to być pusta lista)
        • Jeśli następny znak jest !następnie
          • Jeśli zdefiniowano WARIANCJA,
            zamień na !VAR!wartość WARIANCJA i kontynuuj skanowanie
          • W przeciwnym razie, jeśli tryb wsadowy, a następnie
            Usuń!VAR! i kontynuuj skanowanie
          • W przeciwnym razie goto 5.2.1
        • W przeciwnym razie goto 5.2.1
      • Else if rozszerzenia poleceń są włączone następnie
        Spójrz na następny ciąg znaków, łamiąc przed !, :lub <LF>, i nazywają je VAR (może być pusta lista). Jeśli WARIANCJA zostanie przerwana przed, :a kolejny znak zostanie !dołączony :jako ostatni znak zmiennej WARIANCJA i przerwij przed!
        • Jeśli następny znak to !następnie
          • Jeśli VAR istnieje, wówczas
            Zamień!VAR! wartość WARIANCJA i kontynuuj skanowanie
          • W przeciwnym razie, jeśli tryb wsadowy, a następnie
            Usuń!VAR! i kontynuuj skanowanie
          • W przeciwnym razie goto 5.2.1
        • W przeciwnym razie, jeśli następny znak to :wtedy
          • Jeśli WARIANCJA jest niezdefiniowana, to
            • Jeśli tryb wsadowy, następnie
              Usuń!VAR: i kontynuuj skanowanie
            • W przeciwnym razie goto 5.2.1
          • W przeciwnym razie, jeśli następny znak to ~wtedy
            • Jeśli następny ciąg znaków pasuje do wzorca, [integer][,[integer]]!to Zastąp !VAR:~[integer][,[integer]]!podciągiem wartości VAR (może to spowodować pusty ciąg) i kontynuuj skanowanie.
            • W przeciwnym razie goto 5.2.1
          • W przeciwnym razie, jeśli następny ciąg znaków pasuje do wzorca [*]search=[replace]!, gdzie wyszukiwanie może obejmować dowolny zestaw znaków z wyjątkiem =, a zamiana może obejmować dowolny zestaw znaków z wyjątkiem !, a następnie
            Zamień na !VAR:[*]search=[replace]!wartość VAR po wykonaniu wyszukiwania i zamiany (prawdopodobnie skutkuje pustym ciągiem) i kontynuuj skanowanie
          • W przeciwnym razie goto 5.2.1
        • W przeciwnym razie goto 5.2.1
      • 5.2.1
        • W trybie wsadowym usuń wiodący W przeciwnym razie !
          zachowaj wiodący!
        • Kontynuuj skanowanie, zaczynając od następnego znaku po zachowanej wiodącej !

3
+1, Brakuje tu tylko składni i reguł dwukropka dla %definedVar:a=b%vs %undefinedVar:a=b%i %var:~0x17,-010%formularzy
jeb

2
Słuszna uwaga - rozszerzyłem sekcję rozszerzania zmiennych, aby odpowiedzieć na Twoje obawy. Rozszerzyłem także sekcję rozszerzania argumentów, aby uzupełnić brakujące szczegóły.
dbenham

2
Po uzyskaniu dodatkowych informacji zwrotnych od jeb, dodałem regułę dla nazw zmiennych kończących się dwukropkiem i dodałem uwagę 2. Dodałem również uwagę 3 po prostu dlatego, że uważałem to za interesujące i ważne.
dbenham

1
@aschipfl - Tak, rozważałem wejście w szczegóły na ten temat, ale nie chciałem zagłębiać się w tę króliczą norę. Celowo byłem niezobowiązujący, kiedy użyłem terminu [liczba całkowita]. Więcej informacji na temat tego, jak CMD.EXE analizuje liczby, znajduje się w Regułach .
dbenham

1
Brakuje mi reguł rozszerzania dla kontekstu cmd, na przykład nie ma zastrzeżonych znaków dla pierwszego znaku nazwy zmiennej, takich jak %<digit>, %*lub %~. A zachowanie zmienia się dla niezdefiniowanych zmiennych. Być może musisz otworzyć drugą odpowiedź
jeb

7

Jak już wspomniano, do poleceń przekazywany jest cały ciąg argumentów w μSoft land i do nich należy przeanalizowanie go na oddzielne argumenty do własnego użytku. Nie ma w tym spójności między różnymi programami, dlatego nie ma jednego zestawu reguł opisujących ten proces. Naprawdę musisz sprawdzić każdy przypadek narożny pod kątem jakiejkolwiek biblioteki C, której używa twój program.

Jeśli chodzi o .batpliki systemowe , oto ten 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

Teraz możemy przeprowadzić kilka testów. Sprawdź, czy możesz dowiedzieć się, co próbuje zrobić μSoft:

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

Jak dotąd dobrze. (Pominę to, co nieinteresujące %cmdcmdline%i %0od teraz.)

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

Brak rozszerzenia nazwy pliku.

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

Bez usuwania cudzysłowów, chociaż cudzysłowy zapobiegają dzieleniu argumentów.

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

Kolejne podwójne cudzysłowy powodują utratę wszelkich specjalnych zdolności analizy, które mogły posiadać. Przykład @ Beniot:

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

Quiz: Jak przekazać wartość zmiennej środowiskowej jako pojedynczy argument (tj. As %1) do pliku 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!

Rozsądne analizowanie wydaje się na zawsze zepsute.

Dla rozrywki, spróbuj dodać Różny ^, \, ', &(& c.) Znaki do tych przykładów.


Aby przekazać% t% jako pojedynczy argument, możesz użyć "% t:" = \ "%" To znaczy, użyj składni% VAR: str = zamiennik% do rozwijania zmiennych. Metaznaki powłoki, takie jak | a & w zmiennej zawartości nadal może być ujawniona i zepsuć powłokę, chyba że ponownie im uciekniesz ....
Toughy

@Toughy Więc w moim przykładzie tjest a "b c. Czy masz przepis na uzyskanie tych 6 znaków ( a2 x przestrzeń ", bi c) do stawienia się jako %1Wewnątrz .cmd? Podoba mi się jednak twoje myślenie. args "%t:"=""%"jest całkiem blisko :-)
bobbogo

5

Masz już kilka świetnych odpowiedzi powyżej, ale aby odpowiedzieć na jedną część pytania:

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

To, co się tam dzieje, polega na tym, że ponieważ masz spację przed znakiem =, tworzona jest zmienna nazywana %a<space>% tak, gdy echo %a %jest ona oceniana poprawnie jako b.

Pozostała część b% c%jest następnie oceniana jako zwykły tekst + niezdefiniowana zmienna % c%, która powinna zostać powtórzona tak, jak wpisana, dla mnie echo %a %b% c%zwracabb% c%

Podejrzewam, że możliwość wstawiania spacji w nazwach zmiennych jest raczej przeoczeniem niż planowaną „funkcją”


0

edit: zobacz zaakceptowaną odpowiedź, co poniżej jest błędne i wyjaśnia tylko, jak przekazać linię poleceń do TinyPerl.


Jeśli chodzi o cytaty, mam wrażenie, że zachowanie jest następujące:

  • kiedy " zostanie znalezione, zaczyna się globbing łańcucha
  • kiedy występuje globbing łańcucha:
    • każdy znak, który nie jest " jest jest globbed
    • kiedy " zostanie znalezione:
      • jeśli następuje ""(a więc potrójny" ), do ciągu dodawany jest podwójny cudzysłów
      • jeśli występuje po nim "(a więc double" ), to do łańcucha dodawany jest podwójny cudzysłów, a końce globowania łańcucha
      • jeśli następny znak nie jest ", globbing łańcucha się kończy
    • kiedy kończy się linia, kończy się globbing łańcucha.

W skrócie:

"a """ b "" c"""składa się z dwóch ciągów: a " b "ic"

"a"", "a"""i "a""""wszystkie są tym samym ciągiem, jeśli znajdują się na końcu wiersza


tokenizer i globbing łańcuchów zależą od polecenia! „Zestaw” działa inaczej niż „połączenie” lub nawet „jeśli”
jeb

tak, ale co z zewnętrznymi poleceniami? Domyślam się, że cmd.exe zawsze przekazuje im te same argumenty?
Benoit

1
cmd.exe przekazuje wynik rozwinięcia zawsze jako ciąg znaków, a nie tokeny, do polecenia zewnętrznego. Zależy to od zewnętrznego polecenia, jak uciec i tokenizować go, findstr używa odwrotnego ukośnika, następny może użyć czegoś innego
jeb

0

Zwróć uwagę, że Microsoft opublikował kod źródłowy swojego terminala. Może działać podobnie do wiersza poleceń pod względem analizy składni. Może ktoś jest zainteresowany przetestowaniem odwrotnych reguł analizowania pod kątem zgodności z regułami przetwarzania terminala.

Link do kodu źródłowego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.