Batch String +=? [duplicate] - windows

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
)

Related

Bypass native variables sorting in a batch FOR loop

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

How to assign call argument to var and echo it in Windows batch script

I want to do this:
set kommune
FOR /F "tokens=* delims=" %%x in (DBLib.txt) DO (
CALL :decryptLine "%%x"
)
GOTO:eof
:decryptLine
for /f "tokens=1,* delims==" %%a in ("%~1") do set argument=%%a & set value=%%b
set "argument=%argument:~0,-2%"
set "value=%value:~1%"
call:updateVar "%argument%" "%value%"
GOTO:EOF
:updateVar
IF "%~1" == "KommuneNavn" (
ECHO "%~2"
ECHO "KommuneNavn"
set kommune=%~2
ECHO kommune = "%kommune%" testhest
)
What it outputs:
"ABC Test Kommune"
"KommuneNavn"
"kommune = "" testhest"
How do i copy the value of the secont argument to the Variable "kommune"? And Echo it?
Edit 1: updated to exact code. "inside IF"
#ECHO OFF
SETLOCAL
set kommune
FOR /F "tokens=* delims=" %%x in (q27922463.txt) DO (
CALL :decryptLine "%%x"
)
GOTO:eof
:decryptLine
for /f "tokens=1,* delims==" %%a in ("%~1") do set "argument=%%a" & set "value=%%b"
set "argument=%argument:~0,-2%"
set "value=%value:~1%"
call:updateVar "%argument%" "%value%"
GOTO:EOF
:updateVar
IF "%~1" == "KommuneNavn" (
ECHO "%~2"
ECHO "KommuneNavn"
set kommune=%~2
CALL ECHO kommune = "%%kommune%%" testhest
)
GOTO :eof
Critical point: You haven't shown us the content of your file, so we have to construct it: and I've changed the filename to suit my system (q27922463.txt)
contents of q27922463.txt
KommuneNavnxy=yourvalue
output generated:
"ourvalue"
"KommuneNavn"
kommune = "ourvalue" testhest
Note the positioning of the quotes in the set assignments. Batch is sensitive to spaces in a SET statement. SET FLAG = N sets a variable named "FLAGSpace" to a value of "SpaceN"
So, %%a becomes KommuneNavnxy, is assigned to argument, and the last 2 characters are removed, making KommuneNavn
Similarly, %%b gets yourvalue, you remove the first and make ourvalue
Since the string kommune is set within the code block of the if statement, you need to use call echo %%var%% to display it (one of several ways).

How to output exclamation mark with EnabledDelayedExapnsion?

I am working on editing an XML file, and approximately the first 10 lines are comments. And for xml comments are in the form
<!-- COMMENT HERE -->
But when using my code it does not output that ! mark, which screws up the comments in the xml. I understand that the ENABLEDELAYEDEXPANSION is doing this because it thinks the exclamation mark is expanding a variable. How could I get this to work?
Here is my code below
setlocal ENABLEDELAYEDEXPANSION
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line = !line!+1
if !line!==39 (echo REPLACED TEXT39>>%tempfile%
)else if(!line!==45 (echo REPLACED TEXT45>>%tempfile%
)else (echo %%a>>%tempfile%
))
EDIT1 Basically what it is supposed to do is output each line as it is, unless it is line 39 or 45. It works, except the ! marks in the comments don't get outputted and they are not comments anymore.
EDIT2
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
setlocal ENABLEDELAYEDEXPANSION
set /a !line! +=1
echo !line!
if !line!==39 (
echo REPLACED TEXT39>>%tempfile%
endlocal
)else if !line!==45 (
echo REPLACED TEXT45>>%tempfile%
endlocal
)else (
endlocal
setlocal DISABLEDELAYEDEXPANSION
echo %%a>>%tempfile%
endlocal
))
Here is the latest code I have been using. It works the best, but now the problem is that the variable "line" is not getting updated. I have a feeling that it is because of the "endlocal". The only problem is that I need the "endlocal" there otherwise I get an error
Maximum setlocal recursion level reached.
The problem is, I need to alternate between enableddelayedexpansion and disabledelayedexpansion so that my exclamation marks show up properly. But to do that I need to keep up with the "endlocal" calls which i think is messing up my line variable. Any thoughts?
You can't output the exclamation mark this way.
The exclamation mark is part of the content of %%a but while delayed expansion is enabled you can't access it, as it will be parsed after the %%a is epanded.
So you need to disable delayed expansion at all or temporary.
A sample for temporary disabling it
setlocal ENABLEDELAYEDEXPANSION
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line = !line!+1
if !line!==39 (
echo REPLACED TEXT39>>%tempfile%
) else if !line!==45 (
echo REPLACED TEXT45>>%tempfile%
) else (
setlocal DisableDelayedExpansion
echo %%a>>%tempfile%
endlocal
)
)
Or you don't use it at all, then you only need to get the if line=42 part working.
This uses the fact that modulo by 0, will produce an error (which is suppressed by 2>nul) and the variable stays unchanged, in this case they stay undefined.
setlocal DisableDelayedExpansion
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line+=1
set "notLine39="
set /a "notLine39=1%%(line-39)" 2>nul
set "notLine45="
set /a "notLine45=1%%(line-45)" 2>nul
if not defined line39 (
echo REPLACED TEXT39>>%tempfile%
) else if not defined line45 (
echo REPLACED TEXT45>>%tempfile%
) else (
setlocal DisableDelayedExpansion
echo %%a>>%tempfile%
endlocal
)
)
Edit: Added explanation to your changed question
This uses the toggling delayed expansion technic, described in SO: Batch files: How to read a file?
The trick is to be in disabledDelayedExpansion when transfering %%a to text, then switching to enabledDE and be able to use the extended syntax.
But don't forget the endlocal before the next loop starts.
setlocal DisableDelayedExpansion
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line+=1
set "text=%%a"
setlocal EnableDelayedExpansion
if !line!==39 (
echo REPLACED TEXT39>>%tempfile%
) else if !line!==45 (
echo REPLACED TEXT45>>%tempfile%
) else (
echo %%a>>%tempfile%
)
endlocal
)

Reading a text file line by line and storing it in an array using batch script

I want to read a text file and store each line in an array. When I used the code below, "echo %i%" is printing 0 every time and only array[0] value is getting assigned. But in "set n=%i%",n value is assigned as the last incremented I value.Also "#echo !array[%%i]!" is printing like !array[0]! instead of printing the value. Is there any syntax error in the code?
set /A i=0
for /F %%a in (C:\Users\Admin\Documents\url.txt) do (
set /A i+=1
echo %i%
set array[%i%]=%%a
)
set n=%i%
for /L %%i in (0,1,%n%) do #echo !array[%%i]!
Here's a method that is useful at times and very similar to your code:
#echo off
set "file=C:\Users\Admin\Documents\url.txt"
set /A i=0
for /F "usebackq delims=" %%a in ("%file%") do (
set /A i+=1
call echo %%i%%
call set array[%%i%%]=%%a
call set n=%%i%%
)
for /L %%i in (1,1,%n%) do call echo %%array[%%i]%%
#echo off &setlocal enabledelayedexpansion
for /F "delims=" %%a in (C:\Users\Admin\Documents\url.txt) do (
set /A count+=1
set "array[!count!]=%%a"
)
for /L %%i in (1,1,%count%) do echo !array[%%i]!
Inside a code block you need delayed expansion and !variables!.
Read set /? description about environment run-time linking. When you are using %i% inside for - it is pre-expanded before for execution. You need to use !i! instead.
#ECHO OFF
SETLOCAL
FOR /f "tokens=1*delims=:" %%i IN ('findstr /n /r "$" url.txt') DO SET max=%%i&SET array[%%i]=%%j
FOR /l %%i IN (1,1,%max%) DO CALL ECHO(%%array[%%i]%%
GOTO :EOF
provided no line begins ":"

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%"

Resources