I recently found the post Find if substring is in string (not in a file) where it is stated that considering
@setlocal enableextensions enabledelayedexpansion
@echo off
set str1=%1
if not x%str1:bcd=%==x%str1% echo It contains bcd
endlocal
then
the
x
before the two sides of the equality is to ensure that the stringbcd
works okay. It also protects against certain "improper" starting characters.
However, I haven't found any explanation about the actual effect of this x
. So what is the difference between x"%string%"
and "%string%"
?
That is simply a very bad coded string comparison. The x
on both sides makes it possible to compare the two strings even if %str1:bcd=%
or %str1%
is substituted by Windows command processor on parsing entire command line by an empty string before execution of command IF.
But the batch file execution is nevertheless exited immediately by cmd.exe
because of a syntax error in case of value of environment variable str1
contains a space character or "&<>|
.
Enclosing an argument string in double quotes results in getting all characters except percent sign and with enabled delayed environment variable expansion also the exclamation mark interpreted as literal character including space which is outside a double quoted string interpreted as argument string separator.
So much better is:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
if "%~1" == "" goto EndBatch
set "str1=%~1"
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
The first argument of the batch file is compared first without surrounding double quotes with an empty string. So if the batch file is started without any argument or with just ""
as first argument string, Windows command processor executes the command GOTO resulting in restoring previous environment pushed on stack with command SETLOCAL and exits the batch file.
Otherwise the batch file is called really with an argument string. This argument string is assigned to environment variable str1
with removing surrounding double quotes if there are one. So on calling batch file with argument test
the value test
is assigned to environment variable str1
and on calling it with "another test"
the value another test
without the double quotes is assigned to str1
. And even on calling the batch file with wrong coded argument string "bcd test
(missing second "
) just bcd test
is assigned to the environment variable str1
.
The IF condition compares the value of environment variable str1
with all occurrences of bcd
removed with the unmodified variable value. The double quotes around the two strings make it possible to compare the two strings even on containing space or ampersand or the redirection operators <>|
. The command IF includes the double quotes on comparing the two strings.
So is this code now safe?
No, it is not in case of somebody calls the batch file invalid with test_bcd"
as argument string on which first double quote is missing. In this case the first IF command line executed by cmd.exe
is:
if "test_bcd"" == "" goto EndBatch
The trailing "
of the wrong specified argument string is not removed by cmd.exe
and cause a syntax error on this command line on execution as it can be seen on running the batch file from within a command prompt window with first line modified to @echo on
.
One solution without using delayed environment variable expansion is:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
if not defined str1 goto EndBatch
set "str1=%str1:"=%"
if not defined str1 goto EndBatch
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
This code makes sure that str1
does not contain any double quote before executing the IF command comparing the strings.
Another solution is using delayed environment variable expansion:
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "str1=%~1"
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
That looks better as the above code without usage of delayed environment variable expansion. But it does not work as expected if the the argument string is for example "!Hello!"
because in this case the if not
condition is also true and output is therefore the message It contains bcd
although the string !Hello!
does not contain bcd
.
The solution is:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
setlocal EnableDelayedExpansion
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
endlocal
Delayed expansion is not enabled on assigning the argument string to environment variable str1
which results in getting the exclamation marks in string "!Hello!"
interpreted as literal characters. Then delayed expansion is enabled for making the string comparison with using delayed environment variable expansion which avoids that the command line itself is modified by cmd.exe
before execution of IF command.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
... explains %~1
, not so good as done here.echo /?
endlocal /?
goto /?
if /?
set /?
setlocal /?
See also:
set "variable=value"
should be used in general and not other variants.&
and ||
outside a double quoted argument string are interpreted by cmd.exe
.<>|&
outside a double quoted argument string are interpreted by cmd.exe
.The addition of x
(or any other alphabetic character) in front of a string ensures that the relational statement is syntactically valid even when/if the string is empty.
Suppose str1
is an empty string. Then the comparison %str1:bcd=%==%str1%
after the substitution degenerates to ==
, which is syntactically invalid.
However, with an x
in front, the comparison becomes x==x
and can be evaluated. Naturally, adding the same prefix to each of the two strings does not affect their (in)equality.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With