:ForIF Function, evaluates arguments like a IF inside a for (but there's strange behaviour ?)

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
shodan
Posts: 54
Joined: 01 May 2023 01:49

:ForIF Function, evaluates arguments like a IF inside a for (but there's strange behaviour ?)

#1 Post by shodan » 24 Sep 2023 17:37

Hi,

I needed to evaluate an IF inside a for loop.
I didn't want to go into delayedexpansion and out again.

So instead, I created the function :ForIF

Code: Select all

::Usage Call :ForIF FirstTerm Operator SecondTerm
:ForIF
set "_ForIF_first_term=%~1"
set "_ForIF_operator=%~2"
set "_ForIF_second_term=%~3"
if defined %_ForIF_first_term% call set "_ForIF_first_term=%%%_ForIF_first_term%%%"
if defined %_ForIF_second_term% call set "_ForIF_second_term=%%%_ForIF_second_term%%%"
if "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ( set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 0 ) else ( 
							set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 1 )
This function works as expected.

Code: Select all

call :forif 1 GTR 5 && echo 1 is greater than 5 || echo 1 is not greater than 5 
does return

Code: Select all

1 is not greater than 5
Great !

I have tested several more permutations, works as you would expect.


It even works byref too

Code: Select all

set _myfirstterm=6748
set _mysecondterm=2983
call :forif _myfirstterm EQU _mysecondterm && echo %_myfirstterm% is equal to %_mysecondterm% || echo %_myfirstterm% is not equal to %_mysecondterm%
it returns as expected

Code: Select all

6748 is not equal to 2983
I also ran multiple permutation and it runs as expected.


But when I went to operate as expected inside a for loop, suddenly it breaks !

Suppose this simple for loop

Code: Select all

for /l %%a in (1,1,20) do (
							set /a _myloop+=1
							call :ForIF _myloop LSS 10 && echo %%a
							)

You would expect it to print numbers 1 through 9
but it only outputs

Code: Select all

1
So I created a debug version of :ForIF

Code: Select all

::Usage Call :ForIFdebug FirstTerm Operator SecondTerm
:ForIFdebug
set "_ForIF_first_term=%~1"
set "_ForIF_operator=%~2"
set "_ForIF_second_term=%~3"
echo 00%_ForIF_first_term% %_ForIF_operator% %_ForIF_second_term%
if defined %_ForIF_first_term% call set "_ForIF_first_term=%%%_ForIF_first_term%%%"
if defined %_ForIF_second_term% call set "_ForIF_second_term=%%%_ForIF_second_term%%%"
if "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ( echo 11yes "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ) else ( echo 11no "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" )
if "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ( set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 0 ) else ( 
							set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 1 )
This will print the input parameters before dereferencing

Code: Select all

echo 00%_ForIF_first_term% %_ForIF_operator% %_ForIF_second_term%
This will directly print the result of the evaluation

Code: Select all

if "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ( echo 11yes "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ) else ( echo 11no "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" )
Here is the full code

Code: Select all

@echo off

:setup

:main
echo.&echo Calling :ForIF-broken-mini-DEMO debug version
call :ForIF-debug-broken-mini-DEMO
GoTo :EOF

:ForIF-debug-broken-mini-DEMO

echo.&echo This should print numbers from 1 to 9, this calls the ForIF debug version
set "_myloop="
for /l %%a in (1,1,20) do (
							set /a _myloop+=1
							call :ForIFdebug _myloop LSS 10 && echo debug%%a
							)

GoTo :EOF

::Usage Call :ForIFdebug FirstTerm Operator SecondTerm
:ForIFdebug
set "_ForIF_first_term=%~1"
set "_ForIF_operator=%~2"
set "_ForIF_second_term=%~3"
echo 00%_ForIF_first_term% %_ForIF_operator% %_ForIF_second_term%
if defined %_ForIF_first_term% call set "_ForIF_first_term=%%%_ForIF_first_term%%%"
if defined %_ForIF_second_term% call set "_ForIF_second_term=%%%_ForIF_second_term%%%"
if "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ( echo 11yes "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ) else ( echo 11no "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" )
if "%_ForIF_first_term%" %_ForIF_operator% "%_ForIF_second_term%" ( set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 0 ) else ( 
							set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 1 )

And here is the output of all that

Code: Select all

ForIF-DEMO.bat

Calling :ForIF-broken-mini-DEMO debug version

This should print numbers from 1 to 9, this calls the ForIF debug version
00_myloop LSS 10
11yes "1" LSS "10"
debug1
00_myloop LSS 10
11no "2" LSS "10"
00_myloop LSS 10
11no "3" LSS "10"
00_myloop LSS 10
11no "4" LSS "10"
00_myloop LSS 10
11no "5" LSS "10"
00_myloop LSS 10
11no "6" LSS "10"
00_myloop LSS 10
11no "7" LSS "10"
00_myloop LSS 10
11no "8" LSS "10"
00_myloop LSS 10
11no "9" LSS "10"
00_myloop LSS 10
11no "10" LSS "10"
00_myloop LSS 10
11no "11" LSS "10"
00_myloop LSS 10
11no "12" LSS "10"
00_myloop LSS 10
11no "13" LSS "10"
00_myloop LSS 10
11no "14" LSS "10"
00_myloop LSS 10
11no "15" LSS "10"
00_myloop LSS 10
11no "16" LSS "10"
00_myloop LSS 10
11no "17" LSS "10"
00_myloop LSS 10
11no "18" LSS "10"
00_myloop LSS 10
11no "19" LSS "10"
00_myloop LSS 10
11no "20" LSS "10"

I don't get it !!!

"19" LSS "10" evaluates to false !?

How is that possible ?

Here is my complete DEMO file with every test permutation
ForIF-DEMO.zip
(1.28 KiB) Downloaded 126 times

OJBakker
Expert
Posts: 89
Joined: 12 Aug 2011 13:57

Re: :ForIF Function, evaluates arguments like a IF inside a for (but there's strange behaviour ?)

#2 Post by OJBakker » 24 Sep 2023 18:52

You are using string-compare where you expect to do numerical-compare.

Examples:
if "2" LSS "10" (echo TRUE) else (echo FALSE)
False
explanation: "2 " > "10"

if "02" LSS "10" (echo TRUE) else (echo FALSE)
True

if 2 LSS 10 (echo TRUE) else (echo FALSE)
True

shodan
Posts: 54
Joined: 01 May 2023 01:49

Re: :ForIF Function, evaluates arguments like a IF inside a for (but there's strange behaviour ?)

#3 Post by shodan » 24 Sep 2023 19:37

Surprisingly easy fix for once !

Thank you @OJBakker for helping me bring this function over the finish line

Here is the working version
ForIF-DEMO.zip
(1.38 KiB) Downloaded 113 times

Code: Select all

::Usage Call :ForIF FirstTerm Operator SecondTerm
:ForIF
set "_ForIF_first_term=%1"
set "_ForIF_operator=%2"
set "_ForIF_second_term=%3"
if defined %_ForIF_first_term% call set "_ForIF_first_term=%%%_ForIF_first_term%%%"
if defined %_ForIF_second_term% call set "_ForIF_second_term=%%%_ForIF_second_term%%%"
if %_ForIF_first_term% %_ForIF_operator% %_ForIF_second_term% ( set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 0 ) else ( 
							set "_ForIF_first_term=" & set "_ForIF_operator=" & set "_ForIF_second_term=" & exit /b 1 )
Here is a simple use case

Code: Select all

for /l %%a in (1,1,20) do (
							set /a _myloop+=1
							call :ForIF _myloop LSS 10 && echo %%a
							)
Here is the full DEMO output

Code: Select all

Calling :ForIF-broken-mini-DEMO

This should print numbers from 1 to 9
1
2
3
4
5
6
7
8
9

Calling :ForIF-DEMO , this one works
 :ForIF function demonstrator

call :forif 1 GTR 5 && echo 1 is greater than 5 || echo 1 is not greater than 5
1 is not greater than 5

call :forif "" GTR 5 && echo "" is greater than 5 || echo "" is not greater than 5
"" is not greater than 5

call :forif 4 EQU 5 && echo 4 is equal to 5 || echo 4 is not equal to 5
4 is not equal to 5

call :forif letter EQU number && echo letter is equal to number || echo letter is not equal to number
letter is not equal to number

variables created
_myfirstterm = 6748 _mysecondterm = 2983
_mythirdterm = 6748 _myfourthterm = 1234

call :forif _myfirstterm EQU _mysecondterm && echo 6748 is equal to 2983 || echo 6748 is not equal to 2983
6748 is not equal to 2983

call :forif _mythirdterm EQU _myfourthterm && echo 6748 is equal to 1234 || echo 6748 is not equal to 1234
6748 is not equal to 1234

call :forif _myfirstterm EQU _mythirdterm && echo 6748 is equal to 6748 || echo 6748 is not equal to 6748
6748 is equal to 6748

call :forif 1 LSS 5 && echo 1 is less than 5 || echo 1 is not less than 5
1 is less than 5

call :forif "" LSS 5 && echo "" is less than 5 || echo "" is not less than 5
"" is less than 5

call :forif 4 NEQ 5 && echo 4 is not equal to 5 || echo 4 is not not equal to 5
4 is not equal to 5

call :forif letter NEQ number && echo letter is not equal to number || echo letter is not not equal to number
letter is not equal to number

call :forif _myfirstterm NEQ _mysecondterm && echo 6748 is not equal to 2983 || echo 6748 is not not equal to 2983
6748 is not equal to 2983

call :forif _mythirdterm NEQ _myfourthterm && echo 6748 is not equal to 1234 || echo 6748 is not not equal to 1234
6748 is not equal to 1234

call :forif _myfirstterm NEQ _mythirdterm && echo 6748 is not equal to 6748 || echo 6748 is not not equal to 6748
6748 is not not equal to 6748

This should print numbers from 1 to 9 and then the word no multiple times
1
2
3
4
5
6
7
8
9
no
no
no
no
no
no
no
no
no
no
no

This should print your first 10, and only 10, environement variables
ALLUSERSPROFILE=C:\ProgramData
APPDATA=C:\Users\user\AppData\Roaming
CommonProgramFiles=C:\Program Files\Common Files
CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
CommonProgramW6432=C:\Program Files\Common Files
COMPUTERNAME=USER-7IHCVR710M
ComSpec=C:\Windows\system32\cmd.exe
DriverData=C:\Windows\System32\Drivers\DriverData
FPS_BROWSER_APP_PROFILE_STRING=Internet Explorer

Post Reply