windows batch file with goto command not working - windows

I have a problem with GOTO command and affiliated labels.
Facts: Given a bunch of files from a folder (they are log errors) I need to open them and check if they contain a specific string. If yes then eliminate some characters (all the chars after the last appearance of "_", including itself) from the file names and do other operations.
For cutting off the chars I'm using GOTO command in a loop manner as I found it described here: http://www.robvanderwoude.com/battech_while_loops.php
The script is:
#echo off
setlocal EnableDelayedExpansion
cls
for %%X in (D:\e-pub\outbox\logs\*.*) do (
for /F "tokens=7" %%S in (%%X) do (
if /i "%%S"=="<ml>" (
SET fisier=%%~nX
SET cond=!fisier:~-1!
SET fisier=!fisier:~0,-1!
:loopStart
rem condition to break the loop
if !cond!==_ goto loopEnd
SET cond=!fisier:~-1!
SET fisier=!fisier:~0,-1!
goto loopStart
:loopEnd
rem here it should be out of a loop
rem other stuff to do with var !fisier!
rem the following line is not executed because of the label loopEnd
echo !fisier!
)
)
)
pause
The script is not running because there is an empty line after the label loopEnd?!
If I'm writing any instructions right after that label they will be executed but the rest of iterations from the first for statement won't be executed (the log errors folder contains more one file)
Can someone provide help?

You've got two problems.
One problem is that a goto breaks a for-loop.
The other, labels are quite difficult in parenthesis.
The goto breaks always and all nested loops, even if the label of the goto is in the same block, and the for-variables are lost immediately after the jump.
In parenthesis lables are "two line" oriented!
I experimented with labels and here are some results for parenthesis.
When a label occurs, the next line has to be in the correct format for a "secondary" line.
That's why this fails.
(
:this label fails with a syntax error
)
(
:this works
:because this line is a "legal" secondary line
)
(
:: The remark style
:: fails, because it's not "legal" to use a double colon, because it's not a legal path (in the most cases)
)
(
:and now I got courious & echo This will not echo'd
:but & echo You can see this !
)
For the second line some steps of the batch parser are skipped.
# doesn't work, #echo Hello tries to start a file named #echo.bat.
Splitting of parenthesis fails, like in echo( hello.
Labels are handeled as a file name, :echo checks only if :echo is a valid file name and then skip this part.
::hello searches on the drive ::.
For test purposes the drive :: can be created with subst :: c:\temp.
As labels are simply ignored on the second line, ampersands and also pipes work, but the file on :: have to exist.
(
echo #echo This is %~f0
) > %TEMP%\testLabel.bat
REM create Drive ::
subst :: %temp%
(
:Label
::\testLabel.bat The bat will not be executed | echo But this
)
subst /D ::

Comments / Remarks
:: This is a REMark
A colon (:), which is actually the LABEL tag, can be used for comments instead of REM, by doubling it (::), except within parentheses (i.e. except within a FOR loop).
Using a double-label within a loop can cause the batch script to fail, but ONLY if:
The double-label is followed by a second double-label on the next line
The double-label is followed by an empty line on the next line
The double-label is the last line in the loop
In other words: if used within a loop, the double-label must be followed by a line which contains normal (i.e. valid) syntax. Even a single-label is valid syntax.
This error never occurs if the double-label is replaced with REM.
The error arising from a double-label occurs because CMD.EXE interprets :: as a drive letter (like C:).
.
Note -
This is an explanation for one of the problems mentioned in Jeb's answer, a point which he raises but doesn't deal with.

Related

Question about Comments in Batch *.bat files and speed

I often use :: to initiate a comment.
Now I had a IF block and I found multiline :: comments do not work in *.bat files if they are in a IF-Block.
I found this site https://www.robvanderwoude.com/comments.php and this explains a lot about comments in *.bat files.
normally comments are initiated by a REM command.
as the site says... on each REM command the cmd interpreter will ignore the line but re-read the whole file to get the next line after the REM-line, and this will slow down the whole process.
but :: -comments will be interpreted as a invalid GOTO-Label and so the interpreter just jumps to the next line and will not re-read the file and is there for faster processed.
now I also hat a other idea which is not discussed on this site
to create comments with a false IF-Block
IF defined COMMENTBLOCK (
Write
Your multiline
comment here.
)
because the variable COMMENTBLOCK is not defined the interpreter will never look inside the block of the IF command and so it will just jump over the comments inside of the block.
now on this site the guy is talking about speed of a process.
I think the interpreter will check the variable COMMENTBLOCK and since it is not defined read all lines until the end of the block and then go on with the process
it will, as I think but not am sure of, not re-read the *.bat file as if I would use REM commands, and it would work inside of a IF or FOR block in opposite to :: comments which do not work in IF or FOR blocks if the comment is multiline.
now how can I measure the speed of a *.bat file to find the difference in speed between each method of comment?
How can I be sure that...
IF defined COMMENTBLOCK (
Multiline
Comment
)
... is fast as
:: Multiline
:: Comment
and how can I prove that ...
REM Multiline
REM Comment
... will be slower than the other methods of commenting?
Such *.bat files are extremely fast ... the difference may is below a second the %time% variable shows me hours minutes seconds and hundreds of a second ... but to over jump such a comment does even seem to take less time ... so the result is really just a logical estimation at all.
also I do not have floppy disks to see what happens if the methods are used from floppy.
may the best would be if some one who knows it could explain what the interpreter does if a false undefined IF-Block appears ... does it act like false labels :: or REM?
and what would may be a disadvantage of false IF-Blocks to create multiline comments inside of FOR and IF-Blocks?
Thanks in advance for your clarification...
UPDATE:
the method with undefined variables as COMMENTBLOCK can not handle pipes | or closing brackets ) ...
IF defined COMMENTBLOCK (
)
|
This will fail!!!
)
the method with :: and REM can handle every thing including pipe |
:: |
REM |
.. but :: -comments can not be used inside of blocks as IF and FOR
a other strange method for multiline comments is to place two commands in one line as echo sometext & pause but instead of the pause command you need a invalid goto label :: and then add new lines with ^ at the end, this will need a blank line between every comment line and to keep the lines together each line must get a line break with ^
echo some command &:: ^
_^
Strange characters do work ^
_^
but quotes are sensitive, they must be even ^
_^
last line of comment
a other nice method to create multiline comments is to use %= comment =% lines...
%= multiline =%
%= comment =%
... this also has its drawback, %, !, %~ inside of comments will confuse the interpreter, strange characters can be escaped awkwardly %= %% ~f0 =%
see also the comments below for more information and thanks
on each REM command the cmd interpreter will ignore the line but
re-read the whole file
This seems to be wrong, even for the old command.com (MS DOS 6.22).
But REM is still slower, but it depends on the usage.
Inside a FOR block, the difference is extreme, because the REM will be parsed and executed in each loop, but the :: (also the %= =% comment style) will be parsed and completely removed from the code.
For a heavily repeated block REM takes time, but :: takes no time at all!
In normal code, the difference is only for parsing and a single execution of the comment.
But even there the REM takes approx. 50% more time.
Example:
set "start=%time%"
FOR /L %%# in (1,1,1000) DO (
call :test
)
set "end=%time%"
call :showdiff "%start%" "%end%" -<loop-call-overhead constant>
exit /b
:test
REM 1
REM 2
REM 3
REM 4
REM 5
REM 6
REM 7
REM 8
REM 9
REM 10
exit /b
The loop executes 10000 times REM.
The loop-call-overhead-constant is measured when the :test function is empty (only contains exit /b)
The REM version takes ~3000ms vs ~2000ms for the :: version.
On my system a single REM takes ~0.3ms vs 0.2ms for a ::

new to Batch script, what exactly this for loop do

I have a bactch script which I am trying to understand, as I am new to batch programming and have to customize this code. but I can't understand what the subroutines check_utf8_bom,create_utf8bom_free_file,remove_utf8 are actually doing. Can someone please help
set /p bom=<.\bom
for /f "delims=" %%G in (.\file-list.txt) do (
call:check_utf8_bom %bom% !SOURCE_FOLDER!
)
:check_utf8_bom
rem ;; Checks If There Is The UTF-8 Bom At The Beginning Of The File
set /p firstLine=<%2\tmp
if "%firstLine:~0,3%"=="%~1" call:create_utf8bom_free_file %2
goto:eof
:create_utf8bom_free_file
rem ;; Remove UTF-8 BOM From "tmp" File o Avoid Problems During Interpretation
type %1\tmp>%1\tmp.bom
for /f "delims=" %%G in (%1\tmp.bom) do (
if defined i echo.%%G>>%1\tmp
if not defined i (
call:remove_utf8_bom "%%G" %1
set i=1
)
)
del %1\tmp.bom
goto:eof
:remove_utf8_bom
rem ;; Called From create_utf8bom_free_file Function Create The File Without The BOM In The First line
set fl=%~1
echo %fl:~3,-1%%fl:~-1%>"%2\tmp"
goto:eof
can somebody please help me to understand it?
%1 stands for the first argument passed to the script / :create_utf8bom_free_file subroutine.
type %1\tmp>%1\tmp.bom this prints file tmp from %1 directory to tmp.bom file - this should convert unicode file to ascii one (probaly without changing byte order mark )
for /f "delims=" %%G in (%1\tmp.bom) do - means read %1\tmp.bom line by line without splitting the line with delimiters ("delims=") so on each iteration the line will be assigned to %%G token (temporary variable that lives during FOR /F execution).
if not defined i and set i=1 is a workaround for missing break operator in batch file loops . The first checks if the variable is defined and the second sets value to the variable. So the first line is passed to :remove_utf8_bom (here the first two characters of the line should be removed) function and then the for loop is over.
At the end temp file is deleted and goto:eof means go to the end of the script - i.e. something similar to exit.
It's not really clear what the script does because you didn't post the remove_utf8_bom procedure. So far I can tell you that the the loop reads the content of the file tmp.bom line by line. In each iteration it checks whether the variable i is set or not. If it is, the currently processed line is appended to the file tmp. Otherwise the current line as well as the parameter %1 both are passed to the procedure remove_utf8_bom (which we don't know) and the variable i is being set to 1.
This is - as you've asked - what the for-loop does (without the lines above and below it). For more information I need more code.
EDIT:
By the way...even without the code I'd suppose that this script should remove the BOM from UTF-8 encoded text files. This is often needed because if a batch or cmd file is stored in UTF-8 there is a BOM at the beginning of it which causes that the first line in the script won't be executed.
To eliminate this problem you should avoid saving scripts as UTF-8. If you can't be sure about the encoding, you also could start scripts with REM doesn't matter what text is here as this line will be ignored. The rest of the script will be executed as desired.

Brackets aren't working on this Windows File?

I am new here and my english is not very good so first, pardon me.
My problem is that this batch keep reading the strings inside the variable "IF EXIST %FSIZE%"; i meant, in case that variable does not exist, the batch keep reading inside the () brackets instead go on the rest of the stings.
If %FSIZE% exist, the batch perform the 2 task i assign: (1.) If size is equal, goto cifiles5x. (2.) If size is NOT equal it uses 7z to extract the file i want to be there.
If %FSIZE% DOES NOT EXIST, the batch keep saying "765952 was not expect at this moment".
I am taking the advices on ss64.com like don't use brackets or quotes when comparing numeric values (%size% EQU 765952) but i don't understand why it does not continue to where the ) ends.
I have also try to link the commands with "&&" so i can erase the brackets but the results are the same.
I know there's 2 spaced patches without quotes; they are unquoted because if i did the size checker won't work.
Thanks for reading this.
EDIT: Batch modified according to suggestions made.
#ECHO OFF
TITLE Log checker
COLOR 0F
SET FSIZE=%ProgramFiles(x86)%\Ces\Log Files Ver\LogVbReg_r2.dll
SET InsDIR=%ProgramFiles(x86)%\Ces\Log Files Ver\
REM I didn't add "" on FSIZE and InsDIR because if i did, quote the variable will
REM result a doubled quoted patch and won't work.
CLS
ECHO ==============================================================================
ECHO = Log checker =
ECHO ==============================================================================
ECHO Checking if exist:
ECHO "%FSIZE%"
IF EXIST "%FSIZE%" (
ECHO It does exist, checking size...
FOR %%A IN ("%FSIZE%") DO SET SIZE=%%~ZA
IF "%SIZE%" EQU "765952"
ECHO Size is right, jumping to CIFILES5
GOTO CIFILES5x
) ELSE (
ECHO Size is not right, extracting the needed file...
7z.exe e "Data_X.*" -o"%InsDIR%" -y "LogVbReg_r2.dll"
GOTO CIFILES5x)
ECHO Does not exist; extracting file...
REN Data_X.* Data_X.exe
Data_X.exe
TIMEOUT 2>NUL
REN DATA_X.* Data_X.dat
:CIFILES5x
ECHO Reach cifiles5x
PAUSE
IF EXIST "%TEMP%\9513.CES" (GOTO OPS) ELSE (GOTO LNKD)
You have space in your file name of %FSIZE%. As result text after space it is treated as command for if. Try using quotes around "%FSIZE%" or do CD to folder first and than check for just file name.
You have several minor errors:
If the file name may have spaces, it MUST be enclosed in quotes in all cases:
IF EXIST "%FSIZE%" (
FOR /F is used to read file CONTENTS. If you want to process file NAME, don't use /F option, but plain FOR command:
FOR %%A IN ("%FSIZE%") DO SET SIZE=%%~ZA
The ELSE part of an IF command must appear in a line alone:
GOTO CIFILES5x
) ELSE (
The same thing for a parentheses:
GOTO CIFILES5x
)
Excepting if the opening parentheses appear in the same line. This is correct:
IF EXIST "%TEMP%\9513.CES" (GOTO OPS) ELSE (GOTO LNKD)
If you compare numbers for equality (EQU, NEQ) then don't matter if they are enclosed in quotes or not. Just in case of GTR, GEQ, LSS and LEQ they must have NOT quotes. However, in your case, this is not a problem...

Adding and removing codes in every line in windows

I have lines like the ones shown below.
abcbasndo
bacmaisca
ascmasoc
Now, I need to take out the first three characters of every line and add AAA at the start and end of each line, so that it looks like the one shown below.
AAAabcAAA
AAAbacAAA
AAAascAAA
I am using windows.
Please help.
This little cmd script will do the job for you:
#setlocal enableextensions enabledelayedexpansion
#echo off
for /f "delims=" %%a in (qq.txt) do (
set var=%%a
echo AAA!var:~0,3!AAA
)
endlocal
See the following transcript:
C:\Pax> type qq.txt
abcbasndo
bacmaisca
ascmasoc
C:\Pax> qq
AAAabcAAA
AAAbacAAA
AAAascAAA
The for loop grabs each line in the qq.txt file (without delims=, it would use spaces within the line as delimiters) and puts it in %%a.
The body of the for loop puts that value into var and then uses the substring operator to get the first three characters.
I haven't tested what will happen if the line has less than three characters since (1) you didn't specify what you expected; and (2) it should be fairly easy to expand this script to handle 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