Batch file ENABLEDELAYEDEXPANSION is not writing variables out - loops

I have the below code snippet which outputs a variable in to a text file.
%drone% will be a simple text string, and the %counter% is a number which is in a loop to count the iterations.
The echo %drone% !counter! echos the correct values to the screen but in the echo Done: %drone% Files deleted: !tempcounter! >> clearTempFilesonDrones.txt code the !tempcounter! variable is blank when outputted to the file.
I've tried to set it to a temp variable and use %tempvariable%, but that didn't work. I also tried like "!counter!" incase it would think that that's a string instead of just a number, but didn't work either.
echo %drone% %counter%
endlocal
echo Done: %drone% Files deleted: !counter! >> clearTempFilesonDrones.txt
Any ideas?
I can't copy the entire code onto a pastebin or the like as blocked in current client, so just uploaded an extract.

your endlocal ends the delayed expansion, meaning you can't use ! variables afterwards anymore. try simply using
echo Done: %drone% Files deleted: %counter% >> clearTempFilesonDrones.txt
Note that variables which are first accessed with !temp! in a code block can be accessed with regular %temp% afterwards
If that doesn't work I suggest trying to remove the endlocal, or placing it in a line below the last echo.

It looks like behavior of setlocal without or with 1 or 2 of the optional 4 parameters and endlocal is not really known by you.
See the answers on
change directory command cd ..not working in batch file after npm install
Why is my cd %myVar% being ignored?
Echoing a URL in Batch
which describe in detail with examples what setlocal and endlocal really do.
The current values of the environment variables drone and counter must be passed from current environment variables table deleted by endlocal to the restored environment variables table active before setlocal and active again after endlocal.
This can be done as follows:
echo %drone% %counter%
endlocal & set "drone=%drone%" & set "counter=%counter%"
echo Done: %drone% Files deleted: %counter% >>clearTempFilesonDrones.txt
The entire command line with the command endlocal and the two set commands is first preprocessed by Windows command processor with the values of current environment variable resulting in replacing the variable references by their current values. So the line is after preprocessing:
endlocal & set "drone=value of drone variable" & set "counter=counter value"
Now first endlocal is executed which discards the current environment variables table and restores previous environment variables table. Then then two set commands are executed which assigns the two strings to the two variables in now active environment variables table.
For an explanation of operator & see the answer on Single line with multiple commands using Windows batch file.
Further I suggest to open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
endlocal /?
set /?
setlocal /?
The built-in help is not so detailed as the referenced answers, but should be nevertheless read.

Related

Using CMDER, issue with setting PATH

I'm trying to use CMDER for a development environment that I've setup.
Basically I've created a .bat file that calls:
#ECHO OFF
start Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\Cmder.exe
Then I've placed the file startdev.bat in:
%CMDER_HOME%\config\profile.d
So everything seems to work just fine, but when the startdev.bat finishes, issuing an:
echo %PATH%
returns:
Z:\_DEV\OS_WINDOWS\1_COMPILER\JDK\ORACLE\1.8.0_181\bin;Z:\_DEV\OS_CYGWIN\bin;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CLutils;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\PUTTY;Z:\_DEV\OS_WINDOWS\6_VERSION_CONTROL\PortableGit\bin;C:\WINDOWS;C:\WINDOWS\SysWOW64;C:\WINDOWS\System32
...any idea what's happening?
I would either expect CMDER to override PATH with the value from its own settings, or use my full path, which before the startdev.bat ends shows the value of:
PATH=Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\vendor\conemu-maximus5;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\vendor\conemu-maximus5\ConEmu;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\vendor\conemu-maximus5\ConEmu\Scripts;Z:\_DEV\OS_ALL\JVM\3_BUILD_TOOLS\GRADLE\5.4\bin;Z:\_DEV\OS_ALL\JVM\3_BUILD_TOOLS\MAVEN\3.5.4\bin;Z:\_DEV\OS_ALL\JVM\3_BUILD_TOOLS\ANT\1.10.5\bin;Z:\_DEV\OS_WINDOWS\3_BUILD_TOOLS\NODE\LTS\10.15.3;Z:\_DEV\OS_WINDOWS\3_BUILD_TOOLS\NODE\LTS\10.15.3\node_modules;Z:\_DEV\OS_WINDOWS\1_COMPILER\GO\1.12.4\bin;Z:\_DEV\OS_WINDOWS\1_COMPILER\PYTHON\32bit\2.7.13;Z:\_DEV\OS_WINDOWS\1_COMPILER\PYTHON\32bit\2.7.13\scripts;Z:\_DEV\OS_WINDOWS\1_COMPILER\ANDROID\android-sdk-windows\platform-tools;Z:\_DEV\OS_WINDOWS\1_COMPILER\JDK\ORACLE\1.8.0_181\bin;Z:\_DEV\OS_CYGWIN\bin;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CLutils;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\PUTTY;Z:\_DEV\OS_WINDOWS\6_VERSION_CONTROL\PortableGit\bin;C:\WINDOWS;C:\WINDOWS\SysWOW64;C:\WINDOWS\System32
..but the fact that it only seems to be keeping the value as defined about halfway through the batch job is strange.
Any ideas?
First I recommend opening a command prompt window and run setlocal /? and endlocal /? to get displayed the help/documentation for those two commands. Very important to know is that every setlocal without a corresponding endlocal results in an implicit execution of endlocal by cmd.exe before exiting processing of a batch file or a subroutine called with command CALL.
Next I suggest reading this answer for even more details about the commands SETLOCAL and ENDLOCAL and what happens on using them.
I suggest like michael_heath to change this code block:
setLocal EnableDelayedExpansion
set CLASSPATH=.
for /R %JRE_HOME%\lib %%a in (*.jar) do (
set CLASSPATH=!CLASSPATH!;%%a
)
set CLASSPATH=!CLASSPATH!
Better would be:
setLocal EnableExtensions EnableDelayedExpansion
set CLASSPATH=.
for /R "%JRE_HOME%\lib" %%a in (*.jar) do set "CLASSPATH=!CLASSPATH!;%%a"
endlocal & set "CLASSPATH=%CLASSPATH%"
Now the local environment is ended with passing the environment variable CLASSPATH from local environment, on which it was defined, to the restored previous environment because of cmd.exe expands %CLASSPATH% to current value of the environment variable CLASSPATH in current local environment before executing the command endlocal which restores the previous environment.
Wrong in your batch file is also set WINDIR=%SystemRoot%;%SystemRoot% which should be set "WINDIR=%SystemRoot%".
I recommend further reading Why is no string output with 'echo %var%' after using 'set var = text' on command line? It explains why the syntax set "variable=string value" is recommended nowadays. Many of the environment variable definitions use directly or indirectly %UserProfile% which means depending on whatever the user currently running the batch file has entered as user name on creation of the user account. I have seen users entering their name containing a space and non ASCII characters. And I have seen users creating an account with a user name containing character & like Company GmbH & Co. An ampersand outside a double quoted argument string is interpreted as AND operator and cmd.exe tries to execute after set also the remaining string after & as command line on using something like set USERHOME=%DEVHOME%\%USERNAME% instead of set "USERHOME=%DEVHOME%\%USERNAME%". Well, startdev.bat redefines nearly all predefined Windows Environment Variables including USERNAME and USERPROFILE and so is written safe for most environment variable definitions.
This code block is also not optimal:
FOR /F "usebackq" %%i IN (`hostname`) DO SET HOSTNAME=%%i
echo Running on hostname: %HOSTNAME%
The host name respectively computer name could contain also a space or characters critical for command line or start with a semicolon for some unknown reason. So better would be:
FOR /F delims^=^ eol^= %%i IN ('hostname') DO SET "HOSTNAME=%%i"
setlocal EnableDelayedExpansion & echo Running on host name: !HOSTNAME!& endlocal
Whereby there is the environment variable COMPUTERNAME predefined by Windows making it possible to use just following command line:
setlocal EnableDelayedExpansion & echo Running on host name: !ComputerName!& endlocal
An ECHO command line containing an immediately expanded environment variable reference on which it is unknown if its value contains &|<> is always a problem because of the environment variable reference is expanded before further processing of the command line by cmd.exe as described at How does the Windows Command Interpreter (CMD.EXE) parse scripts?
I suggest also reading DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ and avoid the usage of echo. in the batch file to output an empty line.
"halfway through the batch job" as you have a
setLocal EnableDelayedExpansion which sets any further
changes to the variable PATH or other set variables as local.
The endLocal not specified is implied at the end of the script.
To resolve this, use endLocal and set CLASSPATH=%CLASSPATH%
on the same parsed line to set CLASSPATH as global.
Change this part:
setLocal EnableDelayedExpansion
set CLASSPATH=.
for /R %JRE_HOME%\lib %%a in (*.jar) do (
set CLASSPATH=!CLASSPATH!;%%a
)
set CLASSPATH=!CLASSPATH!
to this:
setLocal EnableDelayedExpansion
set CLASSPATH=.
for /R %JRE_HOME%\lib %%a in (*.jar) do (
set CLASSPATH=!CLASSPATH!;%%a
)
endLocal & set CLASSPATH=%CLASSPATH%
After that changed part, the script will set variables as global again.

Why does Windows command interpreter output the error message: ) was unexpected

I'm making a Minecraft modding tool using a batch file. But on execution of the batch file the Windows command interpreter outputs the syntax error message:
) was unexpected
I can't figure out why.
This is my code:
#echo off
cd mods
setlocal enabledelayedexpansion
set "selected=1"
call:print 1
call:print 2
:menu
choice /c wse>nul
if "%errorlevel%"=="2" (
if not !selected! GEQ !a! (
set /a "selected+=1"
cls
call:print 1
call:print 2
)
)
if "%errorlevel%"=="1" (
if not !selected!==1 (
set /a "selected-=1"
cls
call:print 1
call:print 2
)
)
if "%errorlevel%"=="3" (
)
goto menu
:print
if "%1"=="1"set a=0
echo.
if "%1"=="1" (
echo Uninstalled:
) else (
echo Installed:
)
echo.
for %%f in (*.jar) do (
if "%1"=="1" (
if NOT EXIST
"C:/Users/Coornhert/AppData/Roaming/.minecraft/mods/%%~nf.jar" (
set /a "a+=1"
if "!a!"=="!selected!" (
echo -%%~nf
) else (
echo %%~nf
)
set "b=!a!"
)
) else (
if EXIST "C:/Users/Coornhert/AppData/Roaming/.minecraft/mods/%%~nf.jar" (
set /a "a+=1"
if "!a!"=="!selected!" (
echo -%%~nf
) else (
echo %%~nf
)
set "b=!a!"
)
)
)
goto :eof
And it works, but when I hit s, execution terminates with the error message.
Folder structure of folder containing the batch file:
mods
Foo.jar
Foo2.jar
Folder structure of target folder:
C:\Users\Coornhert\AppData\Roaming\.minecraft\mods
Foo.jar
I partly do not understand what this batch file should do, but here is the batch file rewritten with several improvements.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem cd /D "%~dp0mods"
pushd "%~dp0mods"
set "a=0"
set "selected=1"
call :PrintIt 1
call :PrintIt 2
:Menu
choice /C wse /N
if errorlevel 3 popd & endlocal & goto :EOF
if errorlevel 2 goto AddOne
if %selected% == 1 goto Menu
set /A selected-=1
cls
call :PrintIt 1
call :PrintIt 2
goto Menu
:AddOne
if %selected% GEQ %a% goto Menu
set /A selected+=1
cls
call :PrintIt 1
call :PrintIt 2
goto Menu
:PrintIt
if %1 == 1 set "a=0"
echo/
if %1 == 1 (echo Uninstalled:) else echo Installed:
echo/
for %%I in (*.jar) do (
if %1 == 1 (
if not exist "%APPDATA%\.minecraft\mods\%%~nI.jar" (
set /A a+=1
if !a! == %selected% (echo -%%~nI) else echo %%~nI
set "b=!a!"
)
) else (
if exist "%APPDATA%\.minecraft\mods\%%~nI.jar" (
set /A a+=1
if !a! == %selected% (echo -%%~nI) else echo %%~nI
set "b=!a!"
)
)
)
goto :EOF
It does nothing useful as is, but batch code in question is also not useful at all.
The applied improvements are:
The command SETLOCAL is moved to top of file. The reason is:
It pushes path of current directory on stack.
It pushes state of command extensions on stack.
It pushes state of delayed expansion on stack.
It pushes the memory address of the current environment variables table on stack.
It creates a copy of the current environment variables table in memory and makes this new environment variables table active.
It sets command extensions and delayed expansion according to the specified parameters if the command is called with parameters at all.
The command ENDLOCAL is executed before leaving batch file. The reason is:
It deletes the current environment table which means no environment variable defined in this batch file exists anymore after ENDLOCAL except it existed already before execution of command SETLOCAL.
It pops memory address of previous environment table from stack and uses this address resulting in restoring initial environment variables.
It pops state of delayed expansion from stack and disables/enables delayed expansion accordingly.
It pops state of command extensions from stack and disables/enables command extensions accordingly.
It pops previous current directory path from stack and sets current directory to this path to restore the current directory.
So the entire command process environment is restored on exit of this batch file to exactly the same environment as it was on starting the batch file.
This makes it possible to call this batch file from within another batch file or from within a command prompt window with no impact on calling batch file or command process.
The command CD could be extended to include drive and path of argument 0 which is the full path of the batch file ending with a backslash because the subdirectory mods is most likely always expected in directory of the batch file and it should not matter what is the current directory on running the batch file.
But cd /D "%~dp0mods" could fail if the batch file is located on a network share accessed using UNC path and therefore command PUSHD is used instead working with enabled command extensions also for UNC paths.
In all programming and scripting languages it is required that variables are defined and initialized with a value before being used the first time. For that reason the environment variables a and selected are defined at top of the batch file with default values. By the way: a is a very bad name for a variable. Why? Search for a in batch file. It is quite often found on not using special find features like whole word only, isn't it.
PRINT is a command as it can be seen on running in a command prompt window print /?. While it is possible to use command names as labels or as names for subroutines, it is not advisable to do so as it could be confusing for readers of the batch file.
The command CHOICE has the option /N to hide the list of choices in the prompt. It is better to use this option than redirecting the output of CHOICE to device NUL.
The very old but still valid Microsoft support article Testing for a Specific Error Level in Batch Files explains that if errorlevel X means that the condition is true if the exit code of previous command or application is greater or equal X. The command CHOICE with 3 choices exits always with 1, 2 or 3 as exit code. So it is best to use:
if errorlevel 3 rem Do something on third choice avoiding fall through to next line.
if errorlevel 2 rem Do something on second choice avoiding fall through to next line.
Do something on first choice.
The advantage of using this method is that it even works with CHOICE within a command block on which if %ERRORLEVEL% == X fails because of delayed expansion would be required and usage of if !ERRORLEVEL! == X.
The integer comparison if %selected% GEQ %a% would not work if the two arguments would be enclosed in double quotes as the double quotes are also interpreted as part of the arguments to compare. For that reason using if "%selected%" GEQ "%a%" would result in running a string comparison instead of an integer comparison. For more information about comparing values with IF look on answer on Exit a for loop in batch.
It is safe here to omit the double quotes also on the other IF conditions with == operator running string comparisons because the environment variables selected and a must be both defined before running this IF condition and therefore both variables are defined at top of the batch file.
The answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? explains why set "variable=value" should be always used to assign a value to an environment variable or delete an environment variable on omitting the value. And this answer also explains why on set /A variable=expression the double quotes can be usually omitted as whitespace characters are interpreted completely different within an arithmetic expression. The exception is usage of set /A with 1 or more commands on same command line on which double quotes around variable=expression would be also needed.
The batch file should be exited when the batch file user enters e or E to take third choice. This could be done with just goto :EOF, or with exit /B which is an alias for goto :EOF, or with just exit which always exits entire command process independent on calling hierarchy which is not recommended. Windows command interpreter would implicitly restore the initial stack before finishing batch file execution. But it is nevertheless good coding practice to pop from stack with code which was pushed on stack before with code. For that reason there is used popd & endlocal & goto :EOF. See answer on Single line with multiple commands using Windows batch file for more information about usage of multiple commands on one command line.
The list of predefined environment variables of used user account is output on running in a command prompt window the command set. One predefined Windows environment variable is APPDATA with path to application data of current user account. This environment variable should be used instead of a fixed path to application data directory of user account.
And the directory separator on Windows is the backslash character \ and not slash character / as on Unix and Mac.
The usage of f as loop variable is not recommended as this is also a loop variable modifier. %%~f can be interpreted by Windows command interpreter as value of loop variable f without surrounding double quotes or as incomplete loop variable reference because of missing loop variable after %%~f which could be also interpreted as full file name of ?. So it is better to use # or $ as loop variable or upper case letters to avoid such a confusion on interpreting the loop variable reference. Loop variables are case-sensitive.
I prefer for IF conditions with an ELSE branch the coding style
if condition (
command
) else (
command
)
But here in this batch file with command being just a short ECHO command the code is better readable on being more compact with using:
if condition (echo short message) else echo other short message
Delayed expansion for an environment variable referenced within a command block started with ( and ending with matching ) is only needed if the environment variable is also modified in same command block. Therefore environment variable a must be referenced in body of FOR with usage of delayed expansion while environment variable selected can be referenced as usual because of not modified within this command block at all.
It is better to use echo/ to output an empty line instead of echo.. For the reason read the DosTips forum topic: ECHO. FAILS to give text or blank line - Instead use ECHO/
For a basic understanding of the used commands, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
choice /?
cls /?
echo /?
endlocal /?
for /?
goto /?
if /?
popd /?
pushd /?
rem /?
set /?
setlocal /?

Batch - error when loading from a file

I have been having problems when loading from a file, I cant seem to find the problem since it is working everywhere else in my code.
Anyways here is the code
set /p viewsave=
if exist save%viewsave%.txt (
set /p level=<save%viewsave%.txt
echo save%viewsave%
echo you are on level %level%
pause
it can find save%viewsave% and I know that it knows that level = 0 because when using echo it says
set /p level= 0<save1.txt
but it does not echo it when I echo
you are on level %level%
it just does not display anything
here is an example of where it somehow did work
set /p saves=<savesnr.txt
echo you have %saves% saves
You need to use enabledelayedexpansion and replace:
echo you are on level %level%
With:
echo you are on level !level!
EnableDelayedExpansion
Delayed Expansion will cause variables within a batch file to be
expanded at execution time rather than at parse time, this option is
turned on with the SETLOCAL EnableDelayedExpansion command.
When delayed expansion is in effect variables can be referenced using
!variable_name! (in addition to the normal %variable_name% )
When a batch file is executed, the command processor (CMD.exe) will
parse complete lines and complete compound commands. Variables are
replaced by their values just once, BEFORE the commands of the line
are executed.
Source enabledelayedexpansion
Further Reading
An A-Z Index of the Windows CMD command line
A categorized list of Windows CMD commands
enabledelayedexpansion - Delayed Expansion will cause variables to be expanded at execution time rather than at parse time.

How to change a string in a FOR loop by adding the loop number at the end of the string?

I have a problem when using the loop. What I want to do is to change the file name by adding a number "N" at the end of the file name in each iteration, like file1, file2, file3, ... So it should not overwrite the previous file name.
set path=D:\MeasurementData\%~n0%
echo %path%
pause
for /L %%N in (1,1,5) do (
set file="%path%%%N"
echo %file%
pause
start C:\TomoK.exe %file% "D:\MParameter.mff" "D:\MParameter.mcf"
Pause
)
Does anyone know how to solve this?
My first suggestion is never replacing a predefined environment variable like PATH except there is a very good reason to do it. Open a command prompt window and run set to get output all predefined variables with their current values. Or look on Wikipedia article about environment variables the chapter Windows Environment Variables and read answers on What is the reason for '...' is not recognized as an internal or external command, operable program or batch file?
My second suggestion on needing help it is advisable to open a command prompt window and run each command used in batch file with /? as parameter like set /?. Then the help for the command is output on one or more pages which can be studied. How to use delayed expansion and when it must be used is explained by help of command SET on an IF and a FOR example. Other helpful pages on writing batch files are Microsoft's command-line reference and SS64.com - A-Z index of the Windows CMD command line.
My third suggestion is reading answer on How to set environment variables with spaces? and the other Stack Overflow question linked there. And the second half on this answer with details about the commands SETLOCAL and ENDLOCAL.
After studying all those help and web pages it should be no problem anymore to understand this code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "DataPath=D:\MeasurementData\%~n0"
for /L %%N in (1,1,5) do (
set "DateFile=%DataPath%%%N"
echo !DateFile!
pause
C:\TomoK.exe "!DateFile!" "D:\MParameter.mff" "D:\MParameter.mcf"
pause
)
endlocal
I don't know why command START was used to run the executable TomoK.exe and therefore removed it.
However, the batch file could be also written without delayed expansion because the environment variable DataFile is not really needed in body of loop at all. And the entire batch code above can be optimized to a single command line in batch file:
#for /L %%N in (1,1,5) do #C:\TomoK.exe "D:\MeasurementData\%~n0%%N" "D:\MParameter.mff" "D:\MParameter.mcf"
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 /?
for /?
pause /?
set /?
setlocal /?
start /?

set fileName and echo to cmd.exe in for loop of batch?

I'd like to put each of the many properties' file names into variable fileName and echo them out to the command prompt window. But only the last properties file name to be cycled thru is printed out as many times as there are properties files. Is there an easy fix to this problem. I know that ...DO echo %%-nxG can do the same thing but I'd like to save the file name in %%~nxG for future use.
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo %fileName%
)
You need to use delayed expansion:
setlocal enabledelayedexpansion
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo !fileName!
)
Environment variables in cmd are expanded when a command is parsed – in this case this includes the whole block in parentheses. So %fileName% gets replaced by an empty string because it didn't have a value before the loop ran. Delayed expansion uses ! instead of % and changes variable evaluation so that they are evaluated just before a command is run.
help set has more details about why and when it is necessary. In general, whenever you modify and use a variable within a loop you have to use delayed expansion, but it comes with a few other benefits too.

Resources