Variable names can contain any character except = (Of course can't contain nul byte either, but nothing in batch supports nul, so I will ignore it).
Some characters can cause problems:
% in name can only be expanded using delayed expansion:
Code: Select all
@echo off
setlocal enableDelayedExpansion
set "var%%=hello"
set var
echo !var%%!
! in name can only be expanded using normal expansion:
Code: Select all
@echo off
setlocal
set "var!=hello"
set var
echo %var!%
But what to do if name contains both % and !
Code: Select all
@echo off
setlocal
set "var%%!=hello"
set var
Variables names can contain : anywhere in the name, but unless the : is the last character, then the variable cannot be expanded unless extensions are disabled.
Code: Select all
@echo off
setlocal enableDelayedExpansion
set "var:name=hello"
set var
::These don't work
echo %var:name%
echo !var:name!
setlocal disableExtensions
::These now work
echo %var:name%
echo !var:name!
The SET command can be used to display the definition of a variable. Normally it wouldn't be hard to use FOR /F to iterate the values of all variables that begin with the desired name, and simply parse out the value of the first line. But both the name and the value can contain linefeed characters. With some work it should be possible to handle linefeed in the name, but I don't believe it is possible to identify when the value ends. The value could contain a linefeed followed by a string that looks like a valid variable definition.
I postulate that is is impossible for a pure native batch solution to reliably expand any arbitrary variable. The only native Windows solution I am able to come up with is a hybrid JScript/Batch solution. The following should work with all possible names and values, with the one caveat that it could run into length limitations because the value is expanded slightly for a handful of characters. The solution uses a variation of jeb's safe return technique.
To avoid escape issues, I pass the name of the variable by reference: The first argument is the name of a variable that contains the name of the variable to be expanded. The optional 2nd argument is the name of the variable where the result is to be stored. If the return variable name is not specified, then the value is sent to stdout. The routine can be called safely with delayed expansion enabled or disabled.
GetVar.bat
Code: Select all
@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::************ Batch portion ***********
@echo off
if "%~2" equ "" (
cscript //E:JScript //nologo "%~f0" %*
exit /b
)
setlocal
set notDelayed=!
setlocal disableDelayedExpansion
for /f delims^=^ eol^= %%A in ('cscript //E:JScript //nologo "%~f0" %*') do set "rtn=%%A"
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
for %%1 in (^"^
^") do (
for /f %%2 in ('copy /Z "%~dpf0" nul') do (
for /f "tokens=1,2" %%3 in (^"%% """") do (
endlocal
endlocal
set "%~2=%rtn%" !
)
)
)
exit /b
************* JScript portion **********/
var env=WScript.CreateObject("WScript.Shell").Environment("Process");
var rtn=env(env(WScript.Arguments.Item(0)));
if (WScript.Arguments.Count() > 1) {
rtn=rtn.replace(/%/g,"%3")
.replace(/\n/g,"%~1")
.replace(/\r/g,"%2")
.replace(/"/g,"%~4");
}
WScript.Stdout.WriteLine(rtn);
Here are some test cases for the routine:
Code: Select all
@echo off
setlocal enableDelayedExpansion
set LF=^
for /f %%C in ('copy /Z "%~dpf0" nul') do set "CR=%%C"
set "var=:Part1!LF!Part2%%!LF! art3^!!CR!P!LF!&<>|()^^"
set "name!var!=value!var!"
set "var=name!var!"
echo Use SET to show value
echo -----------------------------
set !var!
echo(
echo Use GetVar.bat to show value
echo -----------------------------
call GetVar var
echo(
echo Use GetVar.bat to store value: Delayed Expansion ON
echo ----------------------------------------------------
call GetVar var delayedExpansionOnResult
set delayedExpansionOnResult
echo(
echo Use GetVar.bat to store value: Delayed Expansion OFF
echo ----------------------------------------------------
setlocal disableDelayedExpansion
call GetVar var delayedExpansionOffResult
set delayedExpansionOffResult
echo(
And here are the results
Code: Select all
Use SET to show value
-----------------------------
name:Part1
Part2%
Part3!
&<>|()^=value:Part1
Part2%
Part3!
&<>|()^
Use GetVar.bat to show value
-----------------------------
value:Part1
Part2%
Part3!
&<>|()^
Use GetVar.bat to store value: Delayed Expansion ON
----------------------------------------------------
delayedExpansionOnResult=value:Part1
Part2%
Part3!
&<>|()^
Use GetVar.bat to store value: Delayed Expansion OFF
----------------------------------------------------
delayedExpansionOffResult=value:Part1
Part2%
Part3!
&<>|()^
Dave Benham