Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between "..." and x"..." in an IF condition in a Windows batch file?

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 string bcd 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%"?

like image 215
Tsathoggua Avatar asked Sep 19 '18 21:09

Tsathoggua


2 Answers

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:

  • How does the Windows Command Interpreter (CMD.EXE) parse scripts? ... a long answer every batch file writer should read carefully from top to bottom.
  • forfiles - FALSE vs. false (doesn't work with lower case?) ... this answer is about argument processing of command IF.
  • Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files ... this answer explains in detail how string comparisons are done by command IF.
  • Why is no string output with 'echo %var%' after using 'set var = text' on command line? ... this answer explains why set "variable=value" should be used in general and not other variants.
  • Single line with multiple commands using Windows batch file ... explains how & and || outside a double quoted argument string are interpreted by cmd.exe.
  • Microsoft article about Using command redirection operators explains how <>|& outside a double quoted argument string are interpreted by cmd.exe.
like image 129
Mofi Avatar answered Sep 29 '22 02:09

Mofi


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.

like image 36
DYZ Avatar answered Sep 29 '22 00:09

DYZ