I am writing a batch library that must not be local, so that external batch files may call upon the functions from a single defined location.
This requires that the calling batch file must pass in a "unique" identifier for the variables named within. (I.E. pass in Application1 for %1).
I then wish to dynamically name variables, such as:
set %1_Timer=Hello
This works well, except I need to be able to evaluate said dynamic variables, but cannot find a solution that allows me to evaluate these parameter based dynamic variables.
I've tried solutions such as:
echo %1_Timer%
echo %1_Timer
echo %%1_Timer%%
echo %%1_Timer%
Call echo %%1%_Timer%
I cannot use a variable that is not dynamically named as other scripts utilizing this library might alter that non-dynamic variable, altering the output for other scripts.
You want to use delayed expansion.
#echo off
setlocal enabledelayedexpansion
echo !%1_Timer!
You could also use call and %% signs (call echo %%%1_Timer%%, but generally delayed expansion will be more useful. Also, it is mandatory if you ever set or modify a variable inside of a code block like a for or an if.
#ECHO OFF
SETLOCAL
SET myapp_timer=original_value
CALL :appsee myapp
CALL :appset myapp
CALL :appsee myapp
GOTO :EOF
:: set variable(s) - can be external batch to set caller's environment
:appset
SET %1_timer=hello
GOTO :EOF
:: see variable(s) - can be external batch to set caller's environment
:appsee
CALL ECHO %%1_timer=%%%1_timer%%
GOTO :EOF
Here's how.
Related
I would like to create a batch script which allows to create number and name of folder chosen by the user.
However, I received a syntax error using the script below.
variable "nome" is not taken.
Here is my code:
echo How many folders?
set /p cc=tell me how many
SETLOCAL EnableDelayedExpansion
FOR /L %%G IN (1,1,%cc%) DO (set /p nome=tell me the name
md %nome%)
pause
mr xyz, please use the search bar to search for delayed expansion. It's not that hard.
What Is DelayedExpansion
Batch-file variables are expanding in the moment the line is parsed. That means the nome variable isn't set when the entire for loop is parsed.
How-To Make The Variable Expand At Run-time?
SETLOCAL Method
Add
setlocal enableDelayedExpansion
anywhere before the for loop, so cmd will process the variable at run-time. And change %nome% to !nome!.
CALL MKDIR/MD Method
Change your MD statement to:
call MD %%nome%%
As this will trigger the emulated delayed expansion using the special property of call and %%.
I'm having trouble access the value stored in the example below. I need to access the value stored in a variable, but the variable name is stored in a different variable. Please help.
Example:
setlocal enabledelayedexpansion
set a222333password=hellopass
for %%i in (%fileserver%\t*) do (
set currentfilename=%%~ni --> file name is "t222333"
set currentlogin=!currentfilename:t=a! --> login is "a222333"
set currentpasswd=!!currentlogin!password! --> password should be "hellopass"
echo !currentpasswd! --> this gives me the value "a222333password" instead of "hellopass"
)
You cannot nest delayed expansion like set currentpasswd=!!currentlogin!password!, because this first detects !!, which are combined to one opening !, so the variable expansion !currentlogin! is done resulting in a222333, then there is the literal part password, and finally another ! that cannot be paired and is therefore ignored.
However, you could try this, because call initiates another parsing phase:
call set "currentpasswd=%%!currentlogin!password%%"
Or this, because for variable references become expanded before delayed expansion occurs:
for /F "delims=" %%Z in ("!currentlogin!") do set "currentpasswd=!%%Zpassword!"
Or also this, because argument references, like normally expanded variables (%-expansion), are expanded before delayed expansion is done:
rem // Instead of `set currentpasswd=!!currentlogin!password!`:
call :SUBROUTINE currentpasswd "!currentlogin!"
rem // Then, at the end of your current script:
goto :EOF
:SUBROUTINE
set "%~1=!%~2password!"
goto :EOF
rem // Alternatively, when you do not want to pass any arguments to the sub-routine:
:SUBROUTINE
set "currentpasswd=!%currentlogin%password!"
goto :EOF
All these variants have got two important things in common:
there occur two expansion phases;
the inner variable reference is expanded before the outer one;
I need help in passing a variable value of a batch file to another batch file.
I am using this statement:
call vartest.bat
if %username%==NA (
echo First login detected.
set /p usernameIN= Username:
#echo set username=%usernameIN% > vartest.bat
)
The problem is that, the value of "usernameIN" does not pass-on to the external batch file. I tried it using normal text instead of the variable and it works.
Is there any way to make this possible?
Thank you.
You need delayed expansion if you want to use a variable, that you changed in the same block (a block is a series of commands within brackets (and ))
setlocal enabledelayedexpansion
call vartest.bat
if %username%==NA (
echo First login detected.
set /p usernameIN= Username:
#echo set "username=!usernameIN!" > vartest.bat
)
Also you should use set "var=value" to avoid unintended spaces in the variable.
See here for a short demonstration of delayed expansion.
I'm trying to make a simple batch file ("javapath.bat") to add the Java compiler to the path when I need it so it won't be on the path all the time. I also want be able to do something like #call javapath.bat in other build scripts so the path can be added automatically when needed. Since those will be run repeatedly during the edit-save-compile-run grind, that means that javapath.bat needs to check if Java is already on the path and not readd it if it is, because apparently Microsoft thinks it's a good idea to let the path variable have lots of silly duplicates.
So to detect if it needs to be added I use setlocal to enable "command extensions" so I can use the environment variable string substitution thing. That ugliness works fine.
Then I use endlocal so I can actually set the enviroment variables without the changes being reverted at the end of the script. That's not working. Or, it certainly stops the variable changes being reverted, but it's not normal: it completely stops them from being visible locally, but they are still visible afterwards.
#echo off
setlocal enableextensions
if "%path:jdk1=%"=="%path%" (
endlocal
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
)
After the above, ANT_HOME and JAVA_HOME are properly set. But the only change to PATH is that "\bin;" has been prepended to it, because none of the variables set during the script seem to be visible until afterwards (so ANT_HOME and JAVA_HOME are blank, and the first change to PATH is forgotten). Therefore, running it twice adds Java to the path okay, and not Ant. I could hardcode the paths twice but this behavior is so bizarre and ridiculous and I've been stuck on it for an hour.
Edit: Adding enabledelayedexpansion had no effect either.
#echo OFF
ECHO starting %PATH%
if "%path:jdk1=%"=="%path%" CALL :addjava
ECHO.
ECHO resulting %PATH%
GOTO :eof
:addjava
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
SET "path=%ANT_HOME%\bin;%JAVA_HOME%\bin;%path%"
GOTO :eof
This is what I'd use - other methods run afoul of the mininterpreted closing-parenthesis problem.
The key to understanding this odd behaviour is history. Batch has always substituted the parse-time value of any %var% into the code, then validated the result and executed if valid. As the language developed, it was necessary to maintain compatibility with existing batches, so you could only ADD new keywords and functionality, not remove or alter functionality.
So, as the capacity to call internal subroutines was added, and cascade instructions on a single line with '&' and allow multi-line instructions for if and for by enclosing the instructions in parentheses were introduced, and the capacity to use spaces and other separator characters in file or directory names was required, the batch language began to have a few little quirks.
It was a really bizarre decision to have the ! to access the run-time value of a variable invoked as a subclause of setlocal - personally, I'd have used a switch like ECHO on/off (ie EXPANSION on/off) but I'm not running the project. In the same way, DATE could have been equipped with a /u switch to return the date in a universal form, but the opportunity was missed (and continues to be missed, 17 years after NT4 and 5 wingenerations later...)
As others have noted, extensions should already be enabled except under rather extraordinary circumstances. All you need is to eliminate your SETLOCAL and restructure your IF a bit so that it exits the script if the PATH is already set.
#echo off
if not "%path:jdk1=%"=="%path%" exit /b
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
If you really need to enable extensions, then
#echo off
setlocal enableExtensions
if not "%path:jdk1=%"=="%path%" exit /b
endlocal
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
If your script has additional work to do, then
#echo off
setlocal enableExtensions
if not "%path:jdk1=%"=="%path%" goto :skip
endlocal
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
:skip
REM carry on with additional code as needed
Everything inside the if block is evaluated in one go. So %ANT_HOME% has no effect after set ANT_HOME, you want delayed expansion you need to type:
setlocal enabledelayedexpansion
if "%path:jdk1=%"=="%path%" (
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
path = !ANT_HOME!;!path!
path = !JAVA_HOME!;!path!
)
:: important trick since they evaluate together %path% is still
:: what is inside local
endlocal & path %path%
path
Otherwise no delayed expansion. Also you need to use on undeleyed call with endlocal to escape the block. remember % variables never delay.
In my pursuit of a solution to another environment-variable/batch-file related problem, I have once again come across a problem I have visited before (but cannot for the life of me remember how, or even if I solved it).
Say you have two BAT files (or one batch file and the command line). How can one pass an environment variable name to the other so that it can read the variable? The following example does not work:
A.BAT:
#call b.bat path
B.BAT:
#echo %%1%
> A.BAT
> %1
> B.BAT path
> %1
It is easy enough to pass the environment variable name, but the callee cannot seem to use it. (I don’t remember if or how I dealt with this the last time it came up, but I suspect it required the less-than-ideal use of redirecting temporary BAT files and calling them and such.)
Any ideas? Thanks.
You can use a little trick which unfortunately is nowhere documented:
call echo %%%1%%
Then you can use delayed expansion:
setlocal enabledelayedexpansion
echo !%1!
Delayed expansion helps here mostly because it uses other delimiters for the variable and evaluates them directly prior to running the command, while normally the evaluation might clash with normal parameter expansion.
Another way of overdoing this would be a subroutine:
call :meh "echo %%%1%%"
...
:meh
%~1
goto :eof
All examples, including the other answer, have one thing in common here: They all force cmd to evaluate variables/parameters twice. It won't work otherwise, since the first evaluation must produce %VariableName%, while the second will expand that to the variable's contents.
You can find the code also on my SVN.
B.BAT:
FOR /F "delims=" %%a IN ('echo.%%%1%%') DO set inputvar=%%a
echo %inputvar%
That is one way of doing it.
If all you want to do is echo it, you can do: echo.%%%1%%|more or echo %%%1%%|find /v ""