2009-05-25 11 views
30

Sto eseguendo un file batch a esecuzione prolungata. Ora mi rendo conto che devo aggiungere altri comandi alla fine del file batch (nessuna modifica al contenuto esistente, solo alcuni comandi extra). È possibile farlo, dato che la maggior parte dei file batch vengono letti in modo incrementale ed eseguiti uno per uno? O il sistema legge l'intero contenuto del file e quindi esegue il lavoro?Modifica di un file batch quando è in esecuzione

+3

che ami devi risposte così veloce. Hai già iniziato a eseguire il batch> ha postato una domanda> hai ottenuto una risposta> hai modificato il tuo file prima che l'esecuzione fosse completata! –

+0

Si noti inoltre che quando il file batch viene rimosso o rinominato, nel momento in cui l'istruzione corrente ha terminato, viene generato un errore: "Impossibile trovare il file batch". –

risposta

28

ho appena provato, e contro la mia intuizione, che raccolse i nuovi comandi alla fine (su Windows XP)

ho creato un file batch che contiene

echo Hello 
pause 
echo world 

ho eseguito il file, e mentre era in pausa, ha aggiunto

echo Salute 

salvato e premuto Invio per proseguire, la pausa, tutte e tre le richieste hanno fatto eco alla console.

Quindi, prendilo!

15

L'interprete dei comandi ricorda la posizione della linea nel file batch. Finché si modifica il file batch dopo la posizione corrente della linea di esecuzione, tutto andrà bene.

Se lo modifichi prima, inizierà a fare cose strane (comandi ripetuti ecc.).

+1

È documentato ovunque, per favore, o è questo dalla tua stessa esperienza? – Benoit

+2

Questo è vero nella mia esperienza. – UnhandledExcepSean

+0

In realtà, ciò che farà è che il puntatore del parser rimarrà allo stesso indice nel file, quindi aggiungendo/rimuovendo il testo prima che l'indice sposterà ciò che è sotto il puntatore dell'istruzione. Succedono cose strane, davvero. – cat

3

Quasi come rein ha detto, cmd.exe ricorda la posizione del file (non solo la posizione della linea) che è attualmente, e anche per ogni chiamata spinge la posizione del file su uno stack invisibile.

Ciò significa, è possibile modificare il file mentre è in esecuzione dietro e davanti la posizione di file effettivo, è necessario solo quello che fai ...

Un piccolo campione di un lotto automodificante
cambia la linea set value=1000 esempio continuamente

@echo off 
setlocal DisableDelayedExpansion 
:loop 
REM **** the next line will be changed 
set value=1000 
rem *** 
echo ---------------------- 
echo The current value=%value% 
<nul set /p ".=Press a key" 
pause > nul 
echo(
(
call :changeBatch 
rem This should be here and it should be long 
) 
rem ** It is neccessary, that this is also here! 
goto :loop 
rem ... 
:changeBatch 
set /a n=0 
set /a newValue=value+1 
set /a toggle=value %% 2 
set "theNewLine=set value=%newValue%" 
if %toggle%==0 (
    set "theNewLine=%theNewLine% & rem This adds 50 byte to the filesize.........." 
) 
del "%~f0.tmp" 2> nul 
for /F "usebackq delims=" %%a in ("%~f0") DO (
    set /a n+=1 
    set "line=%%a" 
    setlocal EnableDelayedExpansion 
    if !n!==5 (
     (echo !theNewLine!) 
    ) ELSE (
     (echo !line!) 
    ) 
    endlocal 
) >> "%~f0.tmp" 
(
    rem the copy should be done in a parenthesis block 
    copy "%~f0.tmp" "%~f0" > nul 
    if Armageddon==TheEndOfDays (
    echo This can't never be true, or is it? 
) 
) 
echo The first line after the replace action.... 
echo The second line comes always after the first line? 
echo The current filesize is now %~z0 
goto :eof 
+0

+1 Questo è un divertente esempio di ciò che è possibile :) – dbenham

7

di Jeb è molto divertente, ma è molto dipendente dalla lunghezza del testo che viene aggiunto o eliminato. Penso che i risultati contro-intuitivi siano ciò che intendeva dire quando ha detto "Se lo modifichi prima inizierà a fare cose strane (comandi ripetuti ecc.)".

Ho modificato il codice di jeb per mostrare in che modo il codice dinamico di lunghezza variabile può essere modificato liberamente all'inizio di un file batch in esecuzione purché sia ​​inserito il riempimento appropriato. L'intera sezione dinamica è completamente sostituita da ogni iterazione. Ogni linea dinamica è preceduta da un prefisso non interferente ;. Ciò consente comodamente a FOR /F di rimuovere il codice dinamico a causa dell'opzione implicita EOL=;.

Invece di cercare un particolare numero di riga, cerco un commento specifico per individuare dove inizia il codice dinamico. Questo è più facile da mantenere.

Uso linee di uguale segno per riempire in modo innocuo il codice per consentire espansione e contrazione. È possibile utilizzare qualsiasi combinazione dei seguenti caratteri: virgola, punto e virgola, uguale, spazio, scheda e/o nuova riga. (Ovviamente il padding non può iniziare con un punto e virgola). I segni di uguale all'interno delle parentesi consentono l'espansione del codice. I segni di uguale dopo le parentesi consentono la contrazione del codice.

Si noti che FOR /F strisce righe vuote.Questa limitazione potrebbe essere superata utilizzando FINDSTR per prefissare ciascuna riga con il numero di riga e quindi rimuovere il prefisso all'interno del ciclo. Ma il codice extra rallenta le cose, quindi non vale la pena farlo a meno che il codice non dipenda da linee vuote.

@echo off 
setlocal DisableDelayedExpansion 
echo The starting filesize is %~z0 
:loop 
echo ---------------------- 
::*** Start of dynamic code *** 
;set value=1 
::*** End of dynamic code *** 
echo The current value=%value% 
:: 
::The 2 lines of equal signs amount to 164 bytes, including end of line chars. 
::Putting the lines both within and after the parentheses allows for expansion 
::or contraction by up to 164 bytes within the dynamic section of code. 
(
    call :changeBatch 
    ============================================================================== 
    ============================================================================== 
) 
================================================================================ 
================================================================================ 
set /p "quit=Enter Q to quit, anything else to continue: " 
if /i "%quit%"=="Q" exit /b 
goto :loop 
:changeBatch 
(
    for /f "usebackq delims=" %%a in ("%~f0") do (
    echo %%a 
    if "%%a"=="::*** Start of dynamic code ***" (
     setlocal enableDelayedExpansion 
     set /a newValue=value+1, extra=!random!%%9 
     echo ;set value=!newValue! 
     for /l %%n in (1 1 !extra!) do echo ;echo extra line %%n 
     endlocal 
    ) 
) 
) >"%~f0.tmp" 
:: 
::The 2 lines of equal signs amount to 164 bytes, including end of line chars. 
::Putting the lines both within and after the parentheses allows for expansion 
::or contraction by up to 164 bytes within the dynamic section of code. 
(
    move /y "%~f0.tmp" "%~f0" > nul 
    ============================================================================== 
    ============================================================================== 
) 
================================================================================ 
================================================================================ 
echo The new filesize is %~z0 
exit /b 

I lavori di cui sopra, ma le cose sono molto più facile se il codice dinamico viene spostato in una subroutine alla fine del file. Il codice può espandersi e contrarsi senza limitazioni e senza necessità di riempimento. FINDSTR è molto più veloce di FOR/F nella rimozione della porzione dinamica. Le linee dinamiche possono essere tranquillamente precedute da un punto e virgola (comprese le etichette!). Quindi l'opzione FINDSTR/V viene utilizzata per escludere le righe che iniziano con un punto e virgola e il nuovo codice dinamico può essere semplicemente aggiunto.

@echo off 
setlocal DisableDelayedExpansion 
echo The starting filesize is %~z0 

:loop 
echo ---------------------- 
call :dynamicCode1 
call :dynamicCode2 
echo The current value=%value% 
call :changeBatch 
set /p "quit=Enter Q to quit, anything else to continue: " 
if /i "%quit%"=="Q" exit /b 
goto :loop 

:changeBatch 
(
    findstr /v "^;" "%~f0" 
    setlocal enableDelayedExpansion 
    set /a newValue=value+1, extra=!random!%%9 
    echo ;:dynamicCode1 
    echo ;set value=!newValue! 
    echo ;exit /b 
    echo ; 
    echo ;:dynamicCode2 
    for /l %%n in (1 1 !extra!) do echo ;echo extra line %%n 
    echo ;exit /b 
    endlocal 
) >"%~f0.tmp" 
move /y "%~f0.tmp" "%~f0" > nul 
echo The new filesize is %~z0 
exit /b 

;:dynamicCode1 
;set value=33 
;exit /b 
; 
;:dynamicCode2 
;echo extra line 1 
;exit /b 
+1

EDIT - Ho modificato l'ultimo codice per dimostrare che anche le etichette possono essere prefissate, in modo che possano essere facilmente incluse nel codice dinamico. – dbenham

2

Risposta breve: sì, i file batch possono modificarsi durante l'esecuzione. Come altri hanno già confermato.

Anni e anni fa, prima che Windows 3, il posto ho lavorato aveva un sistema di menu inhouse in MS-DOS. Il modo in cui gestiva le cose era abbastanza elegante: in realtà veniva eseguito da un file batch che il programma principale (scritto in C) è stato modificato per poter eseguire gli script. Questo trucco significava che il programma di menu non occupava spazio nella memoria mentre le selezioni erano in esecuzione. E questo includeva cose come il programma LAN Mail e il programma del terminale 3270.

Ma l'esecuzione da un file batch auto-modificante implicava che i suoi script potevano anche fare cose come caricare programmi TSR e in effetti potevano fare praticamente tutto ciò che si poteva inserire in un file batch. Il che ha reso molto potente. Solo il comando GOTO non ha funzionato, fino a quando l'autore non ha capito come rendere il file batch riavviato da solo per ogni comando.

2

L'interprete dei comandi sembra ricordare l'offset di byte all'interno di ogni file di comando che sta leggendo, ma il file stesso non è bloccato, quindi è possibile apportare modifiche, ad esempio con un editor di testo, mentre è in esecuzione.

Se viene apportata una modifica al file dopo questa posizione ricordato, l'interprete dovrebbe felicemente continuare a eseguire lo script ora modificato. Tuttavia, se la modifica viene apportata prima di quel punto e tale modifica modifica la lunghezza del testo in quel punto (ad esempio, hai inserito o rimosso del testo), quella posizione memorizzata ora non si riferisce più all'inizio di quel prossimo comando . Quando l'interprete prova a leggere la "linea" successiva, prenderà invece una linea diversa, o forse parte di una linea, a seconda di quanto testo è stato inserito o rimosso. Se sei fortunato, probabilmente non sarà in grado di elaborare qualsiasi parola su cui è successo, dare un errore e continuare ad eseguire dalla riga successiva - ma probabilmente non è quello che vuoi.

Tuttavia, con la comprensione di quello che sta succedendo, è possibile strutturare gli script per ridurre il rischio. Ho degli script che implementano un semplice sistema di menu, visualizzando un menu, accettando input dall'utente utilizzando il comando choice e quindi elaborando la selezione. Il trucco consiste nel garantire che il punto in cui lo script attende l'input sia vicino alla parte superiore del file, in modo che tutte le modifiche che potresti voler apportare si verifichino dopo quel punto e quindi non abbiano impatti sgradevoli.

Esempio:

:top 
call :displayMenu 
:prompt 
REM The script will spend most of its time waiting here. 
choice /C:1234 /N "Enter selection: " 
if ERRORLEVEL == 4 goto DoOption4 
if ERRORLEVEL == 3 goto DoOption3 
if ERRORLEVEL == 2 goto DoOption2 
goto DoOption1 
:displayMenu 
(many lines to display menu) 
goto prompt 
:DoOption1 
(many lines to do Option 1) 
goto top 
:DoOption2 
(many lines to do Option 2) 
goto top 
(etc) 
Problemi correlati