Why doesn't IF branch properly? - cmd

I already asked this in SS64.
I'm trying to use IF DEFINED to control branching, but it doesn't work. I will start with an example
setLocal
if defined _var (
:: _var is NOT defined, so the next line should not be executed
if %_var%=="test" echo %_var% is defined
)
endLocal
Processing stops when it hits the ECHO statement and exits with "echo was unexpected at this time." error message.
The problem is that the nested IF statement is being executed, and it shouldn't be because _var is not defined. How can I fix this?
Thanks,
Shane.

The real problem you have is just that you did not match the if statement correctly. To explain, see the following example.
if a == "a"..
vs
if "a"=="a"..
So in you example you were trying to match:
if test=="test"..
The reason we use double quotes on both sides of the variable is to ensure we eliminate any possible whitespace which might become part of the values you are testing.
So this version should be fine. I added the /i switch to match test case insensitive, so it will match test in any case combination. TesT, tEst, TEST, test etc.:
setLocal
set /p "_var=Enter Variable name: "
set "_var=%_var:"=%"
if defined _var (
:# rem _var is NOT defined, so the next line should not be executed
if /i "%_var%"=="test" echo %_var% is defined
)
endLocal

There are multiple issues with this small code block.
The first issue is using an invalid label with :: to write a comment inside a command block starting with ( and ending matching with ). A label is not allowed inside a command block. A line starting with : after 0 or more spaces/tabs results in undefined behavior on execution of the command block. Undefined behavior means it can work by chance, but it can also fail by chance. The execution behavior is undefined.
The command for a comment is REM. Run in a command prompt window rem /? for help on this command.
Please note that REM is a command and for that reason a comment line with REM is parsed by Windows command processor like any other command line which is important on comment line contains %variable% or operators like &|<>. So be careful on what is written in comment text.
The second issue is that the comment itself is not correct.
_var is NOT defined, so the next line should not be executed
This comment is wrong because the environment variable _var is definitely defined on cmd.exe reaching this line after execution of the command if defined _var with a positive result.
The third issue is that if %_var%=="test" is syntactically not 100% correct. There are missing the spaces around the comparison operator ==. But this syntax error is automatically detected by cmd.exe on processing the batch file with automatic correction by inserting the two missing spaces around operator ==. This can be seen on debugging the batch file.
The fourth issue is that IF compares two strings always with including the double quotes on string comparison. So on usage of if %_var%=="test" or syntactically correct if %var% == "test" the condition is only true if assigned to environment variable _var is the string "test" with the double quotes. This string comparison evaluates to false in all other cases on _var defined if not resulting in a syntax error before, see next issue.
Please read my answer on Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files explaining very detailed how a string comparison is done by command IF.
The fifth issue is that Windows command processor parses the entire command block starting with ( and ending with matching ) before executing the command IF.
The inner IF without environment variable _var defined results on parsing the command block in the line:
if =="test" echo is defined
cmd.exe detects a serious syntax error on this command line and exits batch file processing because of this syntax error before executing the outer IF condition.
One solution would be enclosing %var% in double quotes, i.e. use in batch file:
if "%_var%" == "test" echo %_var% is defined
This IF command line is correct if the environment variable _var is not defined on parsing the command block.
But this solution does not work if _var is defined and contains one or more " because of the double quotes result again in an invalid syntax for command line with command IF.
Further echo does not result in printing the value of the environment variable _var if the string assigned to the environment variable contains one of these characters &|<>. Well, echo is not executed in this case as the string comparison evaluates to false.
Therefore a better solution is enabling delayed expansion and referencing the value of environment variable _var with delayed environment variable expansion to avoid that the value of this environment variable modifies the command line to execute by cmd.exe after parsing entire command block.
setlocal EnableExtensions EnableDelayedExpansion
if defined _var (
rem _var is defined, so the next line should be executed.
if "!_var!" == "test" echo _var is defined with !_var!.
)
endlocal
The command extensions must be also enabled because otherwise if defined would not be supported by command IF. The command extensions are by default enabled, but it is better to write a batch file with no dependency on settings or defaults defined outside of the batch file as far as possible.
Well, the echo command could be also written with:
echo _var is defined with test.
It is not possible that echo prints here something other than test on using !_var!.
Another solution is to avoid the usage of a command block and make sure that the string value assigned to environment variable _var does not contain any " and output the value of this environment variable enclosed in double quotes to avoid that &|<> affect the execution of echo command line.
setlocal EnableExtensions DisableDelayedExpansion
if not defined _var goto Label
rem _var is defined, so the next line should be executed.
rem Remove all double quotes from string value of environment variable.
rem It is important to use double quotes around argument string of the
rem command SET as otherwise a string with "&|<>" would be not correct
rem assigned to the environment variable.
set "_var=%_var:"=%"
rem It could happen that _var is not defined anymore. But in this case
rem it is no problem anymore that cmd.exe replaces all %var% by nothing
rem on entire command line before executing the IF condition as the
rem remaining commands on this command line with enclosing the string
rem value of _var in double quotes is always syntactically correct even
rem on string value containing one or more of the operators "&|<>".
if /I "%_var%" == "test" echo _var is defined with "%_var%".
:Label
endlocal
The string comparison is done here case-insensitive.
See also:
Single line with multiple commands using Windows batch file
Microsoft article about Using command redirection operators
It must be always made sure that a user input string or a string passed to the batch file as argument or a string determined by the batch file itself for example from a file/folder name/path is enclosed in double quotes and does itself not contain double quotes for being correct processed by the batch file. While the characters |<> are not allowed in a file/folder name, & is allowed in a file/folder name and results in many batch files on being interpreted as AND operator on file/folder string not enclosed in double quotes, even on simply echoing the file/folder name.
See also the Microsoft documentation page Naming Files, Paths, and Namespaces.
It is very important to understand on writing a batch file How does the Windows Command Interpreter (CMD.EXE) parse scripts? It must be always taken into account how a command line finally looks on execution with environment variable references expanded during parsing phase of a command line or an entire command block. The finally executed command line after parsing must be of valid syntax or cmd.exe exits batch file processing.

Related

cmd for loop mass renaming again oneliner

I'm over my head with this - spent too much time searching already - evidently I don't understand the basics of CMD variables etc. - and it always gives me such a headache
why wouldn't this work?
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
the above code outputs the value of %tmpx% in some other scope - and it is always constant
yes, i run setlocal ENABLEDELAYEDEXPANSION
basically i need to do a simple rename of all files in folder from constantstring_somenameXX.tif to somenameXX.tif, where i.e. constantstring=0000000005
i had to use set because other posts rightly suggested that %a in a for loop has a special behaviour, and the substitutions wouldn't work for it as it is.
i would prefer not to use scripts and/or powershell - unless not using them is impossible
thank you
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
The problem with the previous code is delayed expansion. Yes, you enabled it, but you have not used it, and depending on how you enabled it, it will not work
In cmd, when a line or block of lines (code inside parenthesis) is reached, it is first parsed and then executed. During the parse phase, variable read operations are removed from the command, replaced with the value in the variable before the command starts to execute. So, if you change the value of a variable inside a line/block you can not retrieve the changed value inside the same line/block as there are no variable reads (they were replaced)
setlocal enabledelayedexpansion allows to replace (where needed) the variable read syntax from %var% to !var!, indicating to the parser that the read operation will be delayed until the execution phase.
So, in your case, your code should have been something like
setlocal enabledelayedexpansion & for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )
BUT this will not work (in default configured environments).
cmd has two execution modes: batch file and command line. In your case, you are using command line (no escaped percent sign in for loop) and in command line mode the setlocal enabledelayedexpansion will not work. It is intended for batch files (see setlocal /?)
How to make it work from the command line? By default cmd is started with delayed expansion disabled and you can not enable it if not inside a batch file. But you can start cmd with delayed expansion enabled and run your command in this started instance (see cmd /?)
cmd /v:on /c "for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )"
Anyway, to solve your rename problem, delayed expansion is not needed
for %a in (*_*.tif) do for /f "tokens=1,* delims=_" %b in ("%~nxa") do echo ren "%a" "%c"
That is, for each tif file with an underscore, take the name and extension of the file (%~nxa) as a string, and using the underscore as a delimiter between tokens, retrieve the first token (the text on the left of the first underscore) in %b and the rest of the text (to the right of the underscore) into %c. Now, just rename the original file name (stored in %a) to the contents of %c (the text on the right of the underscore)
In this code rename operations are only echoed to console. If the output is correct, remove the echo command.
! is the character to use rather than % when wanting execution time value. % does when it's read value.
CMD was written by IBM engineers and they were trying to make MSDos a programming language while making sure Dos commands ran the same. So we get a hodge podge.
& seperates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatinate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SelLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.

How to output special characters like angle brackets into an HTML/XHTML/XML file?

I want to put the following content into the file: <ScriptFile Make="3">
It fails for the reason of the string containing the angle brackets < and > and the double quote character ".
I have tried escaping the characters following way: ^<ScriptFile Make=""3""^>
It worked, but the output in the file was exactly the same as the escaped string.
The code snippet:
#echo off
set TEMP="^<ScriptFile Make=""3""^>"
echo %TEMP% > gen.xml
pause
How can I output the string value of TEMP variable into file gen.xml without loosing the double quotes and the angle brackets?
You can extract the angle brackets out of the variable, like this:
#echo off
set TEMP1=ScriptFile Make="3"
echo ^<%TEMP1%^> > gen.xml
pause
This way, the brackets can be escaped properly, you do not need any special escaping for the string put in the variable and the gen.xml looks like expected:
D:\temp>type gen.xml
<ScriptFile Make="3">
This worked for me:
#echo off
set "TEMP=^<ScriptFile Make="3"^>"
echo %TEMP% > gen.xml
pause
Another method would be to use delayed expansion:
#echo off
set "TEMP=<ScriptFile Make="3">"
setlocal EnableDelayedExpansion
echo !TEMP! > gen.xml
endlocal
pause
TEMP should not be used as environment variable name because TEMP is an important environment variable predefined by Windows. It has as value the name of the directory for temporary files of current user account with complete path. For details see Wikipedia article Windows Environment Variables.
One method is using delayed expansion as suggested also by Andriy M.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "TempVar=<ScriptFile Make="3">"
echo !TempVar!>gen.xml
endlocal
First a local environment is created for the next two command lines with command extensions and delayed expansion of environment variables enabled which are both needed here. Command extensions are enabled by default, but delayed expansion is disabled by default. See this answer explaining in detail what the commands SETLOCAL and ENDLOCAL do.
The string <ScriptFile Make="3"> is assigned next to environment variable TempVar. There is no need to escape the angle brackets or the double quotes. For a detailed explanation why there is no need to escape anything in this string and why first double quote character is left of variable name and not after equal sign read this answer.
The value of the environment variable is output by command ECHO with redirecting this output with the redirection operator > into the file gen.xml using delayed expansion.
There is no space character between second exclamation mark ! and redirection operator >. This avoids writing also a trailing space after <ScriptFile Make="3"> into the file. 1 or more space characters between > and file name gen.xml are ignored on parsing this command line. But any whitespace character left of redirection operator > is also output by command ECHO and for that reason also written into the file.
Another method is not using an environment variable at all and escape the angle brackets with character caret ^ as demonstrated below:
#echo off
echo ^<ScriptFile Make="3"^>>gen.xml
endlocal
Double quotes must not be escaped on using command ECHO as in this special case the double quote characters are interpreted as literal characters.
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.
echo /?
endlocal /?
set /?
setlocal /?
And read also the Microsoft TechNet article Using command redirection operators.

Windows batch file syntax using exclamation mark

While checking the details of the axis2server.bat file in Axis2 binary distribution I see one of the line containing text something like:
FOR %%c in ("%AXIS2_HOME%\lib\*.jar") DO set AXIS2_CLASS_PATH=!AXIS2_CLASS_PATH!;%%c
What does the part below with 2 exclamation marks mean?
!AXIS2_CLASS_PATH!
Names with in % mean variables, not sure what ! mark mean in a batch file.
When you enable delayed expansion and change or set a variable within a loop then the !variable! syntax allows you to use the variable within the loop.
A drawback is that ! becomes a poison character for delayed expansion.
As foxidrive mentioned, this is related to delayed expansion. You can find more information by running help set in a cmd prompt, which has the following explanation:
Finally, support for delayed environment variable expansion has been
added. This support is always disabled by default, but may be
enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?
Delayed environment variable expansion is useful for getting around
the limitations of the current expansion which happens when a line
of text is read, not when it is executed. The following example
demonstrates the problem with immediate variable expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since the %VAR% in BOTH IF statements
is substituted when the first IF statement is read, since it logically
includes the body of the IF, which is a compound statement. So the
IF inside the compound statement is really comparing "before" with
"after" which will never be equal. Similarly, the following example
will not work as expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of files in the current directory,
but instead will just set the LIST variable to the last file found.
Again, this is because the %LIST% is expanded just once when the
FOR statement is read, and at that time the LIST variable is empty.
So the actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the last file found.
Delayed environment variable expansion allows you to use a different
character (the exclamation mark) to expand environment variables at
execution time. If delayed variable expansion is enabled, the above
examples could be written as follows to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
I wanted to hand over a string containing a "!" as parameter (for imageMagick) and the ! was of course interpreted as syntax which broke my script. The solution was for me, change my string from
"Hello World!"
to (just added a ^ before the !):
"Hello World^!"
I found this trick by reading here: https://www.robvanderwoude.com/escapechars.php

Windows command line tilde operators for full environment variables?

In cmd, you can use the tilde "operator" to do some cool tricks with arguments passed in. For example, %~dp0 returns the pathname of the current script.
Can you do that for any environment variable? For example:
set foo=1234.exe
echo %~nfoo%
Is there a way to accomplish this?
You can also filter your variable through a for loop instead of a subroutine:
setlocal
set foo=1234.exe
for %%I in ("%foo%") do echo %%~nI
Yes, sort of.
In the following file (I named test1.cmd) there is an example of passing a file path and name to a subroutine in the same .cmd file and getting the drive letter and path back. By setting more environment variables on the last line of the subroutine you could return more combinations of drive letter, path, file name, attributes, etc.
That last line is the important part. The Windows command processor evaluates a line at a time, so that line first expands environment variables to their text values and then proccesses the line. That expanded line destroys the scope of the subroutine, returning to the outer scope (endlocal), sets a new variable (mytest2) to the value of the subroutine's (expanded) %dp1, then executes a goto :eof, which returns to the calling line.
setlocal
set mytest=c:\windows\a file with spaces in name.txt
call :mytest2 "%mytest%"
echo %mytest2%
:ender
endlocal
goto :eof
:mytest2
setlocal
echo %~dp1
endlocal && set mytest2=%~dp1 && goto :eof

Batch String Concatenation

I am trying to create a batch string like this: >abcd_
I have a variable called soeid, with value as abcd. So this is what i am doing, but it does not work.
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo %str%
I'm sure it is working just fine. To prove it, add SET STR after you define the value, and you will see the correct value.
The problem you are having is when you try to echo the value, the line that is executing becomes: echo >abcd_. The > is not quoted or escaped, so it is simply taking the ouput of ECHO with no arguments and redirecting it to a file named "abcd_"
If you don't mind seeing quotes, then change your line to echo "%str%" and it will work.
The other option is to enable and use delayed expansion (I'm assuming this is a batch script code, and not executing on the command line)
setlocal enableDelayedExpansion
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo !str!
Normal %var% expansion occurs early on while the interpreter is parsing the line. Delayed !var! expansion occurs at the end just before it is executed. The redirection is detected somewhere in the middle. That is why the normal expansion doesn't work - the interpreter sees the expanded > character and interprets it as the output redirection operator. The delayed expansion hides the > character from the interpreter until after redirection is parsed.
For more info about delayed expansion, type SET /? from the command line and read starting with the paragraph that starts with "Finally, support for delayed environment variable expansion...".

Resources