Jak pracuje Win scripting
Nasledující text je mým překladem článku Jana Erica který občas přispívá do ALT.MSDOS.BATCH.NT pod nickem "jeb". Text vznikl jako jeho odpověď když byl dotázán po způsobu jakým konzolový procesor ve Win (cmd.exe) zpracovává série příkazů či .cmd scripty. Přesně takovéto informace jsem i já vždy postrádal. Web je sice plný příkladů jak zapisovat jednotlivé příkazy, parametry a pod., ale jak dochází k jejich vlastnímu zpracování procesorem o tom nejsou informace žádné (MS svůj command procesor sice stále vylepšuje, ale nějak nám o tom zapomíná dát vědět). Rozhodl jsem se proto tento článek přeložit, abych informace zpřístupnil i lidem kteří nevládnou angličtinou, nebo sice vládnou hovorou ale v odborných textech jim některé věci nedávají smysl i když je čtou znova a znova.
Případné poznámky k překladu směřujte na lazna@volny.cz
K lepšímu pochopení jak scripty pracují, a proč občas escaping funguje a jindy selhává. Hodně jsem s tím experimentoval, a sestavil jsem testy které pomáhají identifikovat pořadí průběhu jednotlivých fází.
Je zde několik oblastí k popsání, rozdělil jsem je na:
- BatchLineParser - Rozebírání kódu na jednotlivé řádky či úseky uvnitř scriptů v souboru
- CmdLineParser - Rozebírání kódu na jednotlivé řádky či úseky na příkazové řádce, to pracuje jinak
- LabelParser - Jak pracují call/goto a návěští
- CommandBlockCaching - Jak pracují závorky a cachování
- Tokenizer - Jak se sestavují jednotlivé úseky (skupiny znaků) a v jakých fázích
The BatchLineParser:
Každá řádka kódu v souboru scriptu probíhá několika fázemi (vykonávání příkazů zadaných přímo na příkazové řádce probíhá odlišně!). Celý proces začíná fází 1.
- 0] call/zdvojování stříšek: Pouze pokud je řádka znovuvykonávana příkazem call - Zdvojí všechny stříšky (standardní stříšky zřejmě zůstávají), protože ve fázi 2 budou redukovány na jednu.
- 1. Procento: Nahrazování %proměnných% Začíná zleva, zkoumá každý znak zda je to
%. Pokud je, tak:
- 1.1 (escape
%) - Pokud je následováno dalším
%pak: Zaměň dvě%%za jedno%a pokračuj v prohledávání
- 1.2 (nahrazení parametrů)
- Pokud ze % následováno
<číslicí>pak
Nahraď<číslici>obsahem parametru (nahraď "ničím" pokud je argument prázdný) a pokračuj v prohledávání - Nebo pokud je % následováno
~a command extension je povoleno pak: - Pokud je ~ následováno nějakým ze seznamu platných modifikátorů parametru a následováno požadovanou <číslicí> pak:
Nahraď%~[modifikátor]<číslice>hodnotou modifikovaného parametru (nahraď "ničím" pokud parametr neexistuje nebo pokud zvláštní $PATH: modifier not defined) a pokračuj v prohledávání.
Note: Modifikátory jsou case insensitive a mohou se objevovat vícečetně v různém pořadí kromě modifikátoru $PATH:, který se může objevit jen jednou a musí být poslední v pořadí před<číslicí> - Nebo neplatný modifikátor parametru zapříčiní fatal error: batch processing aborts!
- Pokud ze % následováno
- 1.3 (nahrazení proměnných)
- Nebo pokud command extensions nejsou povoleny pak:
Prohlédni následující řetězec znaků, končící dalším%nebo<LF>, a nahraď tam proměnné (to může být i prázdný seznam)
- Pokud je další znak
%pak
Nahraď%VAR%hodnotami proměnných (nahraď "ničím" pokud proměnná není definována) a pokračuj v prohlížení - Nebo jdi na 1.4
- Pokud je další znak
- Else if command extensions are enabled then
Look at next string of characters, breaking before%:or<LF>, and call them VAR (may be an empty list). If VAR breaks before:and the subsequent character is%then include:as the last character in VAR and break before%.- If next character is
%then
Replace%VAR%with value of VAR (replace with nothing if VAR not defined) and continue scan - Else if next character is
:then- If VAR is undefined then
Remove%VAR:and continue scan. - Else if next character is
~then
- If next string of characters matches pattern of
[integer][,[integer]]%then
Replace%VAR:~[integer][,[integer]]%with substring of value of VAR (possibly resulting in empty string) and continue scan. - Else goto 1.4
- If next string of characters matches pattern of
- Else if followed by
=or*=then
Invalid variable search and replace syntax raises fatal error: batch processing aborts! - Else if next string of characters matches pattern of
[*]search=[replace]%then
Replace%VAR:[*]search=[replace]%with value of VAR after performing search and replace (possibly resulting in empty string) and continue scan - Else goto 1.4
- If VAR is undefined then
- If next character is
- Nebo pokud command extensions nejsou povoleny pak:
- 1.4 (strip %)
Else remove % and continue with scan
The above helps explain why this batch
@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
Gives these results:
%1var% = "arg1"var
!1var! = varA
--------
%~f1var% = P:\arg1var
!~f1var! = varB
Note 1 - Phase 1 occurs prior to the recognition of REM statements. This is very important because it means even a remark can generate a fatal error if it has invalid argument expansion syntax or invalid variable search and replace syntax!
@echo off
rem %~x This generates a fatal argument expansion error
echo this line is never reached
Note 2 - Another interesting consequence of the % parsing rules: Variables containing : in the name can be defined, but they cannot be expanded unless command extensions are disabled. There is one exception - a variable name containing a single colon at the end can be expanded while command extensions are enabled. However, you cannot perform substring or search and replace operations on variable names ending with a colon. The batch file below (courtesy of jeb) demonstrates this behavior
@echo off
setlocal
set var=content
set var:=Special
set var::=double colon
set var:~0,2=tricky
set var::~0,2=unfortunate
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%
echo Now with DisableExtensions
setlocal DisableExtensions
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%
Note 3 - An interesting outcome of the order of the parsing rules that jeb lays out in his post: When performing search and replace with normal expansion, special characters should NOT be escaped (though they may be quoted). But when performing search and replace with delayed expansion, special characters MUST be escaped (unless they are quoted).
@echo off
setlocal enableDelayedExpansion
set "var=this & that"
echo %var:&=and%
echo "%var:&=and%"
echo !var:^&=and!
echo "!var:&=and!"
- 1.5] Maže všechny <CR> (Klávesa ENTER - znak CarriageReturn 0x0d) na řádku
- 2] Speciální znaky, "<LF>^&|<>()": Prochází znaky jeden po druhém:
Jestliže znak jsou dvojité uvozovky (") hledá další do páru, jestliže je najde, další speciální znaky mezi nimy jsou ignorovány ^&|<><závorky>
Jestliže najde stříšku ^ pak další znak nemá speciální význam a stříška sama je odstraněna. Pokud je stříška posledním znakem na řádce, další řádka je přidána jako pokračování této a první znak další řádky je vždy pokládán za speciální znak. <LF> okamžitě zastaví interpretaci řádku, ale nezařadí stříšku.
Pokud je dalším znakem speciální znak &|<>, řádek se v tomto bodě rozděluje. Pokud je to roura |, fáze 2 se nad oběma částmi restartuje (znovu pak probíhá o něco komplexněji)
Závorky zvyšují/snižují závorkové počítadlo, přičemž samotné závorky jsou odstraněny. Hledá se koncová závorka a pokud je nalezena, jejich počítadlo se vynuluje. Pokud je dosaženo konce řádku a závorkové počítadlo má hodnotu >0 další řádka je připojena (začíná vždy fází 1)
V této fázi je sestaven primární seznam tokenů, oddělovače tokenů jsou <mezera>,;=
Jsou také detekovány řetězce REM, IF a FOR, dále s nimi bude nakládáno zvlášť
Jestliže první token na řádku je REM, jsou zpracovávány pouze dva tokeny. Toto je důležité pro víceřádkové stříšky
- 3] Echo: Jestliže "echo je on" zobrazí se výsledky fází 1 a 2
Bloky FOR-LOOP jsou zobrazovány vícenásobně, poprvé v kontextu FOR-LOOP, s nenahrazenými proměnnými
Při každém opakování smyčky jsou bloky zobrazovány už s nahrazenými proměnnými
(Tyto dvě fáze nemusí nutně probíhat přímo po sobě, ale to nehraje roli)
- 4) Smyčka FOR: Nahrazování %% a podobně
- 5) Vykřičník: Pouze pokud je zapnuto opožděné nahrazování, prochází se každý znak
Pokud najde znak stříška ^ další znak nemá speciální význam a stříška sama je vynechána
Pokud najde !, hledá se další do páru (stříšky už nejsou sledovány) a celé je to nahrazeno obsahem proměnné pokud existuje
Pokud nejsou v této fázi nalezeny žádné vykřičníky, obsah je zahozen a namísto něho jsou použity výsledky fáze 4 (důležité pro stříšky)
Důležité: V této fázi jsou uvozovky i případné další zvláštní znaky ignorovány
Nahrazování proměnných je nyní "bezpečné", jelikož v této fázi nejsou detekovány další zvláštní znaky
- 6) call:
Pokud je první řetězec CALL, vrací se proces zpět do fáze 0, ale končí po fázi 2, opožděné nahrazování se už podruhé neprovádí
- 7) Provádění: Nyní jsou vykonávány jednotlivé příkazy operačnímu systému
Zde se zpracovávají různé tokeny, v závislosti provádění interních příkazů
V případě sady "název=obsah" je jako řetězec použita celé část mezi rovnítkem a posledními uvozovkami, pokud už za rovnítkem nejsou další uvozovky, použije se zbytek řádky
CmdLineParser:
Pracuje jako BatchLineParser, ale návěští Goto/Call nejsou povoleny
- 1] Procento:
%promenna% bude nahrazena hodnotou proměnné, pokud proměnná není definována, výraz růstane beze změny
%% zde nemá žádný zvláštní význam, druhé znak procenta můžebý začátek proměnné, sady název=obsah, %%proměnná%% se nahradí řetězcem %proměnná%
- 5] Vykřičník: Pouze pokud je povoleno opožděné nahrazování
!proměnná! bude nahrazena obsahem proměnné. Pokud proměnná není definována, výraz zůstane beze změny
smyčka FOR
e.g. for /F "usebackq" %%a IN (command block) DO echo %%a
Příkaz bude zpracován dvakrát, při prvním zpracování je aktivní BatchLineParser (smyčka je uvnitř scriptu) nebo CmdLineParser (smyčka je spustěna z příkatové řádky), při druhém zpracování je vždy aktivní CmdLineParser. Při druhém zpracování opožděné je nahrazování aktivní pouze pokud je povoleno v registrech.
Druhé zpracování je obdobou spuštění "cmd /c" v příkazové řádce, nastavení proměnných proto není trvalé
Snad Vám to pomůže, Jan Erik

