Come vengono analizzati gli script Windows Command Interpreter (CMD.EXE)?


142

Mi sono imbattuto in ss64.com che fornisce un buon aiuto per quanto riguarda come scrivere script batch che verrà eseguito l'interprete dei comandi di Windows.

Tuttavia, non sono stato in grado di trovare una buona spiegazione della grammatica degli script batch, come le cose si espandono o non si espandono e come sfuggire alle cose.

Ecco alcune domande di esempio che non sono stato in grado di risolvere:

  • Come viene gestito il sistema di preventivi? Ho creato uno script TinyPerl
    ( foreach $i (@ARGV) { print '*' . $i ; }), l'ho compilato e chiamato in questo modo:
    • my_script.exe "a ""b"" c" → l'uscita è *a "b*c
    • my_script.exe """a b c""" → emettilo *"a*b*c"
  • Come funziona l'interno echo comando ? Cosa si espande all'interno di quel comando?
  • Perché devo usare for [...] %%I negli script di file, ma for [...] %Inelle sessioni interattive?
  • Quali sono i personaggi di fuga e in quale contesto? Come sfuggire a un segno di percentuale? Ad esempio, come posso eco%PROCESSOR_ARCHITECTURE% letteralmente? Ho scoperto che echo.exe %""PROCESSOR_ARCHITECTURE%funziona, c'è una soluzione migliore?
  • Come si accoppiano le coppie %? Esempio:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • Come posso garantire che una variabile passi a un comando come singolo argomento se mai questa variabile contiene virgolette?
  • Come vengono memorizzate le variabili quando si utilizza il setcomando? Ad esempio, se lo faccio set a=a" be quindi echo.%a%ottengo a" b. Se comunque utilizzo echo.exeda UnxUtils, ottengo a b. Come mai si %a%espande in modo diverso?

Grazie per le tue luci.


Risposte:


200

Abbiamo eseguito esperimenti per studiare la grammatica degli script batch. Abbiamo anche studiato le differenze tra la modalità batch e la riga di comando.

Analizzatore di linee batch:

Ecco una breve panoramica delle fasi nel parser della riga del file batch:

Fase 0) Leggi la riga:

Fase 1) Espansione percentuale:

Fase 2) Elaborazione di caratteri speciali, tokenizzazione e creazione di un blocco comandi memorizzato nella cache: si tratta di un processo complesso che è influenzato da elementi quali virgolette, caratteri speciali, delimitatori di token e escape del cursore.

Fase 3) Eco il / i comando / i analizzato / i Solo se il blocco comandi non è iniziato @e ECHO era ON all'inizio del passaggio precedente.

Fase 4) %XEspansione variabile FOR : solo se è attivo un comando FOR e vengono elaborati i comandi dopo DO.

Fase 5) Espansione ritardata: solo se è abilitata l'espansione ritardata

Fase 5.3) Elaborazione del tubo: solo se i comandi sono su entrambi i lati di un tubo

Fase 5.5) Eseguire il reindirizzamento:

Fase 6) Elaborazione CHIAMATA / Raddoppio del cursore: solo se il token di comando è CHIAMATA

Fase 7) Esegui: il comando viene eseguito


Ecco i dettagli per ogni fase:

Si noti che le fasi descritte di seguito sono solo un modello di come funziona il parser batch. Gli interni effettivi di cmd.exe potrebbero non riflettere queste fasi. Ma questo modello è efficace nel prevedere il comportamento degli script batch.

Fase 0) Leggi riga: leggi prima la riga di input <LF>.

  • Quando si legge una riga da analizzare come comando, <Ctrl-Z>(0x1A) viene letto come<LF> (LineFeed 0x0A)
  • Quando GOTO o CALL legge linee durante la scansione di: etichetta, <Ctrl-Z>è così trattato stessa - è non convertito<LF>

Fase 1) Espansione percentuale:

  • Una doppia %%è sostituita da una singola%
  • Espansione di argomenti ( %*, %1,%2 , etc.)
  • Espansione di %var% , se var non esiste sostituirlo con nulla
  • La linea viene inizialmente troncata <LF>non all'interno %var%dell'espansione
  • Per una spiegazione completa leggi la prima metà di questo da dbenham Stesso thread: Fase percentuale

Fase 2) Elaborazione di caratteri speciali, tokenizzazione e creazione di un blocco comandi memorizzato nella cache: si tratta di un processo complesso che è influenzato da elementi quali virgolette, caratteri speciali, delimitatori di token e escape del cursore. Ciò che segue è un'approssimazione di questo processo.

Ci sono concetti importanti durante questa fase.

  • Un token è semplicemente una stringa di caratteri che viene trattata come un'unità.
  • I token sono separati da delimitatori token. I delimitatori di token standard sono <space> <tab> ; , = <0x0B> <0x0C>e<0xFF>
    delimitatori di token consecutivi vengono considerati come uno - non ci sono token vuoti tra i delimitatori di token
  • Non ci sono delimitatori di token all'interno di una stringa tra virgolette. L'intera stringa tra virgolette viene sempre trattata come parte di un singolo token. Un singolo token può consistere in una combinazione di stringhe tra virgolette e caratteri non quotati.

I seguenti caratteri possono avere un significato speciale in questa fase, a seconda del contesto: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

Guarda ogni personaggio da sinistra a destra:

  • Se <CR>quindi rimuoverlo, come se non fosse mai stato lì (tranne per il comportamento strano reindirizzamento )
  • Se un punto di inserimento ( ^), il carattere successivo viene evaso e il punto di escape viene rimosso. I caratteri di escape perdono tutto il significato speciale (tranne che per <LF>).
  • Se un preventivo ( "), attiva o disattiva la bandiera del preventivo. Se il flag di citazione è attivo, solo "e <LF>sono speciali. Tutti gli altri personaggi perdono il loro significato speciale fino a quando la citazione successiva disattiva la bandiera della citazione. Non è possibile sfuggire alla citazione di chiusura. Tutti i caratteri tra virgolette sono sempre all'interno dello stesso token.
  • <LF>disattiva sempre la bandiera del preventivo. Altri comportamenti variano a seconda del contesto, ma le virgolette non modificano mai il comportamento di <LF>.
    • Sfuggito <LF>
      • <LF> è spogliato
      • Il personaggio successivo è fuggito. Se alla fine del buffer di riga, la riga successiva viene letta ed elaborata dalle fasi 1 e 1.5 e aggiunta a quella corrente prima di sfuggire al carattere successivo. Se il personaggio successivo è <LF>, allora viene trattato come un valore letterale, il che significa che questo processo non è ricorsivo.
    • <LF>Non sfuggito tra parentesi
      • <LF> viene rimosso e l'analisi della riga corrente viene terminata.
      • Eventuali caratteri rimanenti nel buffer di riga vengono semplicemente ignorati.
    • Senza caratteri <LF>di escape all'interno di un blocco tra parentesi FOR IN
      • <LF> viene convertito in a <space>
      • Se alla fine del buffer di riga, la riga successiva viene letta e aggiunta a quella corrente.
    • Senza caratteri <LF>di escape all'interno di un blocco comandi tra parentesi
      • <LF>viene convertito in <LF><space>e <space>viene trattato come parte della riga successiva del blocco comandi.
      • Se alla fine del buffer di riga, la riga successiva viene letta e aggiunta allo spazio.
  • Se uno dei caratteri speciali & | <o >, dividere la linea in questo punto per gestire le pipe, comandare la concatenazione e il reindirizzamento.
    • Nel caso di un tubo (| ), ogni lato è un comando separato (o blocco comandi) che ottiene una gestione speciale nella fase 5.3
    • Nel caso di &, &&o ||concatenazione di comandi, ciascun lato della concatenazione viene trattato come un comando separato.
    • Nel caso di <, <<, >, o >>il reindirizzamento, la clausola reindirizzamento viene analizzato, temporaneamente rimosso, e poi aggiunto alla fine del comando corrente. Una clausola di reindirizzamento è costituita da una cifra di gestione del file facoltativa, dall'operatore di reindirizzamento e dal token di destinazione del reindirizzamento.
      • Se il token che precede l'operatore di reindirizzamento è una singola cifra senza escape, la cifra specifica l'handle del file da reindirizzare. Se il token handle non viene trovato, l'output predefinito reindirizza su 1 (stdout) e l'input reindirizza su 0 (stdin).
  • Se il primo token per questo comando (prima di spostare il reindirizzamento alla fine) inizia con @, allora @ha un significato speciale. ( @non è speciale in nessun altro contesto)
    • Lo speciale @è stato rimosso.
    • Se ECHO è ON, questo comando, insieme a tutti i seguenti comandi concatenati su questa linea, sono esclusi dall'eco della fase 3. Se il @è prima di un'apertura (, l'intero blocco tra parentesi viene escluso dall'eco della fase 3.
  • Parentesi di processo (fornisce istruzioni composte su più righe):
    • Se il parser non è alla ricerca di un token di comando, (non è speciale.
    • Se il parser è alla ricerca di un token di comando e trova (, quindi avviare una nuova istruzione composta e incrementare il contatore tra parentesi
    • Se il contatore parentesi è> 0, )termina l'istruzione composta e decrementa il contatore parentesi.
    • Se viene raggiunta la fine della linea e il contatore delle parentesi è> 0, la riga successiva verrà aggiunta all'istruzione composta (ricomincia dalla fase 0)
    • Se il contatore tra parentesi è 0 e il parser è alla ricerca di un comando, )funziona in modo simile a REMun'istruzione fintanto che è immediatamente seguito da un delimitatore di token, un carattere speciale, una nuova riga o una fine del file
      • Tutti i caratteri speciali perdono il loro significato tranne ^(la concatenazione di righe è possibile)
      • Una volta raggiunta la fine della linea logica, l'intero "comando" viene scartato.
  • Ogni comando viene analizzato in una serie di token. Il primo token viene sempre trattato come un token di comando (dopo che lo speciale @è stato rimosso e il reindirizzamento viene spostato alla fine).
    • I delimitatori di token iniziali prima del token di comando vengono eliminati
    • Durante l'analisi del token di comando, (funziona come delimitatore di token di comando, oltre ai delimitatori di token standard
    • La gestione dei token successivi dipende dal comando.
  • La maggior parte dei comandi concatena semplicemente tutti gli argomenti dopo il token di comando in un singolo token argomento. Tutti i delimitatori di token argomento vengono conservati. Le opzioni dell'argomento non vengono in genere analizzate fino alla fase 7.
  • Tre comandi ottengono una gestione speciale: IF, FOR e REM
    • IF viene suddiviso in due o tre parti distinte che vengono elaborate in modo indipendente. Un errore di sintassi nella costruzione IF provocherà un errore fatale di sintassi.
      • L'operazione di confronto è il comando effettivo che scorre fino alla fase 7
        • Tutte le opzioni di IF sono analizzate completamente nella fase 2.
        • I delimitatori di token consecutivi vengono compressi in un unico spazio.
        • A seconda dell'operatore di confronto, ci saranno uno o due token valore identificati.
      • Il vero blocco di comandi è l'insieme di comandi dopo la condizione e viene analizzato come qualsiasi altro blocco di comandi. Se si deve utilizzare ELSE, il blocco True deve essere tra parentesi.
      • Il blocco di comandi False opzionale è l'insieme di comandi dopo ELSE. Ancora una volta, questo blocco comandi viene analizzato normalmente.
      • I blocchi di comando Vero e Falso non scorrono automaticamente nelle fasi successive. La loro successiva elaborazione è controllata dalla fase 7.
    • FOR viene diviso in due dopo il DO. Un errore di sintassi nella costruzione FOR comporterà un errore di sintassi fatale.
      • La parte attraverso DO è l'effettivo comando FOR iteration che scorre fino alla fase 7
        • Tutte le opzioni FOR sono completamente analizzate nella fase 2.
        • La clausola tra parentesi IN considera <LF>come <space>. Dopo aver analizzato la clausola IN, tutti i token vengono concatenati insieme per formare un singolo token.
        • I delimitatori di token consecutivi senza escape / non quotati vengono compressi in un unico spazio nel comando FOR tramite DO.
      • La parte dopo DO è un blocco comandi che viene analizzato normalmente. La successiva elaborazione del blocco comandi DO è controllata dall'iterazione nella fase 7.
    • Il REM rilevato nella fase 2 viene trattato in modo drammatico diverso rispetto a tutti gli altri comandi.
      • Viene analizzato solo un token argomento: il parser ignora i caratteri dopo il primo token argomento.
      • Il comando REM può apparire nell'output della fase 3, ma il comando non viene mai eseguito e il testo dell'argomento originale viene ripetuto - i caratteri di escape non vengono rimossi, tranne ...
        • Se esiste un solo token argomento che termina con un senza escape ^che termina la riga, il token argomento viene eliminato e la riga successiva viene analizzata e aggiunta al REM. Questo si ripete fino a quando non vi è più di un token o l'ultimo carattere non lo è ^.
  • Se il token di comando inizia con :, e questo è il primo round della fase 2 (non un riavvio a causa di CHIAMATA nella fase 6), allora
    • Il token viene normalmente trattato come un'etichetta non eseguita .
      • Il resto della linea viene analizzata, tuttavia ), <, >, &e |non hanno più significato speciale. L'intero resto della riga è considerato parte dell'etichetta "comando".
      • La ^continua ad essere speciale, il che significa che la linea di ripresa può essere utilizzato per modificare la linea successiva all'etichetta.
      • Un'etichetta non eseguita all'interno di un blocco parentesi si tradurrà in un errore di sintassi irreversibile se non è immediatamente seguito da un comando o Eseguito etichetta sulla riga successiva.
        • (non ha più un significato speciale per il primo comando che segue l' etichetta non eseguita .
      • Il comando viene interrotto al termine dell'analisi dell'etichetta. Le fasi successive non hanno luogo per l'etichetta
    • Esistono tre eccezioni che possono far sì che un'etichetta trovata nella fase 2 sia trattata come un'etichetta eseguita che continua ad analizzare attraverso la fase 7.
      • C'è reindirizzamento che precede l'etichetta token, e v'è un |tubo o &, &&o ||comando concatenazione sulla linea.
      • C'è un reindirizzamento che precede il token dell'etichetta e il comando si trova all'interno di un blocco tra parentesi.
      • Il token etichetta è il primo comando su una riga all'interno di un blocco tra parentesi e la riga sopra è terminata con un'etichetta non eseguita .
    • Quanto segue si verifica quando viene rilevata un'etichetta eseguita nella fase 2
      • L'etichetta, i suoi argomenti e il suo reindirizzamento sono tutti esclusi da qualsiasi output di eco nella fase 3
      • Eventuali comandi concatenati successivi sulla riga vengono analizzati ed eseguiti completamente.
    • Per ulteriori informazioni sulle etichette eseguite rispetto alle etichette non eseguite , vedere https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Fase 3) Eco il / i comando / i analizzato / i Solo se il blocco comandi non è iniziato @e ECHO era ON all'inizio del passaggio precedente.

Fase 4) %XEspansione variabile FOR : solo se è attivo un comando FOR e vengono elaborati i comandi dopo DO.

  • A questo punto, la fase 1 dell'elaborazione batch avrà già convertito una variabile FOR come %%Xin %X. La riga di comando ha regole di espansione percentuale diverse per la fase 1. Questo è il motivo utilizzato dalle righe di comando %Xma i file batch utilizzano %%Xper le variabili FOR.
  • I nomi delle variabili FOR fanno distinzione tra maiuscole e minuscole, ma ~modifiers non fanno distinzione tra maiuscole e minuscole.
  • ~modifiersavere la precedenza sui nomi delle variabili. Se un carattere che segue ~è sia un modificatore che un nome di variabile FOR valido ed esiste un carattere successivo che è un nome di variabile FOR attivo, il carattere viene interpretato come un modificatore.
  • I nomi delle variabili FOR sono globali, ma solo nel contesto di una clausola DO. Se una routine viene CHIAMATA dall'interno di una clausola FOR DO, le variabili FOR non vengono espanse all'interno della routine CALLed. Ma se la routine ha il proprio comando FOR, allora tutte le variabili FOR attualmente definite sono accessibili ai comandi DO interni.
  • I nomi delle variabili FOR possono essere riutilizzati nei FOR nidificati. Il valore FOR interno ha la precedenza, ma una volta chiuso INNER FOR, viene ripristinato il valore FOR esterno.
  • Se ECHO era ON all'inizio di questa fase, la fase 3) viene ripetuta per mostrare i comandi DO analizzati dopo l'espansione delle variabili FOR.

---- Da questo punto in poi, ciascun comando identificato nella fase 2 viene elaborato separatamente.
---- Le fasi da 5 a 7 sono completate per un comando prima di passare al successivo.

Fase 5) Espansione ritardata: solo se l'espansione ritardata è attiva, il comando non si trova in un blocco tra parentesi su entrambi i lati di una pipe e il comando non è uno script batch "nudo" (nome dello script senza parentesi, CALL, concatenazione dei comandi, o pipe).

  • Ogni token per un comando viene analizzato in modo indipendente per l'espansione ritardata.
    • La maggior parte dei comandi analizza due o più token: il token di comando, il token degli argomenti e ogni token di destinazione del reindirizzamento.
    • Il comando FOR analizza solo il token della clausola IN.
    • Il comando IF analizza solo i valori di confronto, uno o due, a seconda dell'operatore di confronto.
  • Per ogni token analizzato, controlla prima se ne contiene !. In caso contrario, il token non viene analizzato, importante per i ^personaggi. Se il token contiene !, scansiona ogni personaggio da sinistra a destra:
    • Se si tratta di un carattere ( ^), il carattere successivo non ha alcun significato speciale, il carattere stesso viene rimosso
    • Se si tratta di un punto esclamativo, cerca il punto esclamativo successivo (i punti di inserimento non vengono più osservati), espandi al valore della variabile.
      • L'apertura consecutiva !viene compressa in una sola!
      • Qualsiasi residuo non accoppiato !viene rimosso
    • Espandere var a questo punto è "sicuro", perché i caratteri speciali non vengono più rilevati (nemmeno <CR>o <LF>)
    • Per una spiegazione più completa, leggi la seconda metà di questo dallo stesso thread di dbenham - Fase punto esclamativo

Fase 5.3) Elaborazione del tubo: solo se i comandi sono su entrambi i lati di un tubo
Ogni lato del tubo viene elaborato in modo indipendente e asincrono.

  • Se il comando è interno a cmd.exe, oppure si tratta di un file batch o se si tratta di un blocco comandi tra parentesi, viene eseguito in un nuovo thread cmd.exe tramite %comspec% /S /D /c" commandBlock", quindi il blocco comandi ottiene un riavvio di fase, ma questa volta in modalità riga di comando.
    • Se un blocco di comandi tra parentesi, <LF>vengono convertiti tutti con un comando prima e dopo <space>&. Altri <LF>sono spogliati.
  • Questa è la fine dell'elaborazione per i comandi pipe.
  • Vedi Perché l'espansione ritardata ha esito negativo quando si trova all'interno di un blocco di codice convogliato? per ulteriori informazioni sull'analisi e l'elaborazione delle pipe

Fase 5.5) Esegui reindirizzamento: ora viene eseguito qualsiasi reindirizzamento scoperto nella fase 2.

Fase 6) Elaborazione CHIAMATA / Raddoppio del cursore: solo se il token di comando è CHIAMATA o se il testo prima del primo delimitatore di token standard che si verifica è CHIAMATA. Se CALL viene analizzato da un token di comando più grande, la parte inutilizzata viene anteposta al token degli argomenti prima di procedere.

  • Analizza il token argomenti per un non quotato /?. Se presente in qualsiasi punto all'interno dei token, interrompere la fase 6 e passare alla Fase 7, dove verrà stampato l'Help per CALL.
  • Rimuovi il primo CALL, in modo che più CALL possano essere impilate
  • Raddoppia tutti i punti di inserimento
  • Riavviare le fasi 1, 1.5 e 2, ma non continuare con la fase 3
    • Eventuali punti di raddoppio vengono ridotti a un punto di riferimento, purché non vengano quotati. Ma sfortunatamente, i trattini citati rimangono raddoppiati.
    • La fase 1 cambia un po '
      • Gli errori di espansione nel passaggio 1.2 o 1.3 interrompono la CHIAMATA, ma l'errore non è fatale - l'elaborazione batch continua.
    • Le attività di fase 2 sono leggermente modificate
      • Qualsiasi reindirizzamento non quotato, senza escape appena visualizzato che non è stato rilevato nel primo round della fase 2 viene rilevato, ma viene rimosso (incluso il nome del file) senza eseguire effettivamente il reindirizzamento
      • Qualsiasi punto di inserimento non quotato, senza escape appena visualizzato alla fine della linea viene rimosso senza eseguire la continuazione della linea
      • La CHIAMATA viene interrotta senza errori se viene rilevata una delle seguenti condizioni
        • Appare di recente senza quotazione, senza escape &o|
        • Il token di comando risultante inizia con non quotato, senza caratteri di escape (
        • Il primo token dopo l'inizio della CHIAMATA rimossa @
      • Se il comando risultante è un IF o FOR apparentemente valido, l'esecuzione fallirà successivamente con un errore che indica che IFoFOR non viene riconosciuto come comando interno o esterno.
      • Ovviamente la CHIAMATA non viene interrotta in questo secondo round della fase 2 se il token di comando risultante è un'etichetta che inizia con :.
  • Se il risultante token di comando è CALL, riavvia la Fase 6 (si ripete fino a quando non viene più eseguita la CHIAMATA)
  • Se il risultante token di comando è uno script batch o un'etichetta: quindi l'esecuzione della CHIAMATA è completamente gestita dal resto della Fase 6.
    • Inserire l'attuale posizione del file di script batch nello stack di chiamate in modo che l'esecuzione possa riprendere dalla posizione corretta al termine della CHIAMATA.
    • Imposta i token argomento% 0,% 1,% 2, ...% N e% * per la CHIAMATA, usando tutti i token risultanti
    • Se il token di comando è un'etichetta che inizia con : , allora
      • Riavvia fase 5. Ciò può influire su cosa: l'etichetta è CHIAMATA. Ma poiché i token% 0 ecc. Sono già stati impostati, non modificherà gli argomenti passati alla routine CALLed.
      • Eseguire l'etichetta GOTO per posizionare il puntatore del file all'inizio della subroutine (ignorare tutti gli altri token che possono seguire l'etichetta:) Vedere la Fase 7 per le regole su come funziona GOTO.
        • Se manca il token dell'etichetta: o l'etichetta: non viene trovata, lo stack di chiamate viene immediatamente estratto per ripristinare la posizione del file salvato e CALL viene interrotto.
        • Se l'etichetta: contiene /?, Viene stampata la guida di GOTO invece di cercare l'etichetta:. Il puntatore al file non si sposta, in modo tale che il codice dopo la CHIAMATA venga eseguito due volte, una volta nel contesto della CHIAMATA, e poi di nuovo dopo il ritorno della CHIAMATA. Vedi Perché CALL stampa il messaggio di aiuto GOTO in questo script e perché i comandi dopo vengono eseguiti due volte? per maggiori informazioni.
    • Altrimenti controllo del trasferimento allo script batch specificato.
    • Esecuzione di CALLed: l'etichetta o lo script continua fino a raggiungere EXIT / B o la fine del file, a quel punto lo stack CALL viene espulso e l'esecuzione riprende dalla posizione del file salvato.
      La fase 7 non viene eseguita per gli script CHIAMATI o: etichette.
  • Altrimenti il ​​risultato della fase 6 passa alla fase 7 per l'esecuzione.

Fase 7) Esegui: il comando viene eseguito

  • 7.1 - Esegui comando interno - Se il token di comando è citato, salta questo passaggio. Altrimenti, tenta di analizzare un comando interno ed eseguire.
    • Vengono eseguiti i seguenti test per determinare se un token di comando non quotato rappresenta un comando interno:
      • Se il token di comando corrisponde esattamente a un comando interno, quindi eseguirlo.
      • In caso contrario, interrompere il token di comando prima della prima occorrenza di + / [ ] <space> <tab> , ;o =
        Se il testo precedente è un comando interno, ricordare quel comando
        • Se in modalità riga di comando o se il comando proviene da un blocco tra parentesi, blocco di comando SE vero o falso, blocco di comando FOR DO o coinvolto nella concatenazione dei comandi, eseguire il comando interno
        • Altrimenti (deve essere un comando autonomo in modalità batch) scansiona la cartella corrente e il PERCORSO alla ricerca di un file .COM, .EXE, .BAT o .CMD il cui nome di base corrisponde al token di comando originale
          • Se il primo file corrispondente è un .BAT o .CMD, vai a 7.3.exec ed esegui quello script
          • Altrimenti (la corrispondenza non trovata o la prima corrispondenza è .EXE o .COM) esegue il comando interno ricordato
      • Altrimenti rompi il token di comando prima della prima occorrenza di . \o :
        Se il testo precedente non è un comando interno, vai a 7.2 Il
        testo precedente potrebbe essere un comando interno. Ricorda questo comando.
      • Interrompi il token di comando prima della prima occorrenza di + / [ ] <space> <tab> , ;o =
        Se il testo precedente è un percorso di un file esistente, vai a 7.2
        Else esegui il comando interno ricordato.
    • Se un comando interno viene analizzato da un token di comando più grande, la parte inutilizzata del token di comando viene inclusa nell'elenco degli argomenti
    • Solo perché un token di comando viene analizzato come comando interno non significa che verrà eseguito correttamente. Ogni comando interno ha le sue regole su come vengono analizzati gli argomenti e le opzioni e su quale sintassi è consentita.
    • Tutti i comandi interni stamperanno la guida invece di eseguire la loro funzione se /?rilevati. La maggior parte riconosce /?se appare ovunque negli argomenti. Ma alcuni comandi come ECHO e SET stampano aiuto solo se inizia il primo token argomento /?.
    • SET ha alcune semantiche interessanti:
      • Se un comando SET ha una virgoletta prima che il nome della variabile e le estensioni siano abilitate
        set "name=content" ignored -> valore = content
        allora il testo tra il primo segno di uguale e l'ultima citazione viene utilizzato come contenuto (escluso il primo uguale e l'ultima citazione). Il testo dopo l'ultima citazione viene ignorato. Se dopo il segno di uguale non è presente alcuna citazione, il resto della riga viene utilizzato come contenuto.
      • Se un comando SET non ha una virgoletta prima del nome
        set name="content" not ignored -> valore = "content" not ignored
        allora l'intero resto della riga dopo la parità è usato come contenuto, incluse tutte le virgolette eventualmente presenti.
    • Viene valutato un confronto IF e, a seconda che la condizione sia vera o falsa, viene elaborato il blocco di comando dipendente già analizzato appropriato, a partire dalla fase 5.
    • La clausola IN di un comando FOR viene ripetuta in modo appropriato.
      • Se questo è un FOR / F che scorre l'output di un blocco comandi, allora:
        • La clausola IN viene eseguita in un nuovo processo cmd.exe tramite CMD / C.
        • Il blocco comandi deve passare l'intero processo di analisi una seconda volta, ma questa volta in un contesto da riga di comando
        • ECHO si avvia su ON e l'espansione ritardata di solito inizia disabilitata (a seconda delle impostazioni del registro)
        • Tutte le modifiche all'ambiente apportate dal blocco comandi della clausola IN andranno perse al termine del processo cmd.exe figlio
      • Per ogni iterazione:
        • I valori delle variabili FOR sono definiti
        • Il blocco di comandi DO già analizzato viene quindi elaborato, a partire dalla fase 4.
    • GOTO utilizza la seguente logica per individuare: label
      • L'etichetta viene analizzata dal token del primo argomento
      • Lo script viene scansionato per la prossima occorrenza dell'etichetta
        • La scansione inizia dalla posizione corrente del file
        • Se viene raggiunta la fine del file, la scansione torna all'inizio del file e continua fino al punto di partenza originale.
      • La scansione si interrompe alla prima occorrenza dell'etichetta rilevata e il puntatore del file viene impostato sulla riga immediatamente successiva all'etichetta. L'esecuzione della sceneggiatura riprende da quel punto. Si noti che un GOTO vero di successo interromperà immediatamente qualsiasi blocco di codice analizzato, inclusi i cicli FOR.
      • Se l'etichetta non viene trovata o il token dell'etichetta è mancante, GOTO ha esito negativo, viene stampato un messaggio di errore e lo stack di chiamate viene visualizzato. Funziona in modo efficace come EXIT / B, ad eccezione dei comandi già analizzati nel blocco di comandi corrente che seguono GOTO vengono comunque eseguiti, ma nel contesto del CALLer (il contesto esistente dopo EXIT / B)
      • Consulta https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 per una descrizione più precisa delle regole utilizzate per l'analisi delle etichette.
    • RENAME e COPY accettano entrambi i caratteri jolly per i percorsi di origine e destinazione. Ma Microsoft fa un lavoro terribile documentando come funzionano i caratteri jolly, specialmente per il percorso target. Un utile set di regole per i caratteri jolly è disponibile in Come il comando Windows RENAME interpreta i caratteri jolly?
  • 7.2 - Eseguire la modifica del volume - Altrimenti se il token di comando non inizia con una virgoletta, è lungo esattamente due caratteri e il 2o carattere è due punti, quindi modificare il volume
    • Tutti i token argomento vengono ignorati
    • Se non è possibile trovare il volume specificato dal primo carattere, interrompere con un errore
    • Un token di comando ::comporterà sempre un errore a meno che SUBST non venga utilizzato per definire un volume per ::
      Se SUBST viene utilizzato per definire un volume per ::, il volume verrà modificato, non verrà trattato come un'etichetta.
  • 7.3 - Esegui comando esterno - Altrimenti prova a trattare il comando come comando esterno.
    • Se in modalità riga di comando e il comando non è citato e non inizia con una specifica volume, bianco-spazio, ,, ;, =o +poi rompere il comando gettone alla prima occorrenza di <space> , ;o =e anteporre il resto all'argomento gettone (s).
    • Se il 2o carattere del token di comando è due punti, verificare che sia possibile trovare il volume specificato dal 1o carattere.
      Se non è possibile trovare il volume, interrompere con un errore.
    • Se in modalità batch e il token di comando inizia con :, quindi vai a 7.4
      Nota che se il token etichetta inizia con ::, questo non verrà raggiunto perché il passaggio precedente sarà interrotto con un errore a meno che SUBST non sia usato per definire un volume per ::.
    • Identificare il comando esterno da eseguire.
      • Questo è un processo complesso che può coinvolgere il volume corrente, la directory corrente, la variabile PATH, la variabile PATHEXT e le associazioni di file.
      • Se non è possibile identificare un comando esterno valido, interrompere con un errore.
    • Se in modalità riga di comando e il token di comando inizia con :, quindi vai a 7.4
      Nota che questo viene raggiunto raramente perché il passaggio precedente sarà interrotto con un errore a meno che il token di comando non inizi con ::e SUBST viene utilizzato per definire un volume per ::, e il l'intero token di comando è un percorso valido per un comando esterno.
    • 7.3.exec - Esegue il comando esterno.
  • 7.4 - Ignora un'etichetta - Ignora il comando e tutti i suoi argomenti se il token di comando inizia con :.
    Le regole in 7.2 e 7.3 possono impedire a un'etichetta di raggiungere questo punto.

Parser della riga di comando:

Funziona come BatchLine-Parser, tranne:

Fase 1) Espansione percentuale:

  • No %*, %1ecc. Argomento di espansione
  • Se var non è definito, %var%rimane invariato.
  • Nessuna gestione speciale di %%. Se var = content, si %%var%%espande in %content%.

Fase 3) Eco i comandi analizzati

  • Questo non viene eseguito dopo la fase 2. Viene eseguito solo dopo la fase 4 per il blocco comandi FOR DO.

Fase 5) Espansione ritardata: solo se DelayedExpansion è abilitato

  • Se var non è definito, !var!rimane invariato.

Fase 7) Esegui comando

  • Tentativi di CHIAMARE o GOTO a: etichetta genera un errore.
  • Come già documentato nella fase 7, un'etichetta eseguita può provocare un errore in diversi scenari.
    • Le etichette eseguite in batch possono causare un errore solo se iniziano con ::
    • Le etichette eseguite dalla riga di comando generano quasi sempre un errore

Analisi di valori interi

Esistono molti contesti diversi in cui cmd.exe analizza i valori interi dalle stringhe e le regole sono incoerenti:

  • SET /A
  • IF
  • %var:~n,m% (espansione della sottostringa variabile)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

I dettagli per queste regole sono disponibili in Regole su come CMD.EXE analizza i numeri


Per chiunque desideri migliorare le regole di analisi di cmd.exe, nel forum DosTips è presente un argomento di discussione in cui è possibile segnalare problemi e fornire suggerimenti.

Spero che aiuti
Jan Erik (jeb) - Autore originale e scopritore delle fasi
Dave Benham (dbenham) - Molto contenuto aggiuntivo e editing


4
Ciao Jeb, grazie per la tua comprensione ... Potrebbe essere difficile da capire, ma proverò a pensarci su! Sembra che tu abbia eseguito molti test! Grazie per la traduzione ( administrator.de/… )
Benoit

2
Fase batch 5) - %% a sarà già stato modificato in% a nella fase 1, quindi l'espansione for-loop espande davvero% a. Inoltre, ho aggiunto una spiegazione più dettagliata della fase batch 1 in una risposta di seguito (non ho il privilegio di modifica)
dbenham,

3
Jeb - forse la fase 0 potrebbe essere spostata e combinata con la fase 6? Questo ha più senso per me, o c'è un motivo per cui sono separati in quel modo?
dbenham,

1
@aschipfl - Ho aggiornato quella sezione. La )realtà non funzioni quasi come un REMcomando quando il contatore parentesi è 0. Prova entrambi questi dalla riga di comando: ) Ignore thiseecho OK & ) Ignore this
dbenham

1
@aschipfl sì, è corretto, quindi a volte vedi 'set "var =% expr%"! 'l'ultimo punto esclamativo sarà rimosso ma forza la fase 5
jeb

62

Quando si richiama un comando da una finestra di comando, la tokenizzazione degli argomenti della riga di comando non viene eseguita da cmd.exe( nota anche come "shell"). Molto spesso la tokenizzazione viene eseguita dal runtime C / C ++ dei processi appena formati, ma ciò non è necessariamente così - ad esempio, se il nuovo processo non è stato scritto in C / C ++ o se il nuovo processo sceglie di ignorare argved elaborare la riga di comando grezza per se stessa (ad es. con GetCommandLine ()). A livello di sistema operativo, Windows passa le righe di comando non tokenizzate come una singola stringa a nuovi processi. Ciò è in contrasto con la maggior parte delle shell * nix, in cui la shell tokenizza gli argomenti in modo coerente e prevedibile prima di passarli al processo appena formato. Tutto ciò significa che potresti riscontrare comportamenti di tokenizzazione degli argomenti selvaggiamente divergenti tra diversi programmi su Windows, poiché i singoli programmi spesso prendono in mano le tokenizzazioni degli argomenti.

Se sembra un'anarchia, lo è. Tuttavia, dal momento che un gran numero di programmi Windows fare utilizzare il Microsoft C / C ++ Runtime argv, può essere generalmente utile per capire come il MSVCRT tokenizza argomenti. Ecco un estratto:

  • Gli argomenti sono delimitati da uno spazio bianco, che è uno spazio o una scheda.
  • Una stringa racchiusa tra virgolette doppie viene interpretata come un singolo argomento, indipendentemente dallo spazio bianco contenuto all'interno. Una stringa tra virgolette può essere incorporata in un argomento. Si noti che il punto di inserimento (^) non è riconosciuto come carattere di escape o delimitatore.
  • Le virgolette doppie precedute da una barra rovesciata, \ ", vengono interpretate come virgolette letterali doppie (").
  • Le barre rovesciate vengono interpretate letteralmente, a meno che non precedano immediatamente una doppia virgoletta.
  • Se un numero pari di barre rovesciate è seguito da una virgoletta doppia, una barra rovesciata () viene inserita nella matrice argv per ogni coppia di barre rovesciate (\) e la virgoletta doppia (") viene interpretata come delimitatore di stringa.
  • Se un numero dispari di barre rovesciate è seguito da un doppio segno di virgolette, una barra rovesciata () viene inserita nella matrice argv per ogni coppia di barre rovesciate (\) e il segno di virgolette doppie viene interpretato come una sequenza di escape dalla barra rovesciata rimanente, causando una doppia virgoletta letterale (") da inserire in argv.

Il "linguaggio batch" di Microsoft ( .bat) non fa eccezione a questo ambiente anarchico e ha sviluppato regole uniche per la tokenizzazione e l'escaping. Sembra anche che il prompt dei comandi di cmd.exe esegua una preelaborazione dell'argomento della riga di comando (principalmente per la sostituzione e l'escape delle variabili) prima di passare l'argomento al nuovo processo in esecuzione. Puoi leggere di più sui dettagli di basso livello del linguaggio batch e del cmd che sfugge alle eccellenti risposte di jeb e dbenham in questa pagina.


Costruiamo una semplice utility da riga di comando in C e vediamo cosa dice sui tuoi casi di test:

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

(Note: argv [0] è sempre il nome dell'eseguibile ed è omesso di seguito per brevità. Testato su Windows XP SP3. Compilato con 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]

E alcuni dei miei test:

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

La ringrazio per la risposta. Mi sorprende ancora di più vedere che TinyPerl non produrrà i risultati del tuo programma, e ho difficoltà a capire come [a "b" c]potrebbe diventare la [a "b] [c]post-elaborazione.
Benoit,

Ora che ci penso, questa tokenizzazione della riga di comando è probabilmente fatta interamente dal runtime C. Un eseguibile potrebbe essere scritto in modo tale da non utilizzare nemmeno il runtime C, nel qual caso penso che dovrebbe avere a che fare con la riga di comando alla lettera, ed essere responsabile di fare la propria tokenizzazione (se lo volesse.) O anche se l'applicazione utilizza il runtime C, è possibile scegliere di ignorare argc e argv e ottenere semplicemente la riga di comando grezza tramite, ad esempio, Win32 GetCommandLine. Forse TinyPerl sta ignorando argv e semplicemente tokenizzando la riga di comando grezza con le sue stesse regole.
Mike Clark,

4
"Ricorda che dal punto di vista di Win32, la riga di comando è solo una stringa che viene copiata nello spazio degli indirizzi del nuovo processo. Il modo in cui il processo di avvio e il nuovo processo interpretano questa stringa è governato non da regole ma da convenzioni." -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark

2
Grazie per quella bella risposta. Questo spiega molto secondo me. E questo spiega anche perché a volte trovo davvero scadente lavorare con Windows ...
Benoit,

Ho trovato questo riguardo a barre rovesciate e citazioni durante la trasformazione da linea di comando a argv, per i programmi Win32 C ++. Il conteggio delle barre rovesciate è diviso per due solo quando l'ultima barra rovesciata è seguita da un dblquote e il dblquote termina una stringa quando c'è un numero pari di barre rovesciate prima.
Benoit,

47

Regole di espansione percentuale

Ecco una spiegazione estesa della fase 1 nella risposta di jeb (valida sia per la modalità batch che per la modalità riga di comando).

Fase 1) Espansione percentuale Partendo da sinistra, scansiona ogni personaggio alla ricerca di %o <LF>. Se trovato allora

  • 1.05 (troncare la riga a <LF>)
    • Se il personaggio è <LF>allora
      • Rilascia (ignora) il resto della riga dal <LF> poi
      • Goto Phase 1.5 (Strip <CR>)
    • Altrimenti il ​​personaggio deve essere %, quindi procedi al punto 1.1
  • 1.1 (escape %) ignorato se modalità riga di comando
    • Se in modalità batch e seguito da un altro, %quindi
      Sostituisci %%con singolo %e continua la scansione
  • 1.2 (argomento di espansione) ignorato se modalità riga di comando
    • Altrimenti in modalità batch quindi
      • Se seguito da *e le estensioni di comando sono abilitate,
        Sostituisci %*con il testo di tutti gli argomenti della riga di comando (Sostituisci con nulla se non ci sono argomenti) e continua la scansione.
      • Altrimenti se seguito da <digit>quindi
        Sostituisci%<digit> con valore di argomento (sostituire con niente se undefined) e continuare la scansione.
      • Altrimenti, se seguito da ~ed estensioni di comando sono abilitate allora
        • Se seguita da opzionale elenco valido di modificatori di argomento seguita da richieste <digit>poi
          Sostituire %~[modifiers]<digit>con valore dell'argomento modificato (sostituire con niente se non definito o se specificato $ PATH: modificatore non è definito) e continuare la scansione.
          Nota: i modificatori non fanno distinzione tra maiuscole e minuscole e possono apparire più volte in qualsiasi ordine, tranne $ PATH: il modificatore può apparire solo una volta e deve essere l'ultimo modificatore prima del<digit>
        • Altrimenti la sintassi dell'argomento modificato non valida genera un errore fatale: tutti i comandi analizzati vengono interrotti e l'elaborazione batch si interrompe se in modalità batch!
  • 1.3 (espandi variabile)
    • Altrimenti se le estensioni di comando sono disabilitate,
      guarda la stringa di caratteri successiva, che si rompe prima %o alla fine del buffer e chiamale VAR (potrebbe essere un elenco vuoto)
      • Se il personaggio successivo è %allora
        • Se VAR è definito,
          Sostituisci %VAR%con il valore di VAR e continua la scansione
        • Altrimenti se la modalità batch quindi
          Rimuovi %VAR%e continua la scansione
        • Altro vai 1.4
      • Altro vai 1.4
    • Altrimenti se le estensioni di comando sono abilitate,
      guarda la stringa di caratteri successiva, che si interrompe prima % :o alla fine del buffer e chiamale VAR (potrebbe essere un elenco vuoto). Se VAR si interrompe prima :e il carattere successivo viene %quindi incluso :come ultimo carattere in VAR e interrompi prima %.
      • Se il personaggio successivo è %allora
        • Se è definito VAR, quindi
          Sostituisci%VAR% con il valore di VAR e continua la scansione
        • Altrimenti se la modalità batch quindi
          Rimuovi %VAR%e continua la scansione
        • Altro vai 1.4
      • Altrimenti se il personaggio successivo è :allora
        • Se VAR non è definito, allora
          • Se la modalità batch, quindi
            Rimuovi %VAR:e continua la scansione.
          • Altro vai 1.4
        • Altrimenti se il personaggio successivo è ~allora
          • Se la prossima stringa di caratteri corrisponde a modello di [integer][,[integer]]%poi
            Sostituire %VAR:~[integer][,[integer]]%con sottostringa del valore di VAR (possibilmente con conseguente stringa vuota) e continuare la scansione.
          • Altro vai 1.4
        • Altrimenti se seguito da =o *=poi
          Ricerca di variabili non valide e sostituzione della sintassi generano errori fatali: tutti i comandi analizzati vengono interrotti e l'elaborazione batch si interrompe se in modalità batch!
        • Altrimenti se la stringa di caratteri successiva corrisponde al modello di [*]search=[replace]%, dove la ricerca può includere qualsiasi set di caratteri tranne =, e sostituisci può includere qualsiasi set di caratteri tranne %, quindi
          Sostituisci %VAR:[*]search=[replace]%con valore di VAR dopo aver eseguito la ricerca e sostituisci (eventualmente con conseguente stringa vuota) e continua scansione
        • Altro vai 1.4
  • 1.4 (% strip)
    • Altrimenti in modalità batch, quindi
      Rimuovi %e continua la scansione a partire dal carattere successivo dopo il%
    • Altrimenti preserva il comando %e continua la scansione a partire dal personaggio successivo dopo il comando conservato%

Quanto sopra aiuta a spiegare perché questo 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

Dà questi risultati:

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

Nota 1 - La fase 1 si verifica prima del riconoscimento delle istruzioni REM. Questo è molto importante perché significa che anche un'osservazione può generare un errore fatale se ha una sintassi di espansione dell'argomento non valida o una ricerca di variabili non valida e sostituisce la sintassi!

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

Nota 2 - Un'altra conseguenza interessante delle regole di analisi%: Le variabili contenenti: nel nome possono essere definite, ma non possono essere espanse a meno che le estensioni di comando non siano disabilitate. Esiste un'eccezione: un nome di variabile contenente un singolo punto alla fine può essere espanso mentre le estensioni di comando sono abilitate. Tuttavia, non è possibile eseguire la sottostringa o la ricerca e sostituire le operazioni sui nomi delle variabili che terminano con due punti. Il file batch seguente (per gentile concessione di jeb) dimostra questo comportamento

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

Nota 3 - Un risultato interessante dell'ordine delle regole di analisi che il jeb stabilisce nel suo post: Quando si esegue trova e sostituisci con espansione ritardata, i caratteri speciali in entrambi i termini trova e sostituisci devono essere evitati o citati. Ma la situazione è diversa per l'espansione percentuale: il termine find non deve essere sfuggito (sebbene possa essere citato). La percentuale di sostituzione della stringa può richiedere o meno la escape o la citazione, a seconda delle intenzioni.

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

Regole di espansione ritardate

Ecco una spiegazione estesa e più accurata della fase 5 nella risposta di jeb (valida sia per la modalità batch che per la riga di comando)

Fase 5) Espansione ritardata

Questa fase viene ignorata se si verifica una delle seguenti condizioni:

  • L'espansione ritardata è disabilitata.
  • Il comando si trova all'interno di un blocco tra parentesi su entrambi i lati di un tubo.
  • Il token di comando in arrivo è uno script batch "nudo", il che significa che non è associato CALL, blocco tra parentesi, qualsiasi forma di concatenazione di comando ( &, &&o ||) o pipe |.

Il processo di espansione ritardata viene applicato ai token in modo indipendente. Un comando può avere più token:

  • Il token di comando. Per la maggior parte dei comandi il nome del comando stesso è un token. Ma alcuni comandi hanno regioni specializzate che sono considerate un TOKEN per la fase 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, Dove il confronto è uno dei ==, equ, neq, lss, leq, gtr, ogeq
  • Token degli argomenti
  • Il token di destinazione del reindirizzamento (uno per reindirizzamento)

Non viene apportata alcuna modifica ai token che non contengono !.

Per ogni token che ne contiene almeno uno !, scansiona ogni personaggio da sinistra a destra per ^o !, e se trovato, quindi

  • 5.1 (fuga dal cursore) Necessario !o ^letterale
    • Se il personaggio è un cursore ^allora
      • Rimuovi il ^
      • Scansiona il personaggio successivo e conservalo come letterale
      • Continua la scansione
  • 5.2 (espandi variabile)
    • Se il personaggio è !, allora
      • Se le estensioni di comando sono disabilitate,
        guarda la stringa di caratteri successiva, che si rompe prima di !o <LF>e chiamale VAR (potrebbe essere un elenco vuoto)
        • Se il personaggio successivo è !allora
          • Se VAR è definito, quindi
            Sostituisci !VAR!con il valore di VAR e continua la scansione
          • Altrimenti se la modalità batch quindi
            Rimuovi!VAR! e continua la scansione
          • Else goto 5.2.1
        • Else goto 5.2.1
      • Altrimenti, se le estensioni ai comandi sono abilitate poi
        Guardate prossima stringa di caratteri, rompendo prima !, :o <LF>, e chiamarli VAR (può essere una lista vuota). Se VAR si interrompe prima :e il carattere successivo viene !quindi incluso :come ultimo carattere in VAR e interrompi prima!
        • Se il prossimo personaggio è !allora
          • Se esiste VAR, quindi
            Sostituisci!VAR! con il valore di VAR e continua la scansione
          • Altrimenti se la modalità batch quindi
            Rimuovi!VAR! e continua la scansione
          • Else goto 5.2.1
        • Altrimenti se il prossimo personaggio è :allora
          • Se VAR non è definito, allora
            • Se modalità batch, quindi
              Rimuovi!VAR: e continua la scansione
            • Else goto 5.2.1
          • Altrimenti se il personaggio successivo è ~allora
            • Se la stringa di caratteri successiva corrisponde al modello di [integer][,[integer]]!Sostituisci !VAR:~[integer][,[integer]]!con sottostringa del valore di VAR (eventualmente risultante in stringa vuota) e continua la scansione.
            • Else goto 5.2.1
          • Altrimenti se la stringa di caratteri successiva corrisponde al modello di [*]search=[replace]!, dove la ricerca può includere qualsiasi set di caratteri tranne =, e sostituisci può includere qualsiasi set di caratteri tranne !, quindi
            Sostituisci !VAR:[*]search=[replace]!con valore di VAR dopo aver eseguito la ricerca e sostituire (eventualmente risultando in una stringa vuota) e continua la scansione
          • Else goto 5.2.1
        • Else goto 5.2.1
      • 5.2.1
        • Se la modalità batch quindi rimuove il comando !
          Else, conserva il comando!
        • Continua la scansione iniziando con il carattere successivo dopo il comando conservato !

3
+1, Qui mancano solo la sintassi e le regole del colon per %definedVar:a=b%vs %undefinedVar:a=b%e le %var:~0x17,-010%forme
jeb

2
Un buon punto: ho ampliato la sezione di espansione variabile per rispondere alle tue preoccupazioni. Ho anche ampliato la sezione di espansione degli argomenti per riempire alcuni dettagli mancanti.
dbenham,

2
Dopo aver ottenuto un ulteriore feedback privato da jeb, ho aggiunto una regola per i nomi delle variabili che terminano con i due punti e ho aggiunto la nota 2. Ho anche aggiunto la nota 3 semplicemente perché pensavo fosse interessante e importante.
dbenham,

1
@aschipfl - Sì, ho considerato di approfondire questo aspetto, ma non volevo scendere nella tana del coniglio. Ero intenzionalmente non impegnativo quando ho usato il termine [intero]. Ci sono più informazioni su Regole su come CMD.EXE analizza i numeri .
dbenham

1
Mi mancano le regole di espansione per il contesto cmd, come se non ci fossero caratteri riservati per il primo carattere del nome della variabile come %<digit>, %*o %~. E il comportamento cambia per variabili non definite. Forse devi aprire una seconda risposta
jeb

7

Come sottolineato, ai comandi viene passata l'intera stringa di argomenti in μSoft land e spetta a loro analizzarlo in argomenti separati per il proprio uso. Non c'è coerenza in questo tra diversi programmi, e quindi non esiste un insieme di regole per descrivere questo processo. Hai davvero bisogno di controllare ogni caso d'angolo per qualsiasi libreria C utilizzata dal tuo programma.

Per quanto riguarda i .batfile di sistema , ecco quel 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

Ora possiamo eseguire alcuni test. Vedi se riesci a capire esattamente cosa μSoft sta cercando di fare:

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

Bene finora. (Lascerò fuori l'interessante %cmdcmdline%e %0d'ora in poi.)

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

Nessuna espansione del nome file.

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

Nessuna rimozione delle virgolette, anche se le virgolette impediscono la divisione degli argomenti.

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

Le doppie virgolette consecutive fanno perdere loro tutte le abilità di analisi speciali che potrebbero aver avuto. @ Esempio di Beniot:

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

Quiz: come si passa il valore di qualsiasi ambiente var come singolo argomento (es. Come%1 ) a un file 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!

L'analisi sana sembra interrotta per sempre.

Per il vostro divertimento, prova ad aggiungere vari ^, \, ', &(ecc.) Caratteri a questi esempi.


Per passare% t% come singolo argomento, puoi usare "% t:" = \ "%" Ossia, usa la sintassi% VAR: str = sostituzione% per l'espansione variabile. Metacaratteri Shell come | e & nel contenuto variabile può comunque essere esposto e rovinare la shell, a meno che non si sfuggano di nuovo ....
Toughy,

@Toughy Quindi, nel mio esempio, lo tè a "b c. Avete una ricetta per ottenere quei 6 caratteri ( a, 2 × spazio, ", b, e c) per apparire come %1all'interno di una .cmd? Mi piace il tuo pensiero però. args "%t:"=""%"è abbastanza vicino :-)
bobbogo

5

Hai già delle ottime risposte sopra, ma per rispondere a una parte della tua domanda:

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

Ciò che sta accadendo è che, poiché si dispone di uno spazio prima di =, viene creata una variabile chiamata %a<space>% così quando echo %a %viene valutata correttamente come b.

La parte rimanente b% c%viene quindi valutata come testo normale + una variabile non definita % c%, che deve essere ripetuta come digitata, per me echo %a %b% c%restituiscebb% c%

Sospetto che la possibilità di includere spazi nei nomi delle variabili sia più una svista che una "caratteristica" pianificata


0

modifica: vedi la risposta accettata, ciò che segue è sbagliato e spiega solo come passare una riga di comando a TinyPerl.


Per quanto riguarda le citazioni, ho la sensazione che il comportamento sia il seguente:

  • Quando un " viene trovato un, inizia il globbing di stringa
  • quando si verifica lo sfregamento delle stringhe:
    • ogni personaggio che non è un " è è turbato
    • Quando un " viene trovato un:
      • se è seguito da ""(quindi una tripla" ), alla stringa viene aggiunta una doppia virgoletta
      • se è seguito da "(quindi un doppio" ), viene aggiunta una doppia virgoletta alla stringa e alle estremità del globbing della stringa
      • se il personaggio successivo non lo è ", il globbing delle stringhe termina
    • quando la linea termina, il globbing delle corde termina.

In breve:

"a """ b "" c"""è costituito da due stringhe: a " b "ec"

"a"", "a"""E "a""""sono tutti uguali stringa se alla fine di una linea


il tokenizer e il globbing di stringa dipendono dal comando! Un "set" funziona diversamente da un "call" o addirittura un "if"
jeb

si, ma per quanto riguarda i comandi esterni? Immagino che cmd.exe passi sempre gli stessi argomenti a loro?
Benoit,

1
cmd.exe passa sempre il risultato di espansione come stringa e non i token a un comando esterno. Dipende dal comando esterno come fuggire e tokenizzare, findstr usa la barra rovesciata il prossimo può usare qualcos'altro
jeb

0

Si noti che Microsoft ha pubblicato il codice sorgente del suo terminale. Potrebbe funzionare in modo simile alla riga di comando rispetto all'analisi della sintassi. Forse qualcuno è interessato a testare le regole di analisi retroingegnerizzate in conformità con le regole di analisi del terminale.

Link al codice sorgente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.