This behavior seems more like an error prevention mechanism of
Extensions'
if command that is only visible with variable expansion on command execution. Here is my reasoning.
Variable behavioral refresher...
Variable expansion using percent signs %var% happens when the entire command line is read.
Variable expansion using exclamation marks !var! happens when the specific commands using the variable are executed.
Variable expansion for loop variables %%var happens when the specific commands using the variables are executed.
CMD BATCH behavioral refresher... (not conclusive)
1. Line is read (from command or batch) and percent sign %var% variables are expanded.
A line includes everything that is contained within the command's scope. This can span multiple lines when parentheses scope modifiers are used.
For example in the example above, both of the %var% will be expanded since they are both in the same line of scope for the if command.
2. Validation for proper syntax is performed on the line.
3. As the individual commands are executed on the line, the %%var and !var! variables are expanded.
The variables being expanded upon execution of the commands get to bypass the syntax validation for their values. The validation is not needed since the commands were already able to be properly parsed. So the value of the variables get passed directly to the commands being executed.
Variable Expansion Code Illustration
Code: Select all
@echo off
setlocal EnableExtensions EnableDelayedExpansion
:: Illustrates command execution expansion vs line read expansion.
set "X=%%%%~A"
set "Y=%%A%%"
set "Z=%%B%%"
:: Set A to a character known for a script error issue.
set "A=()"
set "B="
for %%A in ("()") do (
echo !X! = %%~A
echo ^^!A^^! = !A!
echo !Y! will cause a scope close error due to expansion upon read.
rem echo %A%
set "B=[]"
for %%A in ("%B%") do (
echo Sub !X! from !Z! = %%~A
)
for %%A in ("!B!") do (
echo Sub !X! from ^^!B^^! = %%~A
)
echo !X! = %%~A
)
echo No issues outside of scope for the parentheses.
echo %A%
echo %B%
endlocal
pause >nul
Therefore, this issue is not really an issue, but the undefined behavior of
Extensions'
if command. As Liviu already quoted, there is a vague explanation in the
if /? documentation.
These comparisons are generic, in that if both string1 and string2 are both comprised of all numeric digits, then the strings are converted to numbers and a numeric comparison is performed.
So when a
nul value is passed to the
if command, instead of throwing an error stating that the input is invalid, it just uses the base default value for the type comparison needed.
My Mental Disassembly of the IF EQU Code
Code: Select all
bool equ(string sLeft, string sRight)
{
// A string is numeric unless it contains characters other than 0-9.
// This allows blank strings to be both numeric and alpha.
if (IsNumeric(sLeft) && IsNumeric(sRight))
{
// ToNumber returns 0 when the string is blank.
int nLeft(sLeft.ToNumber());
int nRight(sRight.ToNumber());
return (nLeft == nRight);
}
// A blank string has a string weight of null "/0".
return (0 == sLeft.ASCIICompare(sRight));
}
Regular variable expansion does not exhibit this undefined behavior because undefined variables are evaluated to blank strings and they get caught at step 2, the validation of the command.
So when the only string specified is a number, the if command performs a numeric comparison with a base type value of 0.
When the only string specified is not a number, the if command performs a string comparison with a null string.
Here is my Tester Script (Contains SOH character, 0x01 Hex value)
Code: Select all
@echo off
setlocal EnableExtensions EnableDelayedExpansion
echo THE IF COMMAND AND DELAYED EXPANSION
:: Create an undefined variable.
set "undefined="
:: String Comparison [ Blank Strings are Evaluated to Null ]
if !undefined! geq (echo ^^!undefined^^! ^>= SOH ) else (echo ^^!undefined^^! ^< SOH )
if !undefined! lss (echo ^^!undefined^^! ^< SOH ) else (echo ^^!undefined^^! ^>= SOH )
if !undefined! equ !undefined! (echo ^^!undefined^^! = ^^!undefined^^! ) else (echo ^^!undefined^^! = ? )
:: Number Comparison [ Blank Strings are Evaluated to Zero ]
if !undefined! lss 1 (echo ^^!undefined^^! ^< 1 ) else (echo ^^!undefined^^! ^>= 1 )
if !undefined! equ 0 (echo ^^!undefined^^! = 0 ) else (echo ^^!undefined^^! ^<^> 0 )
if !undefined! gtr -1 (echo ^^!undefined^^! ^> -1 ) else (echo ^^!undefined^^! ^<= -1 )
:: It does not matter which side the undefined variable is on.
set "X=%%%%~A"
for %%A in ("") do (
rem String Comparison [ Blank Strings are Evaluated to Null ]
if %%~A geq (echo !X! ^>= SOH ) else (echo !X! ^< SOH )
if %%~A lss (echo !X! ^< SOH ) else (echo !X! ^>= SOH )
if %%~A equ %%~A (echo !X! = !X! ) else (echo !X! = ? )
rem Number Comparison [ Blank Strings are Evaluated to Zero ]
if %%~A lss 1 (echo !X! ^< 1 ) else (echo !X! ^>= 1 )
if %%~A equ 0 (echo !X! = 0 ) else (echo !X! ^<^> 0 )
if %%~A gtr -1 (echo !X! ^> -1 ) else (echo !X! ^<= -1 )
)
echo Done
endlocal
pause >nul
I also noticed, that this behavior only appears within BATCH scripts and does not happen on the command line directly (Except with the
for loop command as pointed out by dbenham below). The reasoning for this is, on the command line directly, when a variable is undefined, the variable cannot be expanded and therefore, no expansion is performed on the variable name and it is used as a string directly. See the code snippet below.
Code: Select all
cmd /E:ON /V:ON
set "undefined="
echo %undefined%
echo !undefined!
set "undefined=0"
echo %undefined%
echo !undefined!
TLDR: This is the undefined behavior of the
IF command with
EXTENSIONS. This is only visible due to the ability of variables expanded on command execution to bypass the line syntax validation step that regular variable expansion has to pass.
David Ruhmann