Why doesn't the below batch file keep variables when CMD.exe terminates
#echo off
cmd /c "set var=hi"
if defined var (echo var is defined ("var=%var%")) else (echo var isn't defined)
pause
exit
Is there any way to use CMD /c, whilst keeping variables? and why doesn't CMD /c keep variables?
CMD /C starts a new sub-process that has its own environment space where variables are stored in memory. When that process terminates, the associated environment space is lost, so of course you lose the definition of any variables that were defined there.
It is not possible for the sub-process to directly access or manipulate the variables of the parent process.
If you are executing a simple command and have control over any output, then you can write the definitions to stdout and let FOR /F capture the output so that you can set the values in your parent process. But you will need delayed expansion.
Note - this example is pretty inane, but it should get the point across
for /f "delims=" %%A in ('cmd /v:on /c "set var=hi&echo "var=!var!""') do set %%A
But things can get complicated as far as identifying what is quoted, what needs to be escaped, ...
Life can be simplified if you create a batch script that does the work and execute it with FOR /F.
test.bat
#echo off
set var1=hi
set var2=Ain't this fun
set var3=bye
setlocal enableDelayedExpansion
for %%V in (var1 var2 var3) do echo "%%V=!%%V!"
main.bat
#echo off
for /f "delims=" %%A in ('cmd /c test.bat') do set %%A
Actually, FOR /F uses an implicit CMD /C, and you no longer need to enable delayed expansion in the command, so you can ditch the explicit CMD /C
#echo off
for /f "delims=" %%A in ('test.bat') do set %%A
If you don't have control of the output, then you will have to write the values to a temporary file that the main script can then load. You might be tempted to write a temporary batch script that defines the variables, but then you run the risk of having to escape some characters, depending on the content of the variables. The safest option is to use delayed expansion to write out the variable names and values, and let the parent process use FOR /F to load them in.
test2.bat
#echo off
set var1=hi
set var2=Ain't this fun
set var3=bye
echo Here is some output that must be preserved, unrelated to variables
setlocal enableDelayedExpansion
(for %%V in (var1 var2 var3) do echo "%%V=!%%V!") >"%temp%\vars.temp"
main.bat
#echo off
setlocal disableDelayedExpansion
cmd /c test2.bat
call "%temp%\vars.bat"
for /f "usebackq delims=" %%V in ("%temp%\vars.temp") do set "%%V"
del "%temp%\vars.temp"
Note that delayed expansion must be disabled in the main process when you load the variables, otherwise you will lose any ! characters in values when %%V is expanded.
But to be robust, you should do something to make sure that each instantiation uses a unique temp file name so that simultaneous runs do not clobber eachother.
Related
So when I run this code:
#echo off
setlocal enabledelayedexpansion
set lstFolders= First Second
set intCounter=0
for %%i in (!lstFolders!) do (
set /a intCounter += 1
set strFlder=%%i
set strFolder!intCounter!=!strFlder!
echo %%i
echo !strFlder!
echo !strFolder%intCounter%!
echo !strFolder1!
echo !strFolder2!
)
:End
pause
endlocal
It results with this:
First
First
ECHO is off.
First
ECHO is off.
Second
Second
ECHO is off.
First
Second
Why doesn't it allow me to echo the variable create with the format : !strFolder%intCounter%!? Is there another way to reference this variable and get the data that is inside of it?
As #mklement0 already said, you need an extra evaluation step.
But inspite to use echo "%%strFolder!intCounter!%%", I would recommend delayed expansion.
As delayed expansion is immune to any content, echo "%%strFolder... will fail with content containing quotes or exclamation marks.
#echo off
setlocal enabledelayedexpansion
set "lstFolders=First Second"
set intCounter=0
for %%i in (!lstFolders!) do (
set /a intCounter+= 1
set "strFlder=%%i"
set "strFolder!intCounter!=!strFlder!"
echo !strFlder!
set "varname=strFolder!intCounter!"
for /F "delims=" %%A in (""!varname!"") do echo Indirect %%~A=!%%~A!
)
The doubling of the quotes avoids problems with the eol character of the FOR/F loop.
Caveat: The code below only works with list values (the tokens of %lstFolders% such as First) that:
contain neither spaces
nor any of the following chars.: & | < > "
A different looping approach would be needed to handle such cases.
#echo off
setlocal enabledelayedexpansion
set "lstFolders=First Second"
set intCounter=0
for %%i in (%lstFolders%) do (
rem Increment the counter
set /a intCounter += 1
rem Echo the loop variable
echo #!intCounter!=%%i
rem Set variable strFolder<intCounter> to the loop variable's value
set "strFolder!intCounter!=%%i"
rem Echo the variable created using variable indirection with for /f ('...')
for /f "delims=" %%v in ('echo "%%strFolder!intCounter!%%"') do set "thisFolder=%%~v"
echo %%thisFolder%% ^(via %%strFolder!intCounter!%%^)=!thisFolder!
)
Running the above yields:
#1=First
%thisFolder% (via %strFolder1%)=First
#2=Second
%thisFolder% (via %strFolder2%)=Second
What you're looking for is variable indirection:
While you can set a variable indirectly (by a name that you construct dynamically from the value of another variable), e.g.,
set "strFolder!intCounter!=%%i", with !intCounter! having a value of 1, correctly sets variable strFolder1 to the value of %%i),
you cannot get a variable's value that way; you need an extra evaluation step, which for /f ... ('echo ...') can provide.:
for /f "delims=" %%v in ('echo "%%strFolder!intCounter!%%"') do ... parses the output of the command in single quotes (echo ...) and assigns the result as a whole (delims=) to variable %%v
(%%~v removes the enclosing double quotes, which were added around the echo argument to make the command handle shell metacharacters such as & | < > correctly).
%%strFolder!intCounter!%% immediately evaluates strFolder!intCounter! to strFolder1, if !intCounter! is 1, which, thanks to the enclosing doubled % instances, ends up as literal %strFolder1%, which is what the echo command sees when it is run by the for command, causing it to evaluate the variable reference and expand to its value.
I've question about this variables.How can I store result of a command into a variable? For example: saving drive serial into location variable.
thanks.
E.g:
#ECHO OFF
SET location=vol
ECHO We're working with %location%
for /f "delims=" %%x in ('your command and parameters') do set "var=%%x"
where the 'single quotes' are required around the command from which you wish to save output.
Edit - now we know which command.
Comment - one variable can contain up to ~8,000 characters.
Here's a routine that will set the values in $1...$whatever and the entire set in $all with each field enclosed in angle-brackets.
#ECHO OFF
SETLOCAL
SET "$all="
FOR /f "tokens=1*delims=:" %%a IN (
'systeminfo 2^>nul^|findstr /n /r "$"'
) DO (
SET "$%%a=%%b"
FOR /f "tokens=*" %%c IN ("%%b") DO CALL SET "$all=%%$all%%<%%c>"
SET /a max=%%a
)
SET $
pause
FOR /l %%z IN (1,1,%max%) DO CALL ECHO %%$%%z%%
GOTO :EOF
thank you...please see my batch file contents:
for /f "delims=" %%x in ('systeminfo') do set "var=%%x"
command_line.exe %var%
only last line of 'systeminfo' saved in var variable and it is:
"data execution perevention available: yes"
I want to store all contetnt of systeminfo into variable!
thank you.I'm too basic
do you mean result of systeminfo is into all variable? at the end of your code can I add this command:
start command_line.exe $%all%
is this true?
can you put complete code for my batch file?
I'm working on a batch script for fun and learning.
I've set up multiple choises, e.g Fint IP address, MAC address...and so on.
The problem is that i can't find out how i can insert the output of those two lines into variables.
for /f "usebackq skip=1" %%f in (`wmic COMPUTERSYSTEM get name`) do ???
for /f "usebackq skip=1" %%f in (`wmic COMPUTERSYSTEM get domain`) do ???
So I can use the output in a sentence like;
Your DNS-name is %dns_name_output% and your domain is %domain_name_output%.
try this:
for /f %%f in ("%computername%") do set "name=%%f"
for /f %%f in ("%userdomain%") do set "domain=%%f"
echo %name% %domain%
To capture wmic get output, use the /value flag like this
for /f "tokens=2 delims==" %%A in ('wmic computersystem get name /value') do set "Name=%%A"
for /f "tokens=2 delims==" %%A in ('wmic computersystem get domain /value') do set "Domain=%%A"
The problem you are seeing arises from the fact that WMIC output is in unicode. By some mechanism that I do not fully understand (a bug?), the FOR /F command transforms the unicode command output into ASCII, but mysteriously appends an extra carriage return (<CR>) at the end of each line.
FOR /F does not return empty lines, but the mysterious and seemingly blank lines are not really blank - they contain a <CR>.
Even if the extra lines are properly ignored, the last value in the list will include an unwanted <CR> that is included when assigning the value to an environment variable. The <CR> will not be apparent if the variable is later expanded normally using %VAR% because the command parser automatically strips all <CR> characters. But the <CR> is preserved and can cause problems if delayed expansion !VAR! is used.
The FOR /F command strips the last character from each line if it happens to be a <CR>. So passing the value through an extra FOR /F will eliminate the problem. David Ruhman's suggestion to use the /value switch is a good one, and can be improved upon. Multiple values may be requested in one loop, and the property name can be used as the variable name. Having only one name/value pair per line eliminates potential parsing problems with spaces and or commas in values.
The commas in the WMIC command must either be escaped or quoted when used within FOR /F. In this case, quoting the entire command seems easiest. The following will properly define two environment variables - Domain, and Name:
for /f "delims=" %%A in ('"wmic computersystem get domain, name /value"') do (
for /f "tokens=1* delims==" %%B in ("%%A") do set "%%B=%%C"
)
echo Your host name is %name% and your domain is %domain%
This works using wmic without any odd spaces or CRs embedded.
#echo off
for /f "tokens=2 delims=<>" %%a in ('wmic COMPUTERSYSTEM get name /format:htable^|find "hidden"') do set "name=%%a"
for /f "tokens=2 delims=<>" %%a in ('wmic COMPUTERSYSTEM get domain /format:htable^|find "hidden"') do set "dom=%%a"
echo "%name%,%dom%"
pause
You can define a variable, and assign it's value to the result of a command using back-tick enclosure.
Try this:
a=`ls`
echo $a
I am setting a number of variables using the for /f command.
setlocal ENABLEDELAYEDEXPANSION
set vidx=0
for /F "tokens=*" %%A in (target_list.txt) do (
SET /A vidx=!vidx! + 1
SET var!vidx!=%%A
)
set var
Now that I have set them I need to be able to be able recall them in a loop and apply them to the following commands.
copy gateway%num%.bat \\%var1%\C$\WINDOWS\system32
psexec \\%var1%\ gateway%num%.bat
del \\%var1%\\C$\WINDOWS\system32\gateway%num%.bat
The reason I need them in a loop is because the number of variables will change periodically and I need it to be able to figure out how many it made in the previous command and then apply them in the second command. I don't want to have to copy this command over and over and only change (var1) to (var2) to (var3) etc.
try this:
for /l %%i in (1,1,%vidx%) do copy copy gateway%num%.bat \\!var%%i!\C$\WINDOWS\system32
for /l %%i in (1,1,%vidx%) do psexec \\!var%%i!\ gateway%num%.bat
for /l %%i in (1,1,%vidx%) do del \\!var%%i!\\C$\WINDOWS\system32\gateway%num%.bat
searched this site and others - no joy.
:: first for loop
for /L %%x in (1,1,2) do (
generic-executable.output > grab1-%%x.txt
:: second, nested for loop
for /f "delims=" %%i in (grab1-%%x.txt) do (set grab1=%%i)
echo variable string is %grab1%%x%
generic-executable.output > grab2-%%x.txt
for /f "delims=" %%i in (grab2-%%x.txt) do (set grab2=%%i)
echo variable string is %grab2%%x%
)
Trying to run a nested for loop that will
1) write data to the file
2) take data from the file and save it to another variable.
The names of the end variables should be a concatenation of each of the for loops (i.e. grab1-1, 2-1, 1-2, 2-2).
Saving the data to the variables is no problem, formatting the variables to recall the data IS.
I'm most likely missing something in the formatting of the concatenated variable. I've tried single ', double ", ^, !, one %, two %, backslash, ACK!! ... the closest I've gotten is
echo %grab1-%%x
gave:
%grab1-1
I'd appreciate any tips you can provide.
Thanks,
Dave
You have run into a classic stumbling block for batch newbies: You cannot set a variable within a loop (within parentheses) and then access the value using %var% within the same loop. The Entire loop (parenthesized block of code) is parsed in one pass, and %var% is expanded at parse time. So you see the value of var as it was prior to the loop executing.
The solution is to enable delayed expansion using setlocal enableDelayedExpansion near the top of your script, and then expand the variable using delayed expansion as !var!. Delayed expansion means the value is expanded at run time - exactly what you want.
I believe this is what you were trying to achieve
setlocal enableDelayedExpansion
for /L %%x in (1,1,2) do (
genericOutput1.exe > grab1-%%x.txt
for /f "delims=" %%i in (grab1-%%x.txt) do set "grab1-%%x=%%i"
echo grab1-%%x variable string is !grab1-%%x!
genericOutput2.exe > grab2-%%x.txt
for /f "delims=" %%i in (grab2-%%x.txt) do set "grab2-%%x=%%i"
echo grab2-%%x variable string is !grab2-%%x!
)
::List all of the grab variable defined
set grab
You don't need to save the output of your executables to a file. (Unless of course that is your requirement). You can use FOR /F to process the output directly. The FOR command has many variants that look nearly identical, yet behave very differently. This variant uses single quotes to cause FOR /F to process a command.
setlocal enableDelayedExpansion
for /L %%x in (1,1,2) do (
for /f "delims=" %%i in ('genericOutput1.exe') do set "grab1-%%x=%%i"
echo grab1-%%x variable string is !grab1-%%x!
for /f "delims=" %%i in ('genericOutput1.exe') do set "grab2-%%x=%%i"
echo grab2-%%x variable string is !grab2-%%x!
)
::List all of the grab variable defined
set grab
Note that FOR /F will iterate each line (whether it be from a text file or from command output). Your algorithm will only save and print the content of the last line - each successive line will overwrite the value from the prior line.