Question about Comments in Batch *.bat files and speed - performance

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

Related

Batch script for search & replace without skipping empty line, '!' and ';'?

Sorry, for bothering you for the (n+1)th time about search & replace with batch scripts.
I have text files (actually PS-files) (approx. 10kB-3MB) where I need to replace just a few numbers.
This should be easy, I thought.
I found quite a few scripts here on Stackoverflow but none of them worked properly so far. If I have overlooked THE "working one" please let me know.
The last one I tried:
#echo off
setlocal DisableDelayedExpansion
set OutputFile=%1
set OutputFile=%OutputFile:"=%
set InputFile=%OutputFile%.tmp
set SearchString=636170656C6C6133
set ReplaceString=636170656C6C6134
rem write empty file
type NUL > %OutputFile%
for /f "tokens=1,* delims=ΒΆ" %%A in ( '"type %InputFile%"') do (
SET string=%%A
setlocal EnableDelayedExpansion
SET modified=!string:%SearchString%=%ReplaceString%!
echo !modified!>>%OutputFile%
endlocal
)
del %InputFile%
First of all, it seems to be pretty(!) slow. I can see on disk how the file size increases.
The occurrences of the numbers seem to be replaced. However, the file is altered, which I easily can see from the different file size. As far as I can see, empty lines, exclamation marks and lines beginning with semicolon are skipped. This is messing up my file completely.
How to avoid this?
If I do the same thing with Perl I really get only the numbers altered, nothing else. However, I don't want to and cannot use Perl. I also don't want to use other extra programs or Windows-Powershell, since it should work on older systems too.
Is there any way to achieve this with a simple Windows batch script?
Thanks!
I believe the following is a working bat script that should not make any changes other than the desired number change:
#echo off
setlocal DisableDelayedExpansion
set "out=%~1"
set "in=%out%.tmp"
set "find=636170656C6C6133"
set "repl=636170656C6C6134"
>"%out%" (
for /f "delims=" %%A in ('findstr /n "^" "%in%"') do (
set "str=%%A"
setlocal EnableDelayedExpansion
set "str=!str:*:=!"
if defined str set "str=!str:%find%=%repl%!"
echo(!str!
endlocal
)
)
del "%in%"
Changes I have made:
Use %~1 to remove enclosing parentheses. Though technically, that is not necessary. Something like echo test >"someName.txt".new will work just fine.
FOR /F strips empty lines. I used FINDSTR to prefix each line with the line number, followed by a colon. Now there are no empty lines.
I use an extra variable expansion find/replace with * to remove the line number prefix.
Variable expansion find/replace will fail if a string is empty (undefined variable). So I verify the variable is defined before doing find/replace.
ECHOing an empty line, or line containing only white space, will result in ECHO is off. output. This is solved by using echo(
It takes time to initialize redirection, and your loop does this every iteration, which slows things down. I improved performance by enclosing the entire FOR loop in parentheses and redirecting only once.
You still may see a slight file size change for any of the following reasons
If the input has \n line terminators instead of \r\n.
If the last line of input is not terminated by \r\n. The script terminates all lines with \r\n, regardless what the input had.
The script will fail if any line contains a null byte, or if any line is >~8k length.
I hate editing text files with batch - it is complicated code, slow, and even the best possible solution still has significant limitations.
I recommend you use JREPL.BAT - a command line regular expression text processing utility. JREPL is pure script (hybrid batch/JScript) that runs natively on any Windows machine from XP onward - no 3rd party exe file or special configuration is needed.
The tool is very powerful, with many options. Full documentation is available from the command line via jrepl /?, or jrepl /?? for paged help.
Solving your problem with JREPL is trivial - you don't even need another script. The following command will work right from a command prompt:
jrepl 636170656C6C6133 636170656C6C6134 /f input.txt /o output.txt
Use CALL JREPL if you put the command within another batch script.
JREPL is way more powerful than what you need for this simple problem. But it is incredibly convenient, and once you have the utility, I suspect you will find many uses for it. Especially if you learn to use regular expressions, as well as the many JREPL options.
A VBS script
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Text = Inp.readall
Text = Replace(Text, "636170656C6C6133", "636170656C6C6134")
outp.write Text
To use
cscript //nologo script.vbs < input.txt > Output.txt
You use the right tool for the job. Batch is for starting programs and copying files.
The above is suited to the file sizes given. However if we are getting up to 100s of MB then this code is better.
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Do Until Inp.AtEndOfStream
Text = Inp.readline
Text = Replace(Text, "636170656C6C6133", "636170656C6C6134")
outp.writeline Text
Loop

Batch File - Read specific line, and save a specific string in that line as a variable

Is there any way to get for /f loop (or anything else) to read a specific line?
Here is the code I have so far, it reads first word of every line.
#echo off
set file=readtest.txt
for /f "tokens=1 delims= " %%A in (%file%) do (echo %%A)
pause
If someone can point me in the right direction, it'd be much appreciated.
Thanks
Additional Information: I want to make a batch file which will rename a TXT file to a string within that TXT file, located at a specific location. I have figured out how to rename files, all I need to learn to do is to retrieve a string (located at a specific location) with in the file which will go into the name of that TXT file.
Since you haven't fully defined what you mean by "a specific location", I'll make some (reasonable, in my opinion) assumptions, though the method I present is equally valid no matter what your definition turns out to be.
You can get arbitrary lines and arbitrary words on that line by using a line counter variable in conjunction with tokens.
Let's assume your text file name can be found as the second argument on the fourth line of the infile.txt file. You can get that with something like:
#setlocal enableextensions enabledelayedexpansion
#echo off
set /a "line = 0"
for /f "tokens=2 delims= " %%a in (infile.txt) do (
set /a "line = line + 1"
if !line!==4 set thing=%%a
)
endlocal & set thing=%thing%
echo %thing%
This actually uses a few "tricks" which warrant further explanation:
the line counter to ensure you only grab what you want from a specific line, though you could change the test !line!==4 into anything you need such as a line beginning with #, the fifth line containing the string xyzzy and so on.
the use of setlocal/endlocal to effectively give you a scope from which variables cannot leak. This is good programming practice even for a language often not normally associated with such things :-)
the use of endlocal & set to bypass that scope so that thing is the only thing that does actually leak (as it should).
the use of delayed expansion and !..! variables to ensure they're correct within the for loop. Without this, the %..% will always be expand to the value they were set to when the for loop started.
Those last two bullet points are actually related. %..% variables are expanded when the command is read rather than when it is executed.
For a for loop, the command is the entire thing from the for to the final ). That means, if you use %line% within the loop, that will be evaluated before the loop starts running, which will result in it always being 0 (the variable itself may change but the expansion of it has already happened). However, !line! will be evaluated each time it is encountered within the loop so will have the correct value.
Similarly, while endlocal would normally clear out all variables created after the setlocal, the command:
endlocal & set thing=%thing%
is a single command in the context of expansion. The %thing% is expanded before endlocal is run, meaning it effectively becomes:
endlocal & set thing=whatever_thing_was_set_to_before_endlocal
That's why the use of setlocal and endlocal & set is a very useful way to limit variables "escaping" from a scope. And, yes, you can chain multiple & set stanzas to allow more variables to escape the scope.

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.

windows batch file with goto command not working

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.

What does this batch file code do?

What does this bat code do?
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
set n=%%~ni
set t=!n:~0,-4!
cp Client\Javascript\%%i build\Client\Javascript\!t!.js
)
What does %%~ni,~n:~0,-4!,%%i,!t! mean?
Keep in mind that in batch files, you need to escape percentage signs unless you're referring to arguments given to the batch file. Once you remove those, you get
for /f %i in ('dir /b Client\Javascript\*_min.js') do (
set n=%~ni
set t=!n:~0,-4!
cp Client\Javascript\%i build\Client\Javascript\!t!.js
)
%i is the declaration of a variable used to place the current file for has found. %~ni extracts the filename portion of %i. !n:~0,-4! uses delayed expansion to remove the last four characters from %n% (set in the previous line) !t! is simply delayed expansion of the %t% variable set in the previous line.
Delayed expansion is used because otherwise, the variables will be substituted as soon as the line is encountered, and future iterations will not re-expand the variable.
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
Iterate over every file in the Client\Javascript folder that match "*_min.js". Thedircommand andfor /f` are totally unneeded here, though and only complicate things, especially when file names contain spaces, commas and the like. A more robust and simpler alternative would be
for %%i in (Client\Javascript\*_min.js) do (
But that's just beside the point. People tend to write unelegant batch files sometimes, ignoring the pitfalls and common errors. That's just one example of that.
set n=%%~ni
Creates a variable n, containing the file name (without any directory information or extension) of the file currently processed. We remember that the for statement iterates over every file it finds. With this line starts what it does with those files.
set t=!n:~0,-4!
Creates a second variable, t, containing everything but the last four characters of the file name. This essentially strips away the "_min"
cp Client\Javascript\%%i build\Client\Javascript\!t!.js
Finally, this copies the original file to the directory build\Client\Javascript with the new name, just constructed. So a file like Client\Javascript\foo_min.js will be copied to Client\Javascript\foo.js. The !t! here is just a delayed-evaluated environment variable. More on that below. Here it should suffice that it just inserts the contents of said variable at that point in the line.
Again, bad practice here that will break in numerous interesting ways:
cp is not a command on Windows so this batch will assume cygwin, GNUWin32 or similar things installed. I tend to avoid having too many unneeded dependencies and stick to what Windows provides; in this case the copy command. Two bytes won't kill anyone here, I think.
No quotes are around either argument. Leads to interesting results when spaces start appearing in the file name. Not good, either.
As for why delayed expansion was used (! instead of % surrounding the variables: The for command consists of everything in the block delimited by parentheses here as well. The entire block is parsed at once and normal variable expansion takes place when a line/command is parsed. That would mean that every variable in the block would be evaluated before the loop even runs, leaving just the following:
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
set n=%%~ni
set t=
cp Client\Javascript\%%i build\Client\Javascript\.js
)
which is certainly not what you want in this case.
Delayed expansion is always needed when creating and using variables in a loop such as this. A workaround not needing delayed expansion would be to offload the loop interior into a subroutine:
for /f %%i in ('dir /b Client\Javascript\*_min.js') do call :process "%%i"
goto :eof
:process
set n=%~n1
set t=%n:0,-4%
copy "Client\Javascript\%~1" "build\Client\Javascript\%t%.js"
goto :eof
Since the subroutine is not a single "block" (something delimited by parentheses) it will be parsed line by line as usual. Therefore it's safe to use normal expansion instead of delayed expansion here.
A complete help for the FOR command can be found on the Microsoft TechNet site. See here for more information on delayed expansion :
// Pseudo code
for each file named *_min.js in the specified directory
n is set to the file name (*_min)
t is set to the file name, excluding the last 4 characters (*)
the file is copied and renamed t.js to the specified directory
%~ni expands to just the filename part of i.
!n:~0,-4! expands to all but the last four characters of n.
In general, help for at the command prompt will give an overview of the multitude of ways for can expand variables these days.

Resources