Bypass native variables sorting in a batch FOR loop - sorting

I need to read an arbitrary number of variable "names" from one file, and later assign them "values" supplied by another file, with both sources not available at once. I tried using the code below, but the problem is, SET command natively sorts variables alphabetically, thus preventing correct value assignments. Is there an alternative approach to set variables in this case, or a way to block native Cmd vars sorting by SET? I don't want setting numbered variable arrays if possible, as they complicate the code by adding extra layer of variables:
#echo off
setlocal EnableDelayedExpansion
for /f "tokens=1" %%i in (%args1_file%) do (
set "%%i=0" & set "%%i=_%%i")
for /f "tokens=1 delims==" %%i in ('set _') do (
for /f "tokens=1" %%j in (%args2_file%) do (
set "%%i=%%j"
if not !%%i! equ 0 (echo %%i = %%j
) else (set /p "%%j=Enter %%i > " 2>nul)
call :validate
)
:: more code using vars %%i
exit /b
:validate

Assuming you mean the sorted order returned by set, there's no way around it.
It's not documented anywhere I can see, but the environment variable block maintained by GetEnvironmentStrings() and friends is maintained in sorted order, at least in every NT OS I've seen, and probably before then. When you add a new string to it's list, it's added in sorted position, so the order of addition is lost by the system.
I think you can set the variables based off names in one file with values in another file by:
#echo off
setlocal enabledelayedexpansion
set _i=0
for /f "tokens=1" %%i in (names.txt) do (
set _val_!_i!=%%i
set /a _i=!_i!+1
)
set _i=0
for /f "tokens=1" %%i in (vals.txt) do (
set _temp=_val_!_i!
call set __%%!_temp!%%=%%i
set /a _i=!_i!+1
)
echo one == !__one!
echo two == !__two!
echo three == !__three!

I found the approach that doesn't use a numbered array to read var names and values from separate files, and won't cause vars sorting to ensure correct value assignments:
#echo off
setlocal EnableDelayedExpansion
for /f "tokens=1" %%i in (%args1_file%) do (
set "%%i=0" & set "vars=!vars! _%%i")
for %%i in (!vars!) do (
for /f "tokens=1" %%j in (%args2_file%) do (
if not %%j.==. set %%i=%%j)
if not !%%i! equ 0 (echo %%i = %%j
) else (set /p "%%j=Enter %%i > " 2>nul)
call :validate
)
:: more code using vars %%i
exit /b

Related

Need advise on the Batch Script with the output:

Need your expert advice on the below:
Here is the code:
Note: serial is set to value of 100 which is default and it is pulled from the another script where all the server details are stored.
setlocal enabledelayedexpansion
SET serverlist=
SET env=TBD
if /I "%2" EQU "D" (set env=dev&& set env_dir=dev)
if /I "%2" EQU "U" (set env=uat&& set env_dir=uat)
if /I "%2" EQU "P" (set env=prod&& set env_dir=prod)
echo Here are the server details for %env% %1:
echo These are for DEV:
FOR /l %%a IN (1,1,50) DO (if defined %env%_%serial%_DEVser_%%a_ (for /f "tokens=2,3,4 delims==, " %%a in ('set %env%_%serial%_DEVser_%%a_') do (if %%c NEQ dup (echo %%a ^(%%b^,%%c^) & set serverdetails=!serverdetails! %%a %%b %%c) )))
echo These are for UAT:
FOR /l %%a IN (1,1,50) DO (if defined %env%_%serial%_UATser_%%a_ (for /f "tokens=2,3,4 delims==, " %%a in ('set %env%_%serial%_UATser_%%a_') do (if %%c NEQ dup (echo %%a ^(%%b^,%%c^) & set serverdetails=!serverdetails! %%a %%b %%c) )))
echo These are for PROD:
FOR /l %%a IN (1,1,50) DO (if defined %env%_%serial%_PRODser_%%a_ (for /f "tokens=2,3,4 delims==, " %%a in ('set %env%_%serial%_PRODser_%%a_') do (if %%c NEQ dup (echo %%a ^(%%b^,%%c^) & set serverdetails=!serverdetails! %%a %%b %%c) )))
echo details of all the server:
echo %serverdetails%
Current output:
These are for DEV:
D00123 (testing1)
D00456 (testing2)
D00789 (testing3)
These are for UAT:
UAT001 (UAT-env1)
UAT002 (UAT-env2)
UAT003 (UAT-env3)
These are for PROD:
PRD001 (PRD-env1)
PRD002 (PRD-env2)
PRD003 (PRD-env3)
details of all the server:
D00123 testing1 D00456 testing2 D00789 testing3 UAT001 UAT-env1 UAT002 UAT-env2 UAT003 UAT-env3 PRD001 PRD-env1 PRD002 PRD-env2 PRD003 PRD-env3
Question:
For the details of all the server output: I would like to get the output below
D00123
D00456
D00789
UAT001
UAT002
UAT003
PRD001
PRD002
PRD003
If the Dev server details are requested, then UAT / PROD details should not be visible
similarly, if PROD is requested, then DEV and UAT should not be visible.
can you please help me with this?
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
:: Dummy data established outside of routine
SET "serial=100"
SET "ser_=xyz"
SET "dev_%serial%_dev%ser_%1_=D00123 something1,testing1"
SET "dev_%serial%_dev%ser_%16_=D00456 something2,testing2"
SET "dev_%serial%_dev%ser_%22_=D00789 something3,testing3"
SET "dev_%serial%_dev%ser_%35_=D00987 something4,dup"
SET "uat_%serial%_uat%ser_%1_=UAT123 something11,testing4"
SET "uat_%serial%_uat%ser_%16_=UAT456 something12,testing5"
SET "uat_%serial%_uat%ser_%22_=UAT789 something13,dup"
SET "uat_%serial%_uat%ser_%35_=UAT987 something14,testing7"
SET "prod_%serial%_prod%ser_%1_=PRD123 something21,dup"
SET "prod_%serial%_prod%ser_%16_=PRD456 something22,testing9"
SET "prod_%serial%_prod%ser_%22_=PRD789 something23,testing10"
SET "prod_%serial%_prod%ser_%35_=PRD987 something24,testing11"
SET |FIND /i "something"
IF /i "%1"=="d" SET "env=dev"
IF /i "%1"=="u" SET "env=uat"
IF /i "%1"=="p" SET "env=prod"
for %%e in (dev uat prod) do (
SET "report="
if /i "%%e"=="%env%" SET "report=Y"
IF DEFINED report echo These are for %env%:
FOR /l %%o IN (1,1,50) DO if defined %%e_%serial%_%%e%ser_%%%o_ (
for /f "tokens=2,3,4 delims==, " %%u in (
'set %%e_%serial%_%%e%ser_%%%o_'
) do if %%w NEQ dup set "%%eserverdetails=!%%eserverdetails! %%u"&IF DEFINED report echo %%u
)
SET "serverdetails=!serverdetails! !%%eserverdetails!"
)
echo These are for ALL:
FOR %%e IN (%serverdetails%) DO ECHO %%e
GOTO :EOF
Still not really clear on the values in the variables. Test setup shown. Routine accepts d, u or p as first param.
Don't know what ser_ is set to.
Your code contains for..%%a within a for..%%a - decidedly not good practice.
Speaking of which, a couple of proved "good practice" principles:
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign " or a terminal backslash or Space. Build pathnames from the elements - counterintuitively, it is likely to make the process easier. If the syntax set var="value" is used, then the quotes become part of the value assigned.
Prefer to avoid ADFNPSTXZ (in either case) as metavariables (loop-control variables) ADFNPSTXZ are also metavariable-modifiers which can lead to difficult-to-find bugs (See for/f from the prompt for documentation)
So...
Having established env, process the variables for each of the three possibilities (in %%e). Filter the values for %%e _ %serial% _ %%e %ser_% %%o _ where %%o is 1..50 and append to %%eserverdetails if the third token of the variable is not dup. If %%e matches %env% then set the report flag which controls whether the data is echoed.
Finally, list the serverdetails data.
Of course, it would also be possible to list !%env%serverdetails! for the individual lists, which would make the report flag and reporting the data within the %%u loop redundant.
Append each %%eserverdetails to serverdetails after each %%e is processed

Batch String +=? [duplicate]

I made this code
dir /B /S %RepToRead% > %FileName%
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo %z%
echo %%a
)
echo %%a is working fine but echo %z% returns "echo disabled".
I need to set a %z% because I want to split the variable like %z:~7%
Any ideas?
There are two methods to setting and using variables within for loops and parentheses scope.
setlocal enabledelayedexpansion see setlocal /? for help. This only works on XP/2000 or newer versions of Windows.
then use !variable! instead of %variable% inside the loop...
Create a batch function using batch goto labels :Label.
Example:
for /F "tokens=*" %%a in ('type %FileName%') do call :Foo %%a
goto End
:Foo
set z=%1
echo %z%
echo %1
goto :eof
:End
Batch functions are very useful mechanism.
You probably want SETLOCAL ENABLEDELAYEDEXPANSION. See https://devblogs.microsoft.com/oldnewthing/20060823-00/?p=29993 for details.
Basically: Normal %variables% are expanded right aftercmd.exe reads the command. In your case the "command" is the whole
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo %z%
echo %%a
)
loop. At that point z has no value yet, so echo %z% turns into echo. Then the loop is executed and z is set, but its value isn't used anymore.
SETLOCAL ENABLEDELAYEDEXPANSION enables an additional syntax, !variable!. This also expands variables but it only does so right before each (sub-)command is executed.
SETLOCAL ENABLEDELAYEDEXPANSION
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo !z!
echo %%a
)
This gives you the current value of z each time the echo runs.
I struggeld for many hours on this.
This is my loop to register command line vars.
Example : Register.bat /param1:value1 /param2:value2
What is does, is loop all the commandline params,
and that set the variable with the proper name to the value.
After that, you can just use
set value=!param1!
set value2=!param2!
regardless the sequence the params are given. (so called named parameters).
Note the !<>!, instead of the %<>%.
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%P IN (%*) DO (
call :processParam %%P
)
goto:End
:processParam [%1 - param]
#echo "processparam : %1"
FOR /F "tokens=1,2 delims=:" %%G IN ("%1") DO (
#echo a,b %%G %%H
set nameWithSlash=%%G
set name=!nameWithSlash:~1!
#echo n=!name!
set value=%%H
set !name!=!value!
)
goto :eof
:End
Simple example of batch code using %var%, !var!, and %%.
In this example code, focus here is that we want to capture a start time using the built in variable TIME (using time because it always changes automatically):
Code:
#echo off
setlocal enabledelayedexpansion
SET "SERVICES_LIST=MMS ARSM MMS2"
SET START=%TIME%
SET "LAST_SERVICE="
for %%A in (%SERVICES_LIST%) do (
SET START=!TIME!
CALL :SOME_FUNCTION %%A
SET "LAST_SERVICE=%%A"
ping -n 5 127.0.0.1 > NUL
SET OTHER=!START!
if !OTHER! EQU !START! (
echo !OTHER! is equal to !START! as expected
) ELSE (
echo NOTHING
)
)
ECHO Last service run was %LAST_SERVICE%
:: Function declared like this
:SOME_FUNCTION
echo Running: %1
EXIT /B 0
Comments on code:
Use enabledelayedexpansion
The first three SET lines are typical
uses of the SET command, use this most of the time.
The next line is a for loop, must use %%A for iteration, then %%B if a loop inside it
etc.. You can not use long variable names.
To access a changed variable such as the time variable, you must use !! or set with !! (have enableddelayexpansion enabled).
When looping in for loop each iteration is accessed as the %%A variable.
The code in the for loop is point out the various ways to set a variable. Looking at 'SET OTHER=!START!', if you were to change to SET OTHER=%START% you will see why !! is needed. (hint: you will see NOTHING) output.
In short !! is more likely needed inside of loops, %var% in general, %% always a for loop.
Further reading
Use the following links to determine why in more detail:
Difference between %variable% and !variable! in batch file
Variable usage in batch file
To expand on the answer I came here to get a better understanding so I wrote this that can explain it and helped me too.
It has the setlocal DisableDelayedExpansion in there so you can locally set this as you wish between the setlocal EnableDelayedExpansion and it.
#echo off
title %~nx0
for /f "tokens=*" %%A in ("Some Thing") do (
setlocal EnableDelayedExpansion
set z=%%A
echo !z! Echoing the assigned variable in setlocal scope.
echo %%A Echoing the variable in local scope.
setlocal DisableDelayedExpansion
echo !z! &rem !z! Neither of these now work, which makes sense.
echo %z% &rem ECHO is off. Neither of these now work, which makes sense.
echo %%A Echoing the variable in its local scope, will always work.
)
set list = a1-2019 a3-2018 a4-2017
setlocal enabledelayedexpansion
set backup=
set bb1=
for /d %%d in (%list%) do (
set td=%%d
set x=!td!
set y=!td!
set y=!y:~-4!
if !y! gtr !bb1! (
set bb1=!y!
set backup=!x!
)
)
rem: backup will be 2019
echo %backup%
Try this:
setlocal EnableDelayedExpansion
...
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo !z!
echo %%a
)
You can use a macro if you access a variable outside the scope
#echo off
::Define macro
set "sset=set"
for /l %%a in (1,1,4) do (
::set in loop
%sset% /a "x[%%a]=%%a*%%a"
if %%a equ 4 (
:: set in condition
%sset% "x[%%a]=x Condition"
%sset% "y=y Condition"
)
)
echo x1=%x[1]% x2=%x[2]% x3=%x[3]% x4=%x[4]% y=%y%
:: Bonus. enableDelayedExpansion used to access massive from the loop
setlocal enableDelayedExpansion
echo Echo from the loop
for /l %%a in (1,1,4) do (
::echo in one line - echo|set /p =
echo|set /p "=x%%a=!x[%%a]! "
if %%a equ 4 echo y=%y%
)
pause
I know this isn't what's asked but I benefited from this method, when trying to set a variable within a "loop". Uses an array. Alternative implementation option.
SETLOCAL ENABLEDELAYEDEXPANSION
...
set Services[0]=SERVICE1
set Services[1]=SERVICE2
set Services[2]=SERVICE3
set "i=0"
:ServicesLoop
if defined Services[%i%] (
set SERVICE=!Services[%i%]!
echo CurrentService: !SERVICE!
set /a "i+=1"
GOTO :ServicesLoop
)
The following should work:
setlocal EnableDelayedExpansion
for /F "tokens=*" %%a in ('type %FileName%') do (
set "z=%%a"
echo %z%
echo %%a
)

Print array element value in a Batch file

There is a problem with printing array element values in the below code:
#echo off
setlocal enabledelayedexpansion enableextensions
for /F "tokens=2,3 delims= " %%a in ('findstr "associationMaxRtx maxIncomingStream maxOutgoingStream initialAdRecWin maxUserdataSize mBuffer nThreshold PathMaxRtx maxInitialRtrAtt minimumRto maximumRto initialRto rtoAlphaIndex tSack" C:\Users\ephajin\logs.txt') do (
set /A count+=1
set vartmp1=%%a
set vartmp2=%%b
set "array[!count!]="%%a %%b""
)
(for /L %%i in (1,1,%count%) do echo !array[%%i]!
) > result.txt
in the result file I get the output
ECHO is off.
ECHO is off.
ECHO is off.
ECHO is off.
It does not print the array values.
The problem is probably due to setlocal enabledelayedexpansion but how do you correct it?
FOR /L %%a IN (1,1,4) DO ECHO !array[%%a]!
FOR /f "tokens=1*delims==" %%a IN ('set array[') DO ECHO %%b
Either of these two lines should show you what you appear to require.
Since the first is identical in effect to your code, I suspect that the array[*] array isn't being established correctly. you can check this by executing
set array[
to show precisely what has been set. Actually,
set
should show you all defined user-variables.
set|more
would show the same, but allow you to page through them.
SET "result="
FOR /f "tokens=1*delims==" %%a IN ('set array[') DO SET "result=!result! %%b"
ECHO result: "%result%" or "%result:~1%"
echo===============
SET "result="
FOR /L %%a IN (1,1,4) DO SET "result=!result! !array[%%a]!"
ECHO result: "%result%" or "%result:~1%"
Two methods of setting result - the list of the values in the array. Naturally, the space in the set instruction could be almost any character you desire - comma, for instance. The result is shown both with a leading space and with that space removed.

In a batch file, how can I get the value of an environment variable whose name is the value of another environment variable?

If I know that one environment variable contains the name of another, how can I get the value of the second environment variable?
Assume I have a file java.properties alongside my batch file with the following contents.
JAVA_HOME_OVERRIDE_ENV_VAR=JAVA_HOME_1_7_0_17
What I want to do is check if JAVA_HOME_1_7_0_17 is set and, if so, do the equivalent of set JAVA_HOME=%JAVA_HOME_1_7_0_17%. I can figure out what environment variable I'm looking for, but I don't know how to get its value. This is what I have so far...
#echo off
setlocal enabledelayedexpansion
if exist %~dp0\java.properties (
echo "Found java properties."
for /F "tokens=1* usebackq delims==" %%A IN (%~dp0\java.properties) DO (
if "%%A"=="JAVA_HOME_OVERRIDE_ENV_VAR" set JAVA_HOME_OVERRIDE_ENV_VAR=%%B
)
if not [!JAVA_HOME_OVERRIDE_ENV_VAR!] == [] (
echo "Override var is !JAVA_HOME_OVERRIDE_ENV_VAR!"
REM This is where I'm stuck!!!
REM Assume JAVA_HOME_OVERRIDE_ENV_VAR is JAVA_HOME_1_7_0_17
)
)
endlocal & set JAVA_HOME=%JAVA_HOME%
What I want to do is check if the environment variable JAVA_HOME_1_7_0_17 exists and, if it does, use its value to set JAVA_HOME.
Updated
I think the nested if statements are making things more difficult then needed. I got rid of them and the following seems to work.
#echo off
setlocal enabledelayedexpansion
if not exist "%~dp0\java.properties" (
goto:EOF
)
for /F "tokens=1* usebackq delims==" %%A IN ("%~dp0\java.properties") DO (
if "%%A"=="JAVA_HOME_OVERRIDE_ENV_VAR" set JAVA_HOME_OVERRIDE_ENV_VAR=%%B
)
if [!JAVA_HOME_OVERRIDE_ENV_VAR!] == [] (
goto:EOF
)
set JAVA_HOME=!%JAVA_HOME_OVERRIDE_ENV_VAR%!
endlocal & set JAVA_HOME="%JAVA_HOME%"
Try set JAVA_HOME=%!JAVA_HOME_OVERRIDE_ENV_VAR!%.
EDIT: This should not work if !JAVA_HOME_OVERRIDE_ENV_VAR! was set on the same line. Try
call set JAVA_HOME=!%JAVA_HOME_OVERRIDE_ENV_VAR%!
a downside being that since it will search the disk for a file/executable with the name set, the command should take slightly longer to finish, though it should only be noticeable in large loops.
EDIT 2: Try this too...
(add set override=0 in front, add set override=1 under if not, and replace the endlocal line)
#echo off
setlocal enabledelayedexpansion
set override=0
if exist %~dp0\java.properties (
echo "Found java properties."
for /F "tokens=1* usebackq delims==" %%A IN (%~dp0\java.properties) DO (
if "%%A"=="JAVA_HOME_OVERRIDE_ENV_VAR" set JAVA_HOME_OVERRIDE_ENV_VAR=%%B
)
if not [!JAVA_HOME_OVERRIDE_ENV_VAR!] == [] (
echo "Override var is !JAVA_HOME_OVERRIDE_ENV_VAR!"
set override=1
REM Assume JAVA_HOME_OVERRIDE_ENV_VAR is JAVA_HOME_1_7_0_17
)
)
endlocal & if override=1 set JAVA_HOME=!%JAVA_HOME_OVERRIDE_ENV_VAR%!
I would use FINDSTR to filter out the relevant line, IF DEFINED to validate the existence of the variable, and delayed expansion within the loop to get the appropriate value.
Your code could be as simple as:
#echo off
setlocal enableDelayedExpansion
for /f "tokens=1* delims==" %%A in (
'2^>nul findstr /bil "JAVA_HOME_OVERRIDE_ENV_VAR=" "%~dp0\java.properties"'
) do if defined %%B set "JAVA_HOME=!%%B!"
endlocal & set "JAVA_HOME=%JAVA_HOME%"

Parsing string in batch file

I have the following string:
MyProject/Architecture=32bit,BuildType=Debug,OS=winpc
I would like to be able to grab the values 32bit, Debug, and winpc and store them in variables named Architecture, BuildType, and OS to reference later in the batch script. I'm normally a Unix guy so this is new territory for me. Any help would be greatly appreciated!
This should do it:
FOR /F "tokens=1-6 delims==," %%I IN ("MyProject/Architecture=32bit,BuildType=Debug,OS=winpc") DO (
ECHO I %%I, J %%J, K %%K, L %%L, M %%M, N %%N
)
REM output is: I MyProject/Architecture, J 32bit, K BuildType, L Debug, M OS, N winpc
The batch FOR loop is a pretty interesting piece of machinery. Type FOR /? in a console for a description of some of the crazy stuff it can do.
Here is an interesting solution that doesn't care how many or what order the name=value pairs are specified. The trick is to replace each comma with a linefeed character so that FOR /F will iterate each name=value pair. This should work as long as there is only one / in the string.
#echo off
setlocal enableDelayedExpansion
set "str=MyProject/Architecture=32bit,BuildType=Debug,OS=winpc"
::Eliminate the leading project info
set "str=%str:*/=%"
::Define a variable containing a LineFeed character
set LF=^
::The above 2 empty lines are critical - do not remove
::Parse and set the values
for %%A in ("!LF!") do (
for /f "eol== tokens=1* delims==" %%B in ("!str:,=%%~A!") do set "%%B=%%C"
)
::Display the values
echo Architecture=%Architecture%
echo BuildType=%BuildType%
echo OS=%OS%
With a bit more code it can selectively parse out only name=value pairs that we are interested in. It also initializes the variables to undefined in case the variable is missing from the string.
#echo off
setlocal enableDelayedExpansion
set "str=MyProject/Architecture=32bit,BuildType=Debug,OS=winpc"
::Eliminate the leading project info
set "str=%str:*/=%"
::Define a variable containing a LineFeed character
set LF=^
::The above 2 empty lines are critical - do not remove
::Define the variables we are interested in
set "vars= Architecture BuildType OS "
::Clear any existing values
for %%A in (%vars%) do set "%%A="
::Parse and conditionally set the values
for %%A in ("!LF!") do (
for /f "eol== tokens=1* delims==" %%B in ("!str:,=%%~A!") do (
if !vars: %%B ! neq !vars! set "%%B=%%C"
)
)
::Display the values
for %%A in (%vars%) do echo %%A=!%%A!
Try the following:
#ECHO OFF
SET Var=MyProject/Architecture=32bit,BuildType=Debug,OS=winpc
FOR /F "tokens=1,2,3 delims=," %%A IN ("%Var%") DO (
FOR /F "tokens=1,2 delims==" %%D IN ("%%A") DO (
SET Architecture=%%E
)
FOR /F "tokens=1,2 delims==" %%D IN ("%%B") DO (
SET BuildType=%%E
)
FOR /F "tokens=1,2 delims==" %%D IN ("%%C") DO (
SET OS=%%E
)
)
ECHO %Architecture%
ECHO %BuildType%
ECHO %OS%
PAUSE

Resources