5

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?

  • 5
    why you are using `cmd /c "set var=hi"`? this will create varible in a sub-process and the variable will die with it. – npocmaka Sep 21 '16 at 21:23
  • 1
    I appreciate that you're using an MCVE (seriously, that's super rare on this tag), but why in the world are you setting variables like that? – SomethingDark Sep 21 '16 at 21:27
  • @SomethingDark, it was only an example. In real use, I would perform `call cmd /c "%~0"` inside a batch file and then when the batch file exits, it would return to the main script. This is the only way I can think of to 'keep' a batch file from terminating its self. Additionally, if CMD /c kept variables then it would allow the rest of the script to access the variables it set. Long explanation soz – Burnie Riley Sep 21 '16 at 21:38
  • Since `%0` is the batch file itself, why not just `call "%~0"`? – SomethingDark Sep 21 '16 at 21:39
  • 1
    @SomethingDark if the batch file crashes eg. put |& into the batch file, then it just terminates the parent window, even if it has been called. If you use `call cmd /c "%~0"`, it just terminates the child CMD process, not the parent. It kinda sandboxes it if you know what I mean – Burnie Riley Sep 21 '16 at 21:42
  • See [this answer](http://stackoverflow.com/questions/39055349/difference-between-call-and-cmd-c-in-windows-batch/39057200#39057200). – Aacini Sep 22 '16 at 01:58

1 Answers1

3

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.

dbenham
  • 127,446
  • 28
  • 251
  • 390
  • 1
    In real use, I would perform call `cmd /c "%~0"` inside a batch file and then when the batch file exits, it would return to the main script. This is the only way I can think of to 'keep' a batch file from terminating its self. Additionally, if CMD /c kept variables then it would allow the rest of the script to access the variables it set. Can you think of any other solution that could allow a batch file to call it's self whilst keeping it's environment space? – Burnie Riley Sep 21 '16 at 21:40
  • @BurnieRiley - just add a `setlocal` to the start of your script and all the variables that get set inside of the script will be unset once the script terminates. – SomethingDark Sep 21 '16 at 21:43