M-am întâlnit ss64.com, care oferă o bună ajutor cu privire la modul de a scrie script-uri lot că Windows Interpretor de comenzi va rula.
Cu toate acestea, am fost în imposibilitatea de a găsi o explicație bună de gramatica de script-uri lot, cum lucrurile se extindă sau să nu se extindă, și cum să scape de lucruri.
Aici sunt exemple de întrebări care nu am fost în măsură să rezolve:
foreach $i (@ARGV) { print '*' . $i ; }
), compilat-o și a numit-o în acest fel :my_script.exe "o ""b"" c"
→ ieșire este *un "b*c
my_script.exe """c"""
→ ieșire *"a*b*c"
în fișierul script-uri, dar
pentru [...] %I` în sesiuni interactive?%PROCESSOR_ARCHITECTURE%
propriu? Am gasit asta echo.exe %""PROCESSOR_ARCHITECTURE%` funcționează, există o soluție mai bună?%
meci? Exemplu:b=a
, echo %o %b% c%
→ %o c%
→
bb c%`set a=un" b "și apoi" ecou.%o%
obțin un" b
. Dacă am folosi totuși echo.exe din UnxUtils, am primit
o b. Cum vine
%o%` se extinde într-un mod diferit?Vă mulțumesc pentru lumini.
<CR>
: Scoateți toate Retur de car (0x0D) caractere
Faza 2) Procesul de caractere speciale, tokenize, și de a construi un cache bloc comanda: Acesta este un proces complex, care este afectat de lucruri, cum ar fi citate, caractere speciale, token delimitatori, și caret scapă.
Faza 3) Echo analizat de comandă(s) Numai în cazul în care comanda bloc nu a început cu @
, iar ECOUL a fost PE la începutul pasul precedent.
Faza 4) PENTRU%X` variabil de expansiune: Numai dacă o comandă este activă și comenzile după ce sunt prelucrate.
Faza 5) Întârziere de Expansiune: Numai în cazul în expansiune întârziată este activat
Faza 5.3) Țeavă de prelucrare: Numai dacă comenzile sunt pe fiecare parte a conductei
Faza 5.5) Executa Redirection:
Faza 6) prelucrarea APEL/Caret dublare: Numai în cazul în care comanda token este APEL
Faza 7) Executa: comanda este executat
Aici sunt detalii pentru fiecare faza:
Rețineți că fazele descrise mai jos sunt doar un model de cum lot parser funcționează. Real cmd.exe interne pot să nu reflecte aceste faze. Dar acest model este eficient în a prevedea comportamentul de script-uri lot.
Faza 0) Linia de Citit: Citeste o linie de intrare prin prima <DACĂ>
.
<Ctrl-Z>
(0x1A) este citit ca <DACĂ>
(linie nouă 0x0A) <Ctrl-Z>
, este tratată în sine - acesta este de nu convertit la <DACĂ>
Faza 1) La Suta De Expansiune: %%
este înlocuit de către un singur %
%*
, %1
, %2
, etc.) %var%
, dacă var nu există o înlocuiască cu nimic <DACĂ> nu în
%var%` expansiune <CR>
: Scoateți toate Transportul de Returnare (0x0D) de la linia de
Faza 2) Procesul de caractere speciale, tokenize, și de a construi un cache bloc comanda: Acesta este un proces complex, care este afectat de lucruri, cum ar fi citate, caractere speciale, token delimitatori, și caret scapă. Ceea ce urmează este o aproximare a acestui proces.
Există concepte care sunt importante pe tot parcursul acestei faze. <spațiu>
<tab>
;
,
=
<0x0B>
<0x0C> " și " <0xFF>
Consecutiv, semn delimitatori sunt tratate ca una - nu există gol jetoane între token delimitatori ^
(
@
&
|
<
"> " <DACĂ>
<spațiu>
<tab>
;
,
=
<0x0B>
<0x0C>
<0xFF>
Uita-te la fiecare caracter la stânga la dreapta: ^
), următorul caracter este scăpat și scapă caret este eliminat. Scăpat de caractere pierde toate semnificație specială (cu excepția <DACĂ>
). "
), comutare citat de pavilion. Dacă citatul de pavilion este activ, atunci doar" " și " <DACĂ>` sunt speciale. Toate celelalte personaje pierde sensul lor speciale până la următorul citat comută citat steagul jos. Nu este posibil pentru a scăpa de închidere citat. Tot citat personajele sunt întotdeauna în aceeași ordine de idei. <DACĂ>
stinge mereu citat de pavilion. Alte comportamente variază în funcție de context, dar citate nu-și modifice comportamentul de <DACĂ>
. <DACĂ>
<DACĂ>
este deposedat <DACĂ>
, atunci este tratat ca un literal, înseamnă că acest proces nu este recursiv. <DACĂ>
este deposedat și de analiză din linia curentă este reziliat. <DACĂ>
într-o ÎN paranteze bloc <DACĂ>
este transformată într-o <spațiu>
<DACĂ>
în cadrul unei paranteze bloc comanda<DACĂ>
este transformată în <DACĂ><spațiu>", iar " <spațiu>
este tratat ca parte din următoarea linie de comanda bloc. &
|
< " sau " >
, divizat în linie la acest punct, în scopul de a se ocupe de conducte, comanda concatenare, și de redirecționare. |
), fiecare parte este o comandă separată (sau command block), care devine o manipulare specială în faza 5.3 &
, &&
, sau ||
comanda concatenare, fiecare parte de concatenare este tratată ca o comandă separată. <
, <<
, >", sau " >>
redirecționare, redirecționarea clauză este analizat, eliminat temporar, iar apoi anexată la sfârșitul comanda curentă. O redirecționare clauză este format dintr-un fișier opțional se ocupe de cifre, redirecționarea operator, și redirecționarea destinație token. @
, apoi @
are o semnificație specială. (@
nu este special în orice alt context) @
este eliminat. @
este înainte de o deschidere (
, atunci întreaga paranteze bloc este exclus din faza 3 ecou. (
nu este special. (
, apoi începe o nouă instrucțiune compusă și creștere paranteza contra )
reziliază compus declarație și diminuări de paranteze contra. )
funcții similare unei REM
declarație atâta timp cât acesta este imediat urmat de un semn de delimitare, caractere speciale, newline, sau de sfârșit de fișier ^
(linie de concatenare este posibil) @
fi fost dezbrăcat și redirecționarea mutat la sfârșitul). (
funcționează ca o comandă semn delimitator, în plus față de standardul token delimitatori ^
care se termină linia, atunci argumentul token este aruncat, și linia următoare este analizat și se anexează la REM. Acest lucru se repetă până când nu există mai mult de un simbol, sau ultimul caracter nu este ^
. :
, și aceasta este prima rundă de faza a 2-a (nu un restart din cauza pentru a APELA în faza 6) atunci )
, <
, " >", &
și |
nu mai au o semnificație specială. Tot restul liniei este considerat a fi parte din eticheta "comanda". ^
continuă să fie specială, în sensul că linia de continuare poate fi folosit pentru a adăuga ulterioare line pentru etichetă. (
nu mai are o semnificație deosebită pentru prima comandă care urmează Neexecutate Etichetă. |
conductă sau &
, &&
, sau ||
comanda de concatenare pe linie. @
, iar ECOUL a fost PE la începutul pasul precedent.
Faza 4) PENTRU%X` variabil de expansiune: Numai dacă o comandă este activă și comenzile după ce sunt prelucrate. %%X " în " %X
. Linia de comandă are diferite procente de expansiune reguli pentru faza 1. Acesta este motivul pentru care liniile de comanda folosi %X
dar fișiere lot folosi %%X
pentru variabile. ~modificatori
nu sunt sensibile la majuscule. ~modificatori
întâietate față de nume de variabile. Dacă un personaj următoarele " ~ " este atât un modificator și valabil PENTRU nume de variabilă, și există un ulterioare personaj care este un activ PENTRU numele variabilei, apoi personajul este interpretat ca un modificator. !
. Dacă nu, atunci token nu este analizat important pentru ^
personaje.
Dacă simbolul nu conține !
, apoi scana fiecare caracter la stânga la dreapta: ^
) următorul caracter nu are nici o semnificație specială, pe caret în sine este eliminat !
sunt s-a prăbușit într-o singur !
!
este eliminat <CR> " sau " <DACĂ>
) %comspec% /S /D /c" commandBlock"
, deci, bloc comanda devine o faza de restart, dar de data asta în modul linie de comandă. <DACĂ> cu o comandă înainte și după ce sunt convertite la
<spațiu>&. Alte
<DACĂ>` sunt dezbrăcat. ||
este folosit.
Faza 6) prelucrarea APEL/Caret dublare: Numai în cazul în care comanda token este APELUL, sau dacă textul înainte de primul loc de standardul token delimitator este APELUL. Dacă APELUL este analizat dintr-o comanda mai mare token, apoi porțiunea neutilizată sunt prefixate cu argumente semn înainte de a continua. /?
. Dacă va fi găsit oriunde în jetoane, apoi abandonați faza 6 și treceți la Etapa 7, în cazul în care AJUTORUL de APEL va fi imprimat. &
sau |
(
@
:
. :
, apoi +
/
[
]
<spațiu>
<tab>
,
;
sau =
Dacă precedent de text este o comandă internă, atunci amintiți-vă că comanda .
\
sau :
Dacă precedent text nu este o comandă internă, apoi du-te la 7.2
Altceva precedent de text poate fi o comandă internă. Amintiți-vă această comandă. +
/
[
]
<spațiu>
<tab>
,
;
sau =
Dacă precedent de text este o cale spre un fișier existent, apoi du-te la 7.2
Altceva executa amintit de comandă internă. /?
este detectat. Cele mai recunosc /?
dacă apare oriunde în argumente. Dar câteva comenzi, cum ar fi ECOU și a STABILIT doar print ajuta dacă primul argument simbol incepe cu /?
. set nume="content" nu ignorat
--> valoare="content" nu ignorat
apoi tot restul de linia de după egal este folosit ca și conținut, precum și orice alte citate care pot fi prezente. ::
întotdeauna va duce la o eroare dacă SUBST este folosit pentru a defini un volum de ::
Dacă SUBST este folosit pentru a defini un volum de ::
, atunci volumul va fi schimbat, acesta nu va fi tratată ca o etichetă. <spațiu>
,
;
sau =
si adauga restul de argument(token). :
, apoi du-te la 7.4
Rețineți că, dacă eticheta simbol incepe cu ::
, atunci acest lucru nu va fi atins, deoarece pasul anterior va fi anulat cu o eroare dacă SUBST este folosit pentru a defini un volum de ::
. :
, apoi du-te la 7.4
Rețineți că acest lucru este rareori atins, deoarece pasul anterior va fi anulat cu o eroare dacă comanda simbol incepe cu ::
, și SUBST este folosit pentru a defini un volum de ::
, și întreaga comandă token este o cale validă pentru o comanda externa. :
.
Reguli în 7.2 și 7.3 se poate preveni o etichetă de la a ajunge la acest punct. Funcționează ca BatchLine-Parser, cu excepția: Faza 1) La Suta De Expansiune:
%*
, %1
etc. argument de expansiune %var%
este lăsat neschimbat. %%
. Dacă var=conținutul, apoi %%var%%
se extinde la %conținut%
.
Faza 3) Echo analizat de comandă(s) !var!
este lăsat neschimbat.
Faza 7) Executa Comanda ::
Există multe diferite contexte unde cmd.exe analizează valori întregi de la siruri de caractere, și regulile sunt inconsistente:
PENTRU a /L %%O în (n1, n2, n3)
Pentru oricine care doresc pentru a îmbunătăți cmd.exe parsarea reguli, nu e un subiect de discutie pe DosTips forum](https://www.dostips.com/forum/viewtopic.php?f=3&t=8355) în cazul în care problemele pot fi raportate și sugestiile făcute. Sper că vă ajută Jan Erik (jeb) - autorul Original și descoperitor de faze Dave Benham (dbenham) - mai Mult conținut suplimentar și editare
Atunci când se invocă o comandă de la o fereastră de comandă, tokenizarea de argumente în linia de comandă nu este realizat prin cmd.exe(un.k.o. "shell"). De cele mai multe ori tokenizarea se face de către nou format de procesele de' C/C++ runtime, dar acest lucru nu este neapărat așa, de exemplu, dacă noul proces nu a fost scrisă în C/C++, sau dacă noul proces, alege să ignore
argv` și procesul prime commandline pentru sine (de exemplu, cu GetCommandLine()). La nivel de OS, Windows trece linia de comanda untokenized ca un singur șir la noi procese. Acest lucru este în contrast cu cele mai multe *nix scoici, în cazul în care shell-ul tokenizes argumente într-un mod coerent, previzibil înainte de a trece-le în format nou proces. Toate acestea înseamnă că pot apărea extrem de divergente argument tokenizarea comportament în diferite programe de pe Windows, ca programe individuale să ia de multe ori argumentul tokenizarea în propriile mâini.
Dacă sună ca anarhie, cam asta este. Cu toate acestea, deoarece un număr mare de programe Windows nu utilizați Microsoft C/C++ runtime's argv
, poate fi, în general, util pentru a înțelege cum msvcrt dll tokenizes argumente. Aici este un fragment:
Microsoft "lot limba" (.bat
) nu este o excepție în acest mediu anarhic, și s-a dezvoltat propriile sale reguli unice pentru tokenizarea și evadarea. De asemenea, se pare ca cmd.exe's de comandă face unele de preprocesare de argument în linia de comandă (mai ales pentru variabila de substituție și evadarea) înainte de a trece la o discuție recent de executare proces. Puteți citi mai multe despre low-detalii la nivel de lot limba și cmd evadarea din raspunsuri excelente de jeb și dbenham pe această pagină.
Las's a construi un simplu utilitar de linie de comandă în C și a vedea ceea ce se spune despre cazuri de testare:
int main(int argc, char* argv[]) {
int i;
for (i = 0; i < argc; i++) {
printf("argv[%d][%s]\n", i, argv[i]);
}
return 0;
}
(Note: argv[0] este întotdeauna nume de executabil, și este omis de mai jos pentru concizie. Testat pe Windows XP SP3. Compilat cu 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 câteva din propriile mele teste:
> 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]
Aici este un extins explicație din Faza 1 în jeb's a răspunde (Valabil pentru ambele modul de lot și modul linie de comandă).
Faza 1) La Suta De Expansiune
Începând de la stânga, scana fiecare personaj pentru % " sau " <DACĂ>
. Dacă va fi găsit atunci
<DACĂ>
) <DACĂ>
apoi <CR>
) %
, astfel încât proceda la 1.1 %
) omis dacă modul linie de comandă %
apoi
Înlocuiți %%
cu un singur %
și continua scanare *
și comanda extensiile sunt activate atunci
Înlocuiți ` % * cu textul de toate argumente în linia de comandă (Înlocui cu nimic dacă nu există argumente) și a continua scanarea. <cifre>
apoi
Înlocuiți `%~
și comanda extensiile sunt activate atunci <cifre>
apoi
Înlocuiți %~[modificatori]<cifre> cu argument modificat valoarea (înlocui cu nimic dacă nu este definit sau dacă este specificat $PATH: modificator nu este definită) și a continua scanarea. *Notă: modificatori sunt case insensitive și pot apărea de mai multe ori, în orice ordine, cu excepția $PATH: modificator poate să apară doar o singură dată și trebuie să fie ultimul modificator înainte de
%
sau la sfârșitul buffer, și le numim VAR (poate fi o listă goală) %
apoi %VAR%
cu valoarea de VAR și de a continua de scanare %VAR%
și continua scanare %
:
sau la sfârșitul buffer, și le numim VAR (poate fi o listă goală). Dacă VAR pauze inainte de": "și ulterioare caracter este %
apoi includ :
ca ultim caracter în VAR și pauză înainte de %
. %
apoi %VAR%
cu valoarea de VAR și de a continua de scanare %VAR%
și continua scanare :
atunci %VAR:
și continua scanarea. ~
atunci [integer][,[integer]]%
apoi
Înlocuiți %VAR:~[integer][,[integer]]%
cu subșir de valoarea VAR (eventual rezultat în șir gol) și a continua scanarea. =
sau *=
atunci
Invalid variabilă de căutare și înlocuiți sintaxa ridică eroare fatală: Toate analizat comenzi sunt avortați, și de prelucrare a lot intrerupe dacă în modul de lot! , unde de căutare poate include orice set de caractere, cu excepția
=, și înlocuiți pot include orice set de caractere, cu excepția
%, apoi înlocuiți
%VAR:[]cauta=[replace]%` cu valoarea de VAR după efectuarea de căutare și înlocui (eventual rezultat în șir gol) si continua de scanare %
și continua scanare începând cu caracterul de după %
%
și continua scanare începând cu caracterul de după %
Cele de mai sus explică de ce acest lot @echo off
setlocal enableDelayedExpansion
set "1var=varA"
set "~f1var=varB"
call :test "arg1"
exit /b
::
:test "arg1"
echo %%1var%% = %1var%
echo ^^^!1var^^^! = !1var!
echo --------
echo %%~f1var%% = %~f1var%
echo ^^^!~f1var^^^! = !~f1var!
exit /b
Dă aceste rezultate:
%1var% = "arg1"var
!1var! = varA
--------
%~f1var% = P:\arg1var
!~f1var! = varB
Notă 1 - Faza 1 are loc înainte de recunoașterea REM declarații. Acest lucru este foarte important pentru că înseamnă chiar o remarcă poate genera o eroare fatală dacă are invalid argument expansiune sintaxa sau invalid variabilă de căutare și înlocuiți sintaxa!
@echo off
rem %~x This generates a fatal argument expansion error
echo this line is never reached
Notă 2 - o Altă consecință interesantă a % parsarea reguli: Variabile care conțin : în numele poate fi definit, dar ele nu pot fi extinse decât dacă comanda extensiile sunt dezactivate. Există o singură excepție - un nume de variabilă care conține o singură colon, la sfârșitul poate fi extinsă, în timp ce comanda extensiile sunt activate. Cu toate acestea, nu puteți efectua subșir sau de căutare și înlocuiți operațiuni pe nume de variabile care se încheie cu un colon. Fișierul de comenzi de mai jos (prin amabilitatea jeb) demonstrează acest comportament
@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%
Notă 3 - Un interesant rezultat de ordinul a normelor de parsare că jeb stabilește în postul său: atunci Când se efectuează găsi și înlocui cu întârziere de expansiune, caractere speciale în ambele găsi și înlocui termenii trebuie să fie scăpat sau citat. Dar situația este diferită de la suta de expansiune - de a găsi pe termen nu trebuie să fi scăpat (deși acesta poate fi citat). Procentul înlocui șirul poate sau nu poate solicita de evacuare sau citat, în funcție de intenție.
@echo off
setlocal enableDelayedExpansion
set "var=this & that"
echo %var:&=and%
echo "%var:&=and%"
echo !var:^&=and!
echo "!var:&=and!"
Aici este un extins, și mai precis explicație a etapei 5 în jeb's a răspunde (Valabil pentru ambele modul de lot și modul linie de comandă) Faza 5) Expansiune Întârziată
Această etapă este omisă dacă oricare dintre următoarele condiții se aplică:
&
, && "sau"||
), sau o țeavă |
.
A întârziat procesul de extindere este aplicat la jetoane în mod independent. O comandă poate avea mai multe jetoane: , în care comparația este una dintre
==,
equ,
cen,
iss,
leq,
gtr", sau " geq` !
.
Pentru fiecare simbol care conține cel puțin un !
, scana fiecare caracter la stânga la dreapta pentru ^
sau !
, și dacă este găsit, atunci ! " sau " ^
literali ^
apoi ^
!
, apoi ! " sau " <DACĂ>
, și le numim VAR (poate fi o listă goală) !
atunci !VAR!
și continua scanare !
, :
, sau <DACĂ>
, și le numim VAR (poate fi o listă goală). Dacă VAR pauze inainte de": "și ulterioare caracter este !
apoi includ :
ca ultim caracter în VAR și pauză înainte de a !
!
atunci !VAR!
și continua scanare :
atunci !VAR:
și continua scanare ~[integer][,[integer]]!
atunci
Înlocuiți !VAR:~[integer][,[integer]]!
cu subșir de valoarea VAR (eventual, rezultând într-un șir gol) si continua de scanare , în cazul în care căutarea poate include orice set de caractere, cu excepția
=, și înlocuiți pot include orice set de caractere, cu excepția
!, apoi Înlocuiți
!VAR:[]cauta=[replace]!` cu valoarea de VAR după efectuarea de căutare și înlocui (eventual, rezultând într-un șir gol) si continua de scanare !
Altcineva a păstra !
!
După cum a subliniat, comenzile sunt transmise întregului argument șir în µSoft teren, și este de până la ei pentru a analiza acest lucru în diferite argumente pentru uz propriu. Nu există nici consistencty în acest între programe diferite, și, prin urmare, nu există nici un set de reguli pentru a descrie acest proces. Ai într-adevăr nevoie pentru a verifica fiecare caz colț pentru orice C biblioteca dvs. utilizează programul.
În măsura în sistem .bat
fișiere merge, aici este testul:
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
Acum putem rula câteva teste. A se vedea dacă vă puteți da seama ce µSoft sunt încercarea de a face:
C>args a b c
cmdcmdline:[cmd.exe ]
0:[args]
*:[a b c]
1:[a]
2:[b]
3:[c]
Bine până acum. (M-am'll lăsa neinteresant %cmdcmdline%
și %0
de acum.)
C>args *.*
*:[*.*]
1:[*.*]
Nu filename expansiune.
C>args "a b" c
*:["a b" c]
1:["a b"]
2:[c]
Nu, citat de separare, deși citate nu împiedică argument divizare.
c>args ""a b" c
*:[""a b" c]
1:[""a]
2:[b" c]
Consecutiv ghilimele le face să-și piardă orice speciale de analiză abilitățile care le-au avut. @Beniot's exemplu:
C>args "a """ b "" c"""
*:["a """ b "" c"""]
1:["a """]
2:[b]
3:[""]
4:[c"""]
Quiz: Cum fac sa trec de valoarea de orice mediu de var ca un single argument (de exemplu, ca %1
) pentru un fișier 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!
Normal parsarea pare rupt pentru totdeauna.
Pentru divertisment, încercați să adăugați diverse^
, \
, '
, &
(&c.) caractere de la aceste exemple.
Ai multe răspunsuri de mai sus deja, dar pentru a răspunde la o parte din întrebarea ta:
set a =b, echo %a %b% c% → bb c%
Ceea ce se întâmplă este că, dacă ai un spatiu inainte de semnul =, o variabilă este creat numit %o<spațiu>%
deci, atunci când echo %o %
care este evaluată corect ca "b".
Partea rămasă b% c%
este apoi evaluată ca text simplu + un undefined variable % c%
, care ar trebui să fie repetat ca tastat, pentru mine echo %o %b% c%
a se întoarce bb% c%
Bănuiesc că abilitatea de a include spații în nume de variabile este mai mult de o supraveghere decât planificat 'dispun'
edit: a se vedea răspunsul acceptat, ceea ce urmează este greșit și explică numai cum să treacă o linie de comandă pentru a TinyPerl.
Cu privire citate, am sentimentul că comportamentul este următoarea:
"
este găsit, string expandarea începe""
(astfel un triplu "
) apoi un citat dublu este adăugat la șirul"
(, astfel, un dublu "
) apoi un citat dublu este adăugat la șirul de coarde și expandarea se termină"
, string expandarea se terminăPe scurt:
"o """ b "" c"""
este format din doua siruri de caractere: un " b " " și " c"
"o""
, `"o""" " și " "o"""" sunt toate la fel șir dacă la sfârșitul anului o linie