Batch script read a file that's continously written - windows

I have a file that's being continuously written with information like this
HU/gen/tcfg/target/jacinto/starter/starter_b1.cfg
32.77 34% 141.59kB/s 0:00:00
94.64 100% 405.35kB/s 0:00:00 (xfer#60, to-check=1002/1097)
It is the output of rsync tool, that copies a folder to another path
I'm trying to write a batch script that reads this file and calculates the total amount of data being copied, focusing on this line
94.64 100% 405.35kB/s 0:00:00 (xfer#60, to-check=1002/1097)
the number 94.64 being the file size in bytes, so I'm guessing I should extract whatever is before "100%" from the line and add it
But I don't know how to continuously read the file while it's being written at the same time
Can somebody help?
Thanks

Here is an example that shows how to read a file while it is being written by another process. This is very similar to what jeb shows, except I have added a generic test to see if the process is complete. It assumes the process maintains a lock on the file throughout the entire execution, and it also assumes 20 consecutive empty lines indicates either the end of file, or else we are waiting for more output from the process. The 20 empty line threshold could be set to any number that works for you.
This solution may not work properly if the process writes partial lines to the file. I believe it is reliable if the the process always writes each line in its entirety in a single operation. Also, the lines must be less than or equal to 1021 bytes long, and they must be terminated by carriage return, linefeed.
#echo off
setlocal enableDelayedExpansion
set "file=test.txt"
set emptyCount=0
del "%file%
:: For this test, I will start an asynchronous process in a new window that
:: writes a directory listing to an output file 3 times, with a 5 second pause
:: between each listing.
start "" cmd /c "(for /l %%N in (1 1 3) do #dir & ping localhost -n 6 >nul) >"%file%""
:wait for the output file to be created and locked
if not exist "%file%" goto :wait
:: Now read and process the file, as it is being written.
call :read <"%file%"
exit /b
:read
set "ln="
set /p "ln="
if defined ln (
REM Process non-empty line here - test if correct line, extract size, and add
REM I will simply echo the line instead
echo(!ln!
REM Reset emptyCount for end of file test and read next line
set /a emptyCount=0
goto :read
) else ( REM End of file test
REM Count how many consecutive empty lines have been read.
set /a emptyCount+=1
REM Assume that 20 consecutive empty lines signifies either end of file
REM or else waiting for output. If haven't reached 20 consectutive
REM empty lines, then read next line.
if !emptyCount! lss 20 goto :read
REM Test if output file is still locked by attempting to redirect an unused
REM stream to the file in append mode. If the redirect fails, then the file
REM is still locked, meaning we are waiting for the process to finish and have
REM not reached the end. So wait 1 sec so as not to consume 100% of CPU, then
REM reset count and try to read the next line again.
REM If the file is not locked, then we are finished, so simply fall through
REM and exit.
(echo off 9>>"%file%")2>nul || (
set emptyCount=0
ping localhost -n 2 >nul
goto :read
)
)
exit /b

Like dbenham said, it can be done, but batch isn't the first choice.
Attention, the sample will run in an endless loop.
You need to add a break condition, depending of the file content.
I add the ping ... command to avoid, that the process consumes 100% CPU-time
#echo off
setlocal disabledelayedexpansion
setlocal enableDelayedExpansion
< rsyncOutput.tmp (
call :readData
)
echo ready
exit /b
:readDataX
set "line="
set /p line=
if defined line (
for /f "tokens=1,2 delims= " %%a in ("!line!") do (
if "%%b"=="100%%" echo Bytes %%a
)
)
ping localhost -n 2 > nul
goto :readDataX
exit /b

Related

Display output in a certain way using echo of batch

I am working on a program using batch where the program read root directories from a text file and count total number of Folders and Files in all th give root directories. The program is working as it should be but I want to display the output in a certain way.
This is how I want to display output
0 : OF : 6
The first value should change each time program finish counting in one root directory. I have written code for it but the output I am getting is this.
Here is code I have written to change it.
:textUpdate
echo !counter! : OF : %number%
GOTO :EOF
where counter is the current number of root directory and number is total number of directories found in the text file. Is there any way to display the output like the first one.
you can abuse set /p to write to screen. It doens't append a line feed. You also need a Carriage Return to go back to the beginning of the line to overwrite the old output:
#echo off
setlocal EnableDelayedExpansion
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
for /l %%n in (1,1,6) do (
set /P "=Count %%n of 6!CR!" <nul
timeout 1 >nul
)
The first for /f loop is just to get a CR (Carriage Return). You have to use delayed expansion to be able to use it (%CR% does not work).

Windows batch script to collate log files into master log under condition

I’m new to batch scripting and thanks to stackoverflow, I’m able to put together a script with no time
I’m working on a batch script which triggers other batch scriptlets in parallel (with combination of start + cmd) generating individual logs, wait for them to complete and collate them into a master log.
The condition i’m using is that every log file ends with a keyword “Elapsed” the master scripts checks if each log ends with the keyword and moves the log to the masterlog, else moves to a timeout state and repeats the process again. It work fine for the first attempts but fails read the last line of rest of the files (ch2.log ch3.log and ch4.log) and copies them without checking. Could you please let me know what I am missing?
Here is the part of the script which has the logic
for /f %%i in ('dir /b ch*.log') do (
REM display the list of logs (in this case it's ch1.log ch2.log ch3.log ch4.log)
set %fname% =%%i
:ctout
timeout 20>nul
REM wait until the timer runs out
for /f delims ^=^ eol^=%%l in (%fname%) do set lastline=%%l
REM check for the last line of the file and set the last line of the log as 'lastline'
echo %lastline% | findstr /i "\<Elapsed\>" >null && set var=elapsed
REM check if the lastline has the word "Elapsed", which marks the end of file and assign a dummy variable
if not "%var%"="elapsed" goto :ctout
REM check if the variable is "elapsed" else goto ctout
type %fname% >> masterlog.txt
REM if the condition satisfies the contents of ch1.log is moved to masterlog.txt
del /s %fname% >nul 2>nul
REM deletes the logs from the list and moves to the next log file
)
for /f %%i in ('dir /b ch*.log') do (
REM display the list of logs (in this case it's ch1.log ch2.log ch3.log ch4.log)
call :wait "%%i"
)
rem ... any remaining instructions after concatenating logs here
goto :eof
rem Jumps over the internal subroutine ":wait"
:wait
timeout 20>nul
REM wait until the timer runs out
for /f "usebackq" %%l in (%1) do set lastline=%%l
REM check for the last line of the file and set the last line of the log as 'lastline'
set "var="
echo %lastline% | findstr /i "\<Elapsed\>" >nul && set var=elapsed
REM check if the lastline has the word "Elapsed", which marks the end of file and assign a dummy variable
if not "%var%"=="elapsed" goto wait
REM check if the variable is "elapsed" else goto wait
>> masterlog.txt type %1
REM if the condition satisfies the contents of ch?.log is moved to masterlog.txt
del /s %1 >nul 2>nul
REM deletes the logs from the list and moves to the next log file
goto :eof
Note: Since the filename is being supplied to the :wait routine as a quoted string, %1 will be quoted hence requirement for usebackq.
eol and delims are not required as the default delims includes Space
set var to nothing before the test as its value will persist. the first file will set var to not-empty, so it needs t be cleared before the second file is tested. The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned.
the destination for findstr redirection should be nul, not null - you will find that a file named null was created.
The comparison operator in an if is == not =.

execute batch files in parallel and get exit code from each

How can I execute set of batch files from single batch file in parallel and get the exit code from each. When I use start it executes the batch file in parallel (new cmd window) but don't return the exit code from each. And while using call, I can get the exit code but the batch file execution happens sequentially. I have following code:
ECHO ON
setlocal EnableDelayedExpansion
sqlcmd -S server_name -E -i select_code.sql >\path\output.txt
for /f "skip=2 tokens=1-3 delims= " %%a in ('findstr /v /c:"-" \path\output.txt') do (
echo $$src=%%a>\path1\%%c.prm
echo $$trg=%%b>>\path1\%%c.prm
set param_name=%%c
start cmd \k \path\exec_pmcmd_ctrm.bat workflow_name %%param_name%%
ping 1.1.1.1 -n 1 -w 5000 > nul
set exitcode="%V_EXITCODE%"
echo %exitcode%>>\path\exitcode.txt
)
This executes the exec_pmcmd_ctrm.bat 3 times with different variable in parallel , but I am unable to get the exit code from each execution. I tried using call but then I miss the parallel execution of bat file. Any help in this regard?
First of all, the "exit code" from another Batch file (that ends with exit /B value command) is taken via %ERRORLEVEL% variable (not %V_EXITCODE%). Also, if such a value changes inside a FOR loop, it must be taken via Delayed Expansion instead: !ERRORLEVEL! (and EnableDelayedExpansion at beginning of your program). However, these points don't solve your problem because there is a misconception here...
When START command is used (without the /WAIT switch), a parallel cmd.exe process start execution. This means that there is not a direct way that the first Batch file could know in which moment the parallel Batch ends in order to get its ERRORLEVEL at that point! There is not a "wait for a started Batch file" command, but even if it would exist, it don't solve the problem of have several concurrent Batch files. In other words, your problem can not be solved via direct commands, so a work around is necessary.
The simplest solution is that the parallel Batch files store their ERRORLEVEL values in a file that could be later read by the original Batch file. Doing that imply a synchronization problem in order to avoid simultaneous write access to the same file, but that is another story...
Here is a pure batch-file solution. Basically, this script executes all batch files located in the same directory simultaneously, where the exit code (ErrorLevel) of each one is written to an individual log file (with the same name as the batch file and extension .log); these files are checked for existence; as soon as such a log file is found, the stored exit code is read and copied into a summary log file, together with the respective batch file name; as soon as all log files have been processed, this script is terminated; the exit code of this script is zero only if all the exit codes of the executed batch files are zero too. So here is the code -- see all the explanatory rem remarks:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Collect available scripts in an array:
set /A "INDEX=0"
for %%J in ("%~dp0*.bat" "%~dp0*.cmd") do (
if /I not "%%~nxJ"=="%~nx0" (
set "ITEM=%%~fJ"
call set "$BATCH[%%INDEX%%]=%%ITEM%%"
set /A "INDEX+=1"
)
)
rem // Execute scripts simultaneously, write exit codes to individual log files:
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
start "" /MIN cmd /C rem/ ^& "%%~fJ" ^& ^> "%%~dpnJ.log" call echo %%^^ErrorLevel%%
)
rem // Deplete summary log file:
> "%~dpn0.log" rem/
rem // Polling loop to check whether individual log files are available:
:POLLING
rem // Give processor some idle time:
> nul timeout /T 1 /NOBREAK
rem /* Loop through all available array elements; for every accomplished script,
rem so its log file is availabe, the related array element becomes deleted,
rem so finally, there should not be any more array elements defined: */
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
rem // Suppress error message in case log file is not yet available:
2> nul (
rem // Read exid code from log file:
set "ERRLEV=" & set "FILE="
< "%%~dpnJ.log" set /P ERRLEV=""
if defined ERRLEV (
rem // Copy the read exit code to the summary log file:
set "NAME=%%~nxJ"
>> "%~dpn0.log" call echo(%%ERRLEV%% "%%NAME%%"
rem // Undefine related array element:
set "%%I="
rem // Store log file path for later deletion:
set "FILE=%%~dpnJ.log"
)
rem // Delete individual log file finally:
if defined FILE call del "%%FILE%%"
)
)
rem // Jump to polling loop in case there are still array elements:
> nul 2>&1 set $BATCH[ && goto :POLLING
rem // Check individual exit codes and return first non-zero value, if any:
set "ERRALL="
for /F "usebackq" %%I in ("%~dpn0.log") do (
if not defined ERRALL if %%I neq 0 set "ERRALL=%%I"
)
if not defined ERRALL set "ERRALL=0"
endlocal & exit /B %ERRALL%
This approach is quite similar to the one I used in the following Improving Batch File for loop with start subcommand, but there the outputs of simultaneously executed commands are collected.

Batch File to Loop a VBScript During Certain Times of the Day

I am very new to batch, so please be easy. That being said, I am attempting to write a batch file that every few minutes will execute a VBScript that updates information on our network. Currently I have:
#ECHO OFF
REM Run VBScript
GOTO SKIP01
:LOOP
wscript "C:\Users\Desktop\RenameMove.vbs"
GOTO SKIP01
:
:
:SKIP01
REM 3 Min Delay
PING 1.1.1.1 -n 10 -w 18000 >NUL
IF HOUR:%time:~0,5% <09:44
GOTO SKIP01
IF HOUR:%time:~0,5% >16:00
GOTO SKIP01
IF HOUR:%time:~0,5% >=09:45
GOTO LOOP
Currently the window will open for a minute or two, then it just closes. If I run the above after removing the first GOTO SKIP01, the VBScript executes perfectly. Then there is a delay and the window closes. I imagine that it is everything under :SKIP01 that is causing the problem. The function I am trying to achieve is for the .BAT file to continuously loop a delay between the hours of 16:01 - 09:44. Then run the VBScript every 3 minutes from 09:45 - 16:00. Searching the forums I have yet to find anything to help. As I said, I am very new to Batch.
RE: My last comment below:
#ECHO OFF
setlocal enableextensions disabledelayedexpansion
set delay=180
set "startTime=09:45"
set "endTime=16:00"
:LOOP
set "now=%time: =0%"
:: ECHO It's %now%, waiting %delay% seconds
echo %date% %time% >> log.txt
if "%now%" geq "%startTime%:00,00" (
GOTO LOOP2
)
:LOOP2
set "now=%time: =0%"
:: ECHO It's %now%, waiting %delay% seconds
echo %date% %time% >> log.txt
if "%now%" lss "%endTime%:00,00" (
:: ECHO Start MSAccess
GOTO CALLVBS
)
:WAIT
REM 10 second delay
PING 127.0.0.1 -n %delay% >REM
GOTO LOOP
:CALLVBS
echo Time to Move!
wscript "C:\Users\ljs\Desktop\Stock_Automation_DO_NOT_EDIT\RenameMove.vbs"
GOTO WAIT
I renamed some things so it made more sense to me, but here is a working version. there are variables at the top for the delay (in seconds) and the start and end time for the window. I also modified your ping delay to something more simple, in my opinion.
Remove the echos for production and add the vbscript file to the CALLVBS function.
#ECHO OFF
setlocal enableextensions disabledelayedexpansion
set delay=180
set "startTime=09:45"
set "endTime=16:00"
:LOOP
set "now=%time: =0%"
ECHO It's %now%, waiting %delay% seconds
if "%now%" geq "%startTime%:00,00" (
ECHO It's after %startTime%
if "%now%" lss "%endTime%:00,00" (
ECHO And it's before %endTime%
GOTO CALLVBS
)
)
:WAIT
REM 10 second delay
PING 127.0.0.1 -n %delay% >REM
GOTO LOOP
:CALLVBS
echo It's time!
REM Call VBS here
GOTO WAIT
Here's what's happening, as explained by someone who barely knows what he's talking about:
ABOUT GOTO
First you need to know that batch files process line-by-line from top to bottom, unless it encounters certain statements like if, for or goto, the last of which being the one we are concerned with here. If the interpreter encounters a GOTO command, it will go to the corresponding label and resume processing code line by line until it finds another GOTO or gets to the end of the file.
SIMPLE EXAMPLE
#echo off
GOTO :FRUITS
:COLORS
echo Red
echo Green
GOTO :END
:FRUITS
echo Apple
echo Banana
GOTO :COLORS
:END
echo Done!
This outputs the following:
Apple
Banana
Red
Green
Done!
BREAKDOWN
Set up variables
#ECHO OFF
setlocal enableextensions disabledelayedexpansion
set delay=180
set "startTime=09:45"
set "endTime=16:00"
This sets some settings and creates some variables for use later. The variables should be self explanatory but
I can elaborate if you want.
check the time
01. :LOOP
02. set "now=%time: =0%"
03. ECHO It's %now%, waiting %delay% seconds
04. if "%now%" geq "%startTime%:00,00" (
05. ECHO It's after %startTime%
06. if "%now%" lss "%endTime%:00,00" (
07. ECHO And it's before %endTime%
08. GOTO CALLVBS
09. )
10. )
This is our "loop". :LOOP denotes what is basically a labeled
section of code that we can go back to any time we want. I called it LOOP
because it is the section we are doing over and over. It may have been more
accurate to call it :CHECKTIME or something similar, as that's what it does.
The label means absolutely nothing to the interpreter so calling it "LOOP"
doesn't mean it's going to repeat. This may be the biggest source of confusion.
Here is a
step-by-step of what each line on this block does (not processing
conditions, just line by line):
Get the current time.
Output the current time
Compare the current time to the endTime variable.
Output the result.
Compare the current time to the startTimevariable.
Output the result.
Goto the CALLVBS section of code
Note that I could have put GOTO WAIT at the end of this block and that might make more sense, but since :WAIT is the next block of
code that's what will process next anyway, so the GOTO would be
superfluous! This may be a second point of confusion.
Wait a bit
:WAIT
REM 10 second delay
PING 127.0.0.1 -n %delay% >REM
GOTO LOOP
This is the section of code that simply waits the specified number of
seconds. It does this using the ping command which is common for
batch programming since there is no built-in delay or sleep command
like other languages have. The first line is simply a comment, REM
means "Remove" (I think) and is how you comment out lines of code in
batch. As a matter of fact, I should have removed this since it's
not 10 seconds anyway :). The second line pings the localhost 180
times (or whatever the delay variable is set to). The >REM part
means it outputs the results of the ping to, well, I've never seen
"REM" here. Usually you would output it to nul but either way, it's
making sure you don't see the 180 ping results. Now, the 3rd line
tells the processor to go back to the :LOOP label. No matter what.
After the ping, it does the :LOOP section code again.
Execute your vbscript
:CALLVBS
echo It's time!
REM Call VBS here
GOTO WAIT
This is the :CALLVBS section of code. First it outputs "It's
time!". The second line is again, a comment. As you know, you
replace this with your vbscript. After this, the interpreter is told
to go to the :WAIT section of code. Again, it will always do this,
no matter what, after executing the line above it.

Changing a batch file when its running

I am running a long running batch file. I now realize that I have to add some more commands at the end of the batch file (no changes to exisiting content, just some extra commands). Is it possible to do this, given that most batch files are read incrementally and executed one by one? Or does the system read the entire contents of the file and then runs the job?
I just tried it, and against my intuition, it picked up the new commands at the end (on Windows XP)
I created a batch file containing
echo Hello
pause
echo world
I ran the file, and while it was paused, added
echo Salute
Saved it and pressed enter to contine the pause, all three prompts were echoed to the console.
So, go for it!
The command interpreter remembers the line position byte offset it's at in the batch file. You will be fine as long as you modify the batch file after the current executing line position byte offset at the end of the most recently parsed line of code.
If you modify it before then it will start doing strange things (repeating commands etc..).
jeb's example is a lot of fun, but it is very dependent on the length of the text that is added or deleted. I think the counter-intuitive results are what rein meant when he said "If you modify it before then it will start doing strange things (repeating commands etc..)".
I've modified jeb's code to show how dynamic code of varying length can be freely modified at the beginning of an executing batch file as long as appropriate padding is in place. The entire dynamic section is completely replaced with each iteration. Each dynamic line is prefixed with a non interfering ;. This conveniently allows FOR /F to strip the dynamic code because of the implicit EOL=; option.
Instead of looking for a particular line number, I look for a specific comment to locate where the dynamic code begins. This is easier to maintain.
I use lines of equal signs to harmlessly pad the code to allow for expansion and contraction. Any combination of the following characters could be used: comma, semicolon, equal, space, tab and/or newline. (Of course the padding cannot begin with a semicolon.) The equal signs within the parentheses allow for code expansion. The equal signs after the parentheses allow for code contraction.
Note that FOR /F strips empty lines. This limitation could be overcome by using FINDSTR to prefix each line with the line number and then strip out the prefix within the loop. But the extra code slows things down, so it's not worth doing unless the code is dependent on blank lines.
#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
The above works, but things are much easier if the dynamic code is moved to a subroutine at the end of the file. The code can expand and contract without limitation, and without the need for padding. FINDSTR is much faster than FOR /F at removing the dynamic portion. Dynamic lines can be safely be prefixed with a semicolon (including labels!). Then the FINDSTR /V option is used to exclude lines that begin with a semicolon and the new dynamic code can simply be appended.
#echo off
setlocal DisableDelayedExpansion
echo The starting filesize is %~z0
:loop
echo ----------------------
call :changeBatch
call :dynamicCode1
call :dynamicCode2
echo The current value=%value%
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
Short answer: yes, batch files can modify themselves whilst running. As others have already confirmed.
Years and years ago, back before Windows 3, the place I worked had an inhouse menu system in MS-DOS. The way it ran things was quite elegant: it actually ran from a batch file that the main program (written in C) modified in order to run scripts. This trick meant that the menu program itself was not taking up memory space whilst selections were running. And this included things like the LAN Mail program and the 3270 terminal program.
But running from a self-modifying batch file meant its scripts could also do things like load TSR programs and in fact could do pretty much anything you could put in a batch file. Which made it very powerful. Only the GOTO command didn't work, until the author eventually figured out how to make the batch file restart itself for each command.
Nearly like rein said, cmd.exe remember the file position (not only the line position) it's currently is, and also for each call it push the file position on an invisble stack.
That means, you can edit your file while it's running behind and before the actual file position, you only need to know what you do ...
A small sample of an self modifying batch
It changes the line set value=1000 continuously
#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
The command interpreter appears to remember the byte offset within each command file it is reading, but the file itself is not locked, so it is possible to make changes, say with a text editor, whilst it is running.
If a change is made to the file after this remembered location, the interpreter should happily continue to execute the now modified script. However if the change is made before that point, and that modification changes the length of the text at that point (for example you've inserted or removed some text), that remembered location is now no longer referring to the start of that next command. When the interpreter tries to read the next 'line' it will instead pick up a different line, or possibly part of a line depending on how much text was inserted or removed. If you're lucky, it will probably not be able to process whatever word it happen to land on, give an error and continue to execute from the next line - but still probably not what you want.
However, with understanding of what's going on, you can structure your scripts to reduce the risk. I have scripts that implement a simply menu system, by displaying a menu, accepting input from the user using the choice command and then processing the selection. The trick is to ensure that the point where the script waits for input is near the top of the file, so that any edits you might wish to make will occur after that point and so have no nasty impacts.
Example:
: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)

Resources