Page 1 of 1
Why REM inside a block?
Posted: 16 Feb 2018 05:42
by miskox
Hello all!
I just saw this post
viewtopic.php?p=9#p9
Quote:
fc "%file1%" "%file2%">NUL&&(
echo the files are equal
rem keep this rem as last command in the block
)||(
echo the files are different
)
Question: why should that REM line be there?
Thanks.
Saso
Re: Why REM inside a block?
Posted: 16 Feb 2018 07:22
by npocmaka_
I suppose to avoid setting a command at the end of the block that will exit with something different than 0.If this happens the second block will be executed despite files being identical.
Re: Why REM inside a block?
Posted: 16 Feb 2018 07:39
by dbenham
Perhaps that was the intent, but it does not work that way. REM never sets the ERRORLEVEL - the value that existed before the remark is preserved.
It should not be an issue for that code because the only other command in the block is ECHO, which also preserves the ERRORLEVEL.
If you ever need to explicitly clear the ERRORLEVEL to 0, then you can use any of the following:
Dave Benham
Re: Why REM inside a block?
Posted: 16 Feb 2018 07:52
by npocmaka_
dbenham wrote: ↑16 Feb 2018 07:39
Perhaps that was the intent, but it does not work that way. REM never sets the ERRORLEVEL - the value that existed before the remark is preserved.
It should not be an issue for that code because the only other command in the block is ECHO, which also preserves the ERRORLEVEL.
If you ever need to explicitly clear the ERRORLEVEL to 0, then you can use any of the following:
Dave Benham
Is the call the fastest way? What about type nul or color?
Re: Why REM inside a block?
Posted: 16 Feb 2018 08:50
by Aacini
There are several simple commands that can be used to set ERRORLEVEL to zero; further details at
this answer (below
Table 2). For example:
CD . or
VOL > NUL. However, in some Microsoft site I saw
many years ago they suggested
VER > NUL for this purpose.
IMHO the
special (and strange) forms of CALL command are confusing...
Antonio
Re: Why REM inside a block?
Posted: 16 Feb 2018 13:28
by dbenham
I find all forms to be cryptic - none of them are obvious as to the intent.
I agree that something like (CALL ) or CALL; is especially mysterious looking, but I actually find that kind of good - it is obvious that we are trying to do something that is unusual, not really related to CALL.
Contrast that with something like CD . >NUL, or VER >NUL, and someone might try to figure out a purpose for those commands (other than clearing ERRORLEVEL)
I find that CALL; or (CALL ) is 10 times faster than the other forms. And the (CALL) command that sets ERRORLEVEL to 1 is even faster
For most of the other commands, it is the >NUL that is dramatically slowing things down. But even if you are in a situation where stdout has already been redirected to NUL, the CALL forms are still significantly faster. So I will stick with CALL.
Here is my test suite that times how long it takes to execute each form 10,000 times. I included the (CALL) form that sets ERRORLEVEL to 1 just for timing comparison.
Code: Select all
@echo off
echo Normal
echo --------------------------------
for %%C in (
"cd ."
"ver >nul"
"date /t >nul"
"time /t >nul"
"verify >nul"
"call;"
"(call )"
"(call)"
) do call :test %%C
echo(
echo stdout already redirected to nul
echo --------------------------------
for %%C in (
"cd ."
"ver"
"date /t"
"time /t"
"verify"
"call;"
"(call )"
"(call)"
) do call :test %%C 3>&1 >nul
exit /b
:test
setlocal enableDelayedExpansion
set "cmd=%~1"
set "t0=%time%"
for /l %%N in (1 1 10000) do %~1
set "t1=%time%"
for /f "tokens=1-4 delims=:.," %%a in ("%t0: =0%") do set /a "t0=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"
for /f "tokens=1-4 delims=:.," %%a in ("%t1: =0%") do set /a "t=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100-t0"
if %t% lss 0 set /a t+=24*60*60*100
set "t= %t%"
echo %t:~-4%0 ms !cmd! >&3
exit /b
--OUTPUT--
Code: Select all
Normal
--------------------------------
4720 ms cd .
1720 ms ver >nul
1610 ms date /t >nul
1600 ms time /t >nul
1540 ms verify >nul
160 ms call;
160 ms (call )
120 ms (call)
stdout already redirected to nul
--------------------------------
4730 ms cd .
400 ms ver
310 ms date /t
300 ms time /t
230 ms verify
150 ms call;
150 ms (call )
120 ms (call)
Dave Benham
Re: Why REM inside a block?
Posted: 16 Feb 2018 15:54
by npocmaka_
color behaves strangely with this script at least on my machine:
Code: Select all
Normal
--------------------------------
53780 ms color
1550 ms cd .
1590 ms ver >nul
1280 ms date /t >nul
1270 ms time /t >nul
1250 ms verify >nul
670 ms call;
690 ms (call )
530 ms (call)
stdout already redirected to nul
--------------------------------
570 ms color
1600 ms cd .
880 ms ver
610 ms date /t
630 ms time /t
660 ms verify
680 ms call;
680 ms (call )
510 ms (call)
probably because color does not affect the console collors when redirected.So is the color>nul the fastest way to set the error level to 0?
EDIT when redirected color does not change the errorlevel.
Re: Why REM inside a block?
Posted: 25 Feb 2018 12:58
by jfl
As the cryptic (call)s also confuse me, I usually hide them behind macros:
Code: Select all
set "TRUE.EXE=(call,)" &:# Macro to silently set ERRORLEVEL to 0
set "FALSE.EXE=(call)" &:# Macro to silently set ERRORLEVEL to 1
The names come from the eponymous commands that do the very same thing in Unix. I added the .EXE suffix to make it look like it's an external command running.
Then you use them as %TRUE.EXE% or %FALSE.EXE%.
About why having a rem at the end of the parenthesis block...
Maybe this is a useless leftover of a longer comment?
Because if you do want to put comments there, it is important to use REM, instead of :: or :# as we usually do elsewhere.
I don't know why, but such fake labels placed at the end of a parenthesis block cause corruptions in surrounding commands.
Jean-François
Re: Why REM inside a block?
Posted: 26 Feb 2018 08:13
by pieh-ejdsch
setting the errorlevel to 0 is still a lot faster with an if than with a call.
Code: Select all
@echo off
call :loop Normal
echo(
call :loop "stdout already redirected to nul" "3>&1 >nul"
exit /b
:loop
echo %~1
echo --------------------------------
for %%C in (
"cd ."
"ver >nul"
"date /t >nul"
"time /t >nul"
"verify >nul"
"call &&@"
"call &"
"call ||@"
"call;"
"(call )"
"call "
"call||@"
"call&"
"call&&@"
"(call)"
"call"
"if 1==1 @"
"if not 1==1 @"
"if 1==2 @"
"set /a a=0"
"<nul set/p="
"(set /a a=0)"
"(2>nul \:)"
"(2>nul md)"
) do call :test %%C %~2
call :test "(if 1==2 *)" %~2
call :test "if 1 equ 2 *" %~2
call :test "(if 1 equ 2 *)" %~2
call :test "(if 1 equ 2 rem:)" %~2
call :test "(2>nul *)" %~2
call :test "(call&&*)" %~2
exit /b
:test
setlocal enableDelayedExpansion
set "cmd=%~1"
set "t0=%time%"
for /l %%N in (1 1 10000) do %~1
set "t1=%time%"
for /f "tokens=1-4 delims=:.," %%a in ("%t0: =0%") do set /a "t0=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"
for /f "tokens=1-4 delims=:.," %%a in ("%t1: =0%") do set /a "t=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100-t0"
if %t% lss 0 set /a t+=24*60*60*100
set "t= %t%"
%~1
>&3 echo %t:~-4%0 ms EL: %errorlevel% %1
exit /b
Result
Code: Select all
Normal
--------------------------------
4520 ms EL: 0 "cd ."
2780 ms EL: 0 "ver >nul"
2410 ms EL: 0 "date /t >nul"
2430 ms EL: 0 "time /t >nul"
2420 ms EL: 0 "verify >nul"
1140 ms EL: 0 "call &&@"
1120 ms EL: 0 "call &"
1120 ms EL: 0 "call ||@"
1100 ms EL: 0 "call;"
1140 ms EL: 0 "(call )"
1110 ms EL: 0 "call "
750 ms EL: 1 "call||@"
760 ms EL: 1 "call&"
750 ms EL: 1 "call&&@"
770 ms EL: 1 "(call)"
770 ms EL: 1 "call"
140 ms EL: 0 "if 1==1 @"
140 ms EL: 0 "if not 1==1 @"
120 ms EL: 0 "if 1==2 @"
1100 ms EL: 0 "set /a a=0"
5040 ms EL: 1 "<nul set/p="
1130 ms EL: 0 "(set /a a=0)"
4130 ms EL: 0 "(2>nul \:)"
2720 ms EL: 1 "(2>nul md)"
180 ms EL: 0 "(if 1==2 *)"
190 ms EL: 0 "if 1 equ 2 *"
190 ms EL: 0 "(if 1 equ 2 *)"
180 ms EL: 0 "(if 1 equ 2 rem:)"
3210 ms EL: 9009 "(2>nul *)"
860 ms EL: 1 "(call&&*)"
stdout already redirected to nul
--------------------------------
4470 ms EL: 0 "cd ."
2380 ms EL: 0 "ver >nul"
2050 ms EL: 0 "date /t >nul"
1990 ms EL: 0 "time /t >nul"
2050 ms EL: 0 "verify >nul"
1180 ms EL: 0 "call &&@"
1170 ms EL: 0 "call &"
1170 ms EL: 0 "call ||@"
1290 ms EL: 0 "call;"
1480 ms EL: 0 "(call )"
1280 ms EL: 0 "call "
860 ms EL: 1 "call||@"
810 ms EL: 1 "call&"
830 ms EL: 1 "call&&@"
970 ms EL: 1 "(call)"
1050 ms EL: 1 "call"
180 ms EL: 0 "if 1==1 @"
170 ms EL: 0 "if not 1==1 @"
150 ms EL: 0 "if 1==2 @"
1210 ms EL: 0 "set /a a=0"
3060 ms EL: 1 "<nul set/p="
1260 ms EL: 0 "(set /a a=0)"
4590 ms EL: 0 "(2>nul \:)"
2920 ms EL: 1 "(2>nul md)"
170 ms EL: 0 "(if 1==2 *)"
190 ms EL: 0 "if 1 equ 2 *"
210 ms EL: 0 "(if 1 equ 2 *)"
190 ms EL: 0 "(if 1 equ 2 rem:)"
3520 ms EL: 9009 "(2>nul *)"
960 ms EL: 1 "(call&&*)"
Re: Why REM inside a block?
Posted: 26 Feb 2018 08:55
by jeb
Hi pieh-ejdsch,
looks good, ... until I checked the errorlevel
Code: Select all
(call)
echo err=%errorlevel%
if 1==1 @
echo err=%errorlevel%
if not 1==1 @
echo err=%errorlevel%
if 1==2 @
echo err=%errorlevel%
Output wrote:err=1
err=1
err=1
err=1
"IF" is fast, but doesn't affect the errorlevel at all
Re: Why REM inside a block?
Posted: 26 Feb 2018 17:56
by pieh-ejdsch
Yes Jeb - you are right.
I have practically evaluated the error level of the previous command of the script - well.
ok - I did a few more tests, but I can not do anything with IF.
On the other hand, FOR / F loops can produce Errorlevel 1 if an OR condition is appended.
The AND condition does not work for it. This means that setting the errolevel to 0 takes much longer than to 1.
Both are about the same with SET / A - similar to setting the errorlevel to 0 with CALL.
SET / P is extremely slow - takes more than 5 times.
Using CALL to set the errorlevel to 1 takes about 7/10 of the time to set it to 0 with CALL.
Setting this to 1 with a FOR / F loop will require setting CALL to 0 more than 1/10 of the time.
A FOR / F construct to 1 with OR IF also needs barely 3/10 of the CALL at 0 time.
Code: Select all
@echo off
echo .>"%userprofile%\desktop\testfileEL"
call :loop " Normal"
echo(
call :loop2 " NO change"
del "%userprofile%\desktop\testfileEL"
pause
exit /b
:Loop2
echo %~1
echo --------------------------------
for %%C in (
"for /f %%%%i in () do rem"
"(for /f %%%%i in ()do rem:)&&rem:"
"(for /f %%%%i in () do rem:)"
"(for %%%%i in () do rem:)||@"
"(2>nul \:)"
"if 1==1 @"
"if not 1==1 @"
"if 1==2 @"
"(if 1==2 rem:)||@"
"(if 1 equ 2 rem:)"
) do call :test %%C
call :test "(if 1==2 *)"
call :test "if 1 equ 2 *"
call :test "(if 1 equ 2 *)"
call :test "(if 1==2 *)||rem:"
call :test "(if 1==2 *)&&rem:"
call :test "(for /f %%%%i in ()do *)&&rem:"
exit /b
:loop
echo %~1
echo --------------------------------
for %%C in (
"set /a a=0"
"(set /a a=0)"
"call &&@"
"call &"
"call ||@"
"(call )"
"call "
"<%userprofile%\desktop\testfileEL set/p A="
"<nul set/p="
"(2>nul md)"
"(for /f %%%%i in () do rem:)||rem:"
"call||@"
"call&"
"call&&@"
"(call)"
"call"
"(for /f %%%%i in ()do rem:)||if 1==2 rem:"
"(for /f %%%%i in ()do rem:)||(@"
"(for /f %%%%i in ()do rem:)||@"
) do call :test %%C %~2
call :test "(2>nul *)" %~2
call :test "(call&&*)" %~2
exit /b
:test
set "cmd=%~1"
set "t0=%time%"
for /l %%N in (1 1 10000) do %~1
)
set "t1=%time%"
for /f "tokens=1-4 delims=:.," %%a in ("%t0: =0%") do set /a "t0=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"
for /f "tokens=1-4 delims=:.," %%a in ("%t1: =0%") do set /a "t=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100-t0"
if %t% lss 0 set /a t+=24*60*60*100
set "t= %t%"
call
%~1
)
set "X=%errorlevel%"
call;
%~1
)
set "Y=%errorlevel%"
if %X% equ %y% (set "option=yes") else set "option= NO"
%~1
)
%EL%
%~1
)
>&3 echo %t:~-4%0 ms Save = %option% EL: %errorlevel% %1
exit /b
Results
Code: Select all
Normal
--------------------------------
1290 ms Save = yes EL: 0 "set /a a=0"
1190 ms Save = yes EL: 0 "(set /a a=0)"
1420 ms Save = yes EL: 0 "call &&@"
1470 ms Save = yes EL: 0 "call &"
1220 ms Save = yes EL: 0 "call ||@"
1520 ms Save = yes EL: 0 "(call )"
1420 ms Save = yes EL: 0 "call "
7950 ms Save = yes EL: 0 "<C:\Users\Philipp\desktop\testfileEL set/p A="
5390 ms Save = yes EL: 1 "<nul set/p="
2470 ms Save = yes EL: 1 "(2>nul md)"
1760 ms Save = yes EL: 1 "(for /f %i in () do rem:)||rem:"
850 ms Save = yes EL: 1 "call||@"
910 ms Save = yes EL: 1 "call&"
950 ms Save = yes EL: 1 "call&&@"
910 ms Save = yes EL: 1 "(call)"
920 ms Save = yes EL: 1 "call"
330 ms Save = yes EL: 1 "(for /f %i in ()do rem:)||if 1==2 rem:"
130 ms Save = yes EL: 1 "(for /f %i in ()do rem:)||(@"
140 ms Save = yes EL: 1 "(for /f %i in ()do rem:)||@"
4070 ms Save = yes EL: 9009 "(2>nul *)"
840 ms Save = yes EL: 1 "(call&&*)"
NO change
--------------------------------
130 ms Save = NO EL: 0 "for /f %i in () do rem"
190 ms Save = NO EL: 0 "(for /f %i in ()do rem:)&&rem:"
130 ms Save = NO EL: 0 "(for /f %i in () do rem:)"
130 ms Save = NO EL: 0 "(for %i in () do rem:)||@"
3680 ms Save = NO EL: 0 "(2>nul \:)"
140 ms Save = NO EL: 0 "if 1==1 @"
140 ms Save = NO EL: 0 "if not 1==1 @"
130 ms Save = NO EL: 0 "if 1==2 @"
190 ms Save = NO EL: 0 "(if 1==2 rem:)||@"
190 ms Save = NO EL: 0 "(if 1 equ 2 rem:)"
190 ms Save = NO EL: 0 "(if 1==2 *)"
180 ms Save = NO EL: 0 "if 1 equ 2 *"
190 ms Save = NO EL: 0 "(if 1 equ 2 *)"
220 ms Save = NO EL: 0 "(if 1==2 *)||rem:"
1560 ms Save = NO EL: 0 "(if 1==2 *)&&rem:"
160 ms Save = NO EL: 0 "(for /f %i in ()do *)&&rem:"
Re: Why REM inside a block?
Posted: 26 Feb 2018 23:16
by dbenham
Wow, the FOR /F method for setting to 1 is wicked fast compared to other methods
But talk about cryptic and ungainly code
SET /A is no good for setting to 0 if the extension is .BAT - it only works with .CMD extension.
And there is no need for the /A option - SET A=0 works just as well
Also, the speed must be dependent on the size of the environment space.
Dave Benham
Re: Why REM inside a block?
Posted: 27 Feb 2018 03:04
by jeb
pieh-ejdsch wrote: ↑26 Feb 2018 17:56
On the other hand, FOR / F loops can produce Errorlevel 1 if an OR condition is appended.
That's cool
I tested OR and AND with IF and parenthesis and I also tested FOR, but not in the right combination to find your solution.
pieh-ejdsch wrote: ↑26 Feb 2018 17:56
Using CALL to set the errorlevel to 1 takes about 7/10 of the time to set it to 0 with CALL.
Setting this to 1 with a FOR / F loop will require setting CALL to 0 more than 1/10 of the time.
A FOR / F construct to 1 with OR IF also needs barely 3/10 of the CALL at 0 time.
This part seems to be the result of the measures, but it's wrong as the measures itself are wrong.
We measured with the construct
But this construct parses the command only once and executes it from the cached command block 10000 times.
Therefore the FOR-Set-Trick seems to be very fast compared to (CALL), but in reallity their exectuion times are nearly equal.
I modified the :test function, it uses now an external batch file for the measure and it also tests, if the command has any effect to the errorlevel at all.
Code: Select all
:test
set "cmd=%~1"
setlocal enableDelayedExpansion
set "effectiveErrorlevel=XX"
(echo !cmd!) > cmdTest.bat
(call)
call cmdTest.bat
if %errorlevel%==0 set effectiveErrorlevel=0
(call )
call cmdTest.bat
if %errorlevel% NEQ 0 set effectiveErrorlevel=1
if "!effectiveErrorlevel!" == "XX" (
echo ERROR: Has no effect "!cmd!"
exit /b
)
(
echo set "t0=%%time%%"
for /l %%N in (1 1 10000) do (
(echo !cmd!)
)
echo set "t1=%%time%%"
) > cmdTest.bat
call cmdTest.bat
for /f "tokens=1-4 delims=:.," %%a in ("%t0: =0%") do set /a "t0=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"
for /f "tokens=1-4 delims=:.," %%a in ("%t1: =0%") do set /a "t=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100-t0"
if %t% lss 0 set /a t+=24*60*60*100
set "t= %t%"
>&3 echo %t:~-4%0 ms EL: %effectiveErrorlevel% !cmd!
exit /b
The "new" results are
Code: Select all
Normal
--------------------------------
4770 ms EL: 0 cd .
4070 ms EL: 0 ver >nul
4010 ms EL: 0 date /t >nul
4050 ms EL: 0 time /t >nul
4060 ms EL: 0 verify >nul
2890 ms EL: 0 call &&@
2750 ms EL: 0 call &
2870 ms EL: 0 call ||@
2930 ms EL: 0 call;
2590 ms EL: 0 (call )
2740 ms EL: 0 call
2430 ms EL: 1 call||@
2430 ms EL: 1 call&
2450 ms EL: 1 call&&@
2480 ms EL: 1 (call)
2450 ms EL: 1 call
ERROR: Has no effect "if 1==1 @"
ERROR: Has no effect "if not 1==1 @"
ERROR: Has no effect "if 1==2 @"
ERROR: Has no effect "set /a a=0"
5710 ms EL: 1 <nul set/p=
ERROR: Has no effect "(set /a a=0)"
4030 ms EL: 1 (2>nul \:)
3930 ms EL: 1 (2>nul md)
2530 ms EL: 1 (for /f %%i in () do .)||@
ERROR: Has no effect "rem"
stdout already redirected to nul
--------------------------------
5410 ms EL: 0 cd .
3570 ms EL: 0 ver >nul
3200 ms EL: 0 date /t >nul
3050 ms EL: 0 time /t >nul
3180 ms EL: 0 verify >nul
2610 ms EL: 0 call &&@
2550 ms EL: 0 call &
2610 ms EL: 0 call ||@
2760 ms EL: 0 call;
2580 ms EL: 0 (call )
2620 ms EL: 0 call
2450 ms EL: 1 call||@
2470 ms EL: 1 call&
2480 ms EL: 1 call&&@
2480 ms EL: 1 (call)
2490 ms EL: 1 call
ERROR: Has no effect "if 1==1 @"
ERROR: Has no effect "if not 1==1 @"
ERROR: Has no effect "if 1==2 @"
ERROR: Has no effect "set /a a=0"
5040 ms EL: 1 <nul set/p=
ERROR: Has no effect "(set /a a=0)"
4170 ms EL: 1 (2>nul \:)
3930 ms EL: 1 (2>nul md)
2670 ms EL: 1 (for /f %%i in () do .)||@
ERROR: Has no effect "rem"
In german you can say "Wer mißt, mißt Mist" translated to something like "Who measures measures rubbish!" but "rubbish" is in german the same word like measure
Re: Why REM inside a block?
Posted: 27 Feb 2018 06:49
by dbenham
Good catch jeb, and nice :test design
Although both tests are legitimate - Sometimes you need to set or clear the ERRORLEVEL within a large FOR loop.
jeb wrote: ↑27 Feb 2018 03:04
In german you can say "Wer mißt, mißt Mist" translated to something like "Who measures measures rubbish!" but "rubbish" is in german the same word like measure
Indeed - you forgot to remove the >nul from your commands in the 2nd round of tests with stdout already redirected
Here are some results from my home machine:
Code: Select all
Normal
--------------------------------
5520 ms EL: 0 date /t >nul
5560 ms EL: 0 time /t >nul
5480 ms EL: 0 verify >nul
5090 ms EL: 0 call;
4990 ms EL: 0 (call )
4560 ms EL: 1 (call)
4020 ms EL: 1 (for /f %%a in () do rem.)||rem
stdout already redirected to nul
--------------------------------
3830 ms EL: 0 date /t
3880 ms EL: 0 time /t
3890 ms EL: 0 verify
4170 ms EL: 0 call;
4080 ms EL: 0 (call )
3680 ms EL: 1 (call)
3160 ms EL: 1 (for /f %%a in () do rem.)||rem
I am surprised that even commands that don't require redirection show a significant improvement in performance if stdout is already redirected to nul.
I ran the script a number of times, and the results were consistent.
Re: Why REM inside a block?
Posted: 12 Apr 2018 02:31
by miskox
Hello all!
Sorry for a 'short' (long?) delay in giving an answer (I was ill, my wife had a surgery... so priorities change).
Thank you all for your tests, thoughts...
Saso