Assign variables in a FOR loop [duplicate] - windows

This question already has answers here:
Variables are not behaving as expected
(1 answer)
Example of delayed expansion in batch file
(5 answers)
Closed last year.
I want to process all files in a directory, eg. replace the extension and rename the file. The problem is, I cannot move the filename into a variable (seems to be a problem with the scope of variables).
Here is what I have coded:
#echo off
cls
setlocal
set myname=hugo
echo myname=%myname%
::process all files in directory
for %%f in (C:\windows\*.ico) do (
echo F=%%f <--- this works fine
set myname=%%f <--- this does NOT work!
echo.myname=%myname%
)
echo myname=%myname% <--- displays last set
endlocal
And this is the output:
myname=hugo
F=C:\windows\AnyWeb Print.ico
myname=hugo
F=C:\windows\Dr. Printer Icon.ico
myname=hugo
F=C:\windows\SmartCMS2.ico
myname=hugo
myname=C:\windows\SmartCMS2.ico
I cannot modify the variable "myname" within he loop, but once the loop has ended, it contains the value, that I have tried to assign most recently.
Obviously I don't understand the way a batch file handles variables in a FOR loop. I seem to lack some info about it.

The expansion of variables inside FOR loops requires you to enable delayed expansion to force variables to be expanded at runtime instead of being expanded when parsed. Read HELP SET for more information.
And try changing your code to
setlocal enabledelayedexpansion
for %%f in (C:\windows\*.ico) do (
set myname=%%f
echo !myname!;
)
Note that the variable is referenced with an slightly different syntax !myname! instead of %myname%. Delayed environment variable expansion allows you to use a different character (the exclamation mark) to expand environment variables at execution time.

Related

How to delete quotation marks from a text file using batch file?

I am trying to edit out the quotation marks that have been inputed into a text file using .bat.
echo %name%>./User_Records/%username%.txt
in the text file it is saving as
"Firstname Lastname"
I am trying to add to the batch file so that it will edit the *.txt file and delete the quotation marks if they are saved in that text file.
Can anyone help me?
I have been trying to do this for weeks. I want the output to look like
Firstname Lastname
Try replacing all instances of " in the %name% variable by using Environment variable substitution (see set /? for more)
#echo off
set "name=%name:"=%"
echo %name%>./User_Records/%username%.txt
If you are trying to replace the quotation marks after the text file has been saved, then refer to this previous question
echo %name:"=%>.\User_Records\%username%.txt
should strip the quotes before they are recorded, if that's what your question is.
Note that path-separators in windows are \ not /.
But - if your question is about files that already exist then probably the easiest way is to use your editor. Depends a little on quite how many files you have to process - which you haven't specified.
The bat file you are using should really be altered, especially as the line you've provided from it has some issues, (mostly already mentioned).
SetLocal EnableDelayedExpansion
(Echo=!name:"=!)>"User_Records\%username%.txt"
EndLocal
If you have no control over that bat file then something like this should do what you want:
#For /F "UseBackQ Delims=" %%A In ("User_Records\%username%.txt") Do #Echo(%%~A
Alternatively:
#Set/P "FullName="<"User_Records\%username%.txt"
#Echo(%FullName:"=%
If the name read from text file is assigned to environment variable name using set name="..." syntax, then this is the cause of the double quotes in output string.
It makes a big difference if string assigned to environment variable is enclosed in double quotes or the entire parameter string of command SET. Read answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? to get knowledge about the big difference between using set "variable=string" versus set variable="string".
But it is of course possible to remove all double quotes from string assigned currently to environment variable name by using a string substitution:
set "name=%name:"=%"
All occurrences of " in string of name are replaced by an empty string and resulting string is assigned again to environment variable name.
But be aware that this command line
echo %name%>"./User_Records/%username%.txt"
could result in an unwanted behavior, for example if the name assigned to environment variable name is C&A. The ampersand found by Windows command interpreter after expanding environment variable name and before execution of command ECHO is interpreted now as AND operator and not anymore as literal character to output by ECHO.
One solution is using delayed expansion, for example:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Environment variable name is defined with delayed expansion disabled
rem making it possible to assign an exclamation mark as literal character
rem without the need to escape it as it would be necessary when delayed
rem expansion would be enabled already here at this time.
set "name=C&A!"
rem Enable delayed expansion which results in pushing on stack current
rem state of command extensions and of delayed expansion, the current
rem directory path and the pointer to current environment variables list
rem before creating a copy of all environment variables being used further.
setlocal EnableDelayedExpansion
rem Output the value of environment variable name using delayed expansion
rem with redirection into a text file being named like the currently used
rem user account name which of course can contain a space character or
rem other characters requiring enclosed file name with relative path in
rem double quotes.
echo !name!>".\User_Records\%username%.txt"
rem Delete all environment variables, restore pointer to previous set of
rem environment variables, restore current working directory from stack
rem restore states of command extension and delayed expansion from stack.
endlocal
rem Explicitly call ENDLOCAL once again for initial SETLOCAL command.
rem That would not be necessary because Windows command interpreter
rem runs implicitly ENDLOCAL for each local environment still being
rem pushed on stack.
endlocal
An alternate solution is using command FOR for an implicit delayed expansion:
#echo off
set "name=C&A!"
set "name=%name:"=%"
for /F "delims=" %%I in ("%name%") do echo %%I>".\User_Records\%username%.txt"
set "name=
See also the answers on Batch: Auto escape special 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 /?
for /?
set /?
setlocal /?
Read also the Microsoft article about Using Command Redirection Operators.

Windows Batch setting PATH through loop

I'm trying to create a drive with a suite of programs that can be implemented through the command prompt.
My issue is setting the PATH environment variable to include paths to the program suite. Since there are a couple directories for these suites (and I'm constantly adding more), I am trying to get it so I can have a .ini file be parsed through a simple Batch script and returned. From this I want to take the path parsed from the .ini file and append it to my current PATH variable.
This is the current script I'm trying to get fixed.
FOR /F "delims=" %%i IN ('call %cd:~0,3%\initTermPort\parse.ini.bat %cd:~0,3%\initTermPort\config.ini Alternate _path') DO (
SET PATH="%PATH%;%cd:~0,3%%%i"
)
The call to parse.ini.bat is getting the next path in config.ini on every run.
For example:
If I substitute SET PATH="%PATH%;%cd:~0,3%%%i" with ECHO %%i I get the following output:
E:\> (ECHO ~\bin)
~\bin
E:\>(ECHO ~\library\bin)
~\library\bin
But for some reason SET PATH="%PATH%;%cd:~0,3%%%i" is not setting the PATH at all.
Any help is appreciated.
The problem is that when the for code block (the code enclosed in parenthesis) was parsed, all the read operations to variables were replaced with the value in the variable before starting to execute, and in each iteration what is used is this initial value, and not the value inside the variable during the execution.
If you change a variable inside a block of code and need to access the changed value inside the same block of code, you need to enable delayed expansion and change the syntax to access the variables to use !varName! instead of %varName%. This indicates to the parser that this read operation must be delayed.
setlocal enableextensions enabledelayedexpansion
....
FOR /F "delims=" %%i IN (
'call %cd:~0,3%\initTermPort\parse.ini.bat %cd:~0,3%\initTermPort\config.ini Alternate _path'
) DO (
SET "PATH=!PATH!;%cd:~0,3%%%i"
)

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

How to persistently add directories to PATH when using SETLOCAL? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Make an environment variable survive ENDLOCAL
How to keep the value of a variable outside a Windows batch script which uses “delayed expansion local” mode?
I have a batch file that goes something like this:
REM I need to use SETLOCAL so as not to pollute the environment
REM with any variables used to implement the logic in this script
SETLOCAL
SET I_DONT_WANT_THIS_VARIABLE_TO_LEAK_OUTSIDE=c:\
REM BUT, there is this one change to the environment I want to make sticky:
PATH %PATH%;%I_DONT_WANT_THIS_VARIABLE_TO_LEAK_OUTSIDE%
The script uses SETLOCAL to isolate the environment from whatever the batch file wants to do, but this causes a problem: at the end I do want to modify the caller's environment in some very specific manner, but SETLOCAL prevents this (the change is undone as soon as the batch file exits). I also cannot call ENDLOCAL before setting the path because that restores the environment to its original state, wiping out the value of the variable so I cannot append its value to the path.
Is there a way to set the path while ignoring the SETLOCAL in effect? Alternatively, is there any trick to explicitly preserve the value of a variable from being wiped out when calling ENDLOCAL?
I am aware that it's possible to use a temp file as a vessel that can bypass the ENDLOCAL barrier, but a more elegant solution would be welcome.
Add this to the end of your script (or just before you return):
endlocal & #set path=%path%
The reason that this works is that the cmd parser performs environment variable expansion to the entire line before executing any of it. So by the time endlocal executes, the line that cmd is processing looks like:
endlocal & #set path=c:\whatever\the;c:\new\path;c:\is
Then endlocal throws away all the local changes, but the ones made to the path have been temporarily preserved in the set command that's about to be executed.

How to keep the value of a variable outside a Windows batch script which uses "delayed expansion local" mode?

Context: I need to call a Windows batch script which would update my PATH by adding another path 'xxx' at the end of it, but:
without any duplicate
(if I add 'xxx' to a PATH like 'aaa;xxx;bbb', I need an updated PATH like 'aaa;bbb;xxx')
without any aggregation
(I can call the script repeatedly without ending up with 'aaa;bbb;xxx;xxx;xxx;...')
What I have tried:
The following function takes care of any duplicate and does the job
:cleanAddPath -- remove %~1 from PATH, add it at the end of PATH
SETLOCAL ENABLEDELAYEDEXPANSION
set PATH=!PATH:%~2=!
set PATH=!PATH:;;=;!
set PATH=%PATH%;%~2
set P=!P:;;=;!
echo %PATH%
echo -------------
ENDLOCAL
exit /b
But, it needs delayed expansion local mode, which means: at the end of the script (or here, at the end of the function cleanAddPath), whatever has been set for %PATH% is thrown away.
I could ask the users (for which I write the script) to launch their cmd with a cmd /V:ON option (activating the delayed expansion, otherwise off by default), but that is not practical.
How can I modify the PATH variable the way I described above, and still have it updated in my current DOS session after calling said script?
The page "DOS - Function Collection" gives great example on how a function can return a value in DOS, even when using delayed expansion mode:
The following function will update any variable you want with an addition PATH:
:cleanAddPath -- remove %~2 from %~1, add it at the end of %~1
SETLOCAL ENABLEDELAYEDEXPANSION
set P=!%~1!
set P=!P:%~2=!
set P=!P:;;=;!
set P=!P!;%~2
set P=!P:;;=;!
(ENDLOCAL & REM.-- RETURN VALUES
SET "%~1=%P%"
)
exit /b
Note the concatenation of paths using. As jeb comments:
The line set P=%P%;%~2 is critical if your path contains ampersands like in C:\Documents&Settings.
Better change to set "P=!P!;%~2".
The SET "%~1=%P%" is the part which allows to memorize (in the variable represented by %~1) the value you have set using delayed expansion features.
I initially used SET "%~1=%P%" !, but jeb comments:
The command SET "%~1=%P%" ! could be simplified to SET "%~1=%P%" as the trailing exclamation mark has only a (good) effect in delayed expansion mode and if you prepared %P% before.
To update your PATH variable, you would call your function with:
call :cleanAddPath PATH "C:\my\path\to\add"
And it will persists after leaving that script, for your current DOS session.
dbenham's answer points to a more robust answer (upvoted), but in my case this script is enough.
The problem isn't as simple as you think. There are a number of issues that can break your code before it ever gets to the end where it needs to return the updated value across the ENDLOCAL barrier.
I already answered this question as an extension to an answer I provided for a similar question. See How to check if directory exists in %PATH%?. In that answer I provide a large list of issues that complicate the problem.
The code at the bottom of the linked answer shows how to reliably add a path if it does not exist in PATH already, and it also demonstrates how to reliably return the value across the ENDLOCAL barrier.
The following edits are from VonC in an attempt to actually put the answer here instead of just a link to the answer. I'll preserve the edit, but I find it difficult to follow without the context of the full linked answer.
[The answer demonstrates how to reliably return the value] using the set "%~1=%var%" ! trick (with the trailing '!')
That thread includes:
That's not clear to me. How can an exclamation mark behind the last quote influence the variable content?
The simple rule for delayed expansion is:
For each character in the line do:
If it is a caret (^) the next character has no special meaning, the caret itself is removed
If it is an exclamation mark, search for the next exclamation mark (carets are not observed here), then expands to the content of the variable
If no exclamation mark is found in this phase, the result is discarded, the result of the phase before is used instead (important for the carets)
So, at this point the difference should be clear, the carets are removed even if the exclamation mark have no other effect in a line.
Example:
#echo off
setlocal EnableDelayedExpansion
echo one caret^^
echo none caret^^ !
set "var1=one caret^"
set "var2=none caret^" !
echo !var1!
echo !var2!
----- OUTPUT ----
one caret^
none caret
one caret^
one caret
Yay! Finally got this working with the following test code:
#echo off
Setlocal enabledelayedexpansion
Set p="hello world"
( endlocal & rem return
Set "a1=%p%"
)
Set a1
This outputs:
a1="hello world"
The reason I used delayed expansion in the test without using any !'s is because it still effects how set works and the batchs I'm testing this for all have delayed expansion.
Thanks for the help guys :o)
PS I tried using the same variable name for both local and external environments but this broke the code. Hence the 2 names used.

Resources