1

I've recently been learning a bit about batch scripting and have spent some time the past few days creating different .BAT files to hone my skills and gain further understanding about how they work and the many things they can be used to accomplish.

Today, I was curious whether or not I could effectively call on batch script from another, so I created two batch scrips: test.bat and test2.bat. The code for each is as follows:

TEST.BAT

@echo off
cls
title test.bat

set /p "_result=Enter 'c' for capture or 'd' for deploy:"

call test2.bat %_result% 

TEST2.BAT

if "~%1" == "c" goto :capture
if "~%1" == "d" goto :deploy

:capture
echo _result is %_result%
echo %1
echo You are in the capture label!!
goto :eof

:deploy
echo _result is %_result%
echo %1
echo You are in deploy label!!
goto :eof

I have a few questions related to my above code that I think largely have to do with semantics, but that I also think will help me understand more completely what exactly is happening in this simple exercise.

Question 1:

When I pass the _result variable to test2.bat, is the _result variable now considered a parameter that's being passed to test2.bat? In other words, is it proper to say in this case that I am passing a parameter or, since it is a variable, should I say I'm passing a variable? Or is it both?

I have attempted to figure this out on my own, which is why I've included the echo _result is %_result% and echo %1 in my code. They both echo the same response, so I assume it would be okay to refer to it either way, but please correct me if I'm wrong.

Question 2:

Since it appears (to my current understanding of this) that _result holds it's variable attribute and value when passed to test2.bat, does this mean that it should be considered a global variable?

Question 3:

I'm a little confused by why it retains the same value in test2.bat that it was assigned in test.bat since they are two completely different files (especially if it's just considered a parameter once it's passed). Is test2.bat just considered an extension of sorts of test.bat in this case? And therefore all the variables designated in test.bat would be active in test2.bat?

I hope my questions make sense. Please let me know if I need to clarify anything. Thank you for your help.

3
  • Read SETLOCAL and CALL. Commented Sep 2, 2016 at 0:07
  • @aschipfl Thank you for your responses. They've helped me understand what's going on exactly. I wasn't entirely sure where to post my question, so I just guessed and posted it here. If it needs to be moved, please do so...or if I can do it let me know how. Commented Sep 4, 2016 at 0:07
  • I decided to convert my comments to an answer and extend it a bit, because I feel your question is definitely worth being answered. I still believe it would suit better on SuperUser, and I voted for that, so if others do so as well, it might be migrated in the future (and not deleted but closed here, providing a link); otherwise, it remains here, so let us see what happens... Commented Sep 6, 2016 at 22:34

2 Answers 2

3

Question 1

You are not passing the variable, you are merely passing its value. But the two batch scripts share the same environment context, hence the variable is also available in the called one.

Question 2

Yes, you can call it global in the environment context.
To localise environments use setlocal/endlocal, like in the following example:

caller.bat:

@echo off
set "VAR=foo"
echo CALLER: Variable before call:  "%VAR%"
call callee.bat "%VAR%"
echo CALLER: Variable upon return:  "%VAR%"
exit /B

callee.bat:

setlocal
echo CALLEE: Passed argument:   "%~1"
echo CALLEE: Original variable: "%VAR%"
set "VAR=bar"
echo CALLEE: Modified variable: "%VAR%"
endlocal
exit /B

When running caller.bat, the output is going to be:

CALLER: Variable before call:   "foo"
CALLEE: Passed argument:        "foo"
CALLEE: Original variable:      "foo"
CALLEE: Modified variable:      "bar"
CALLER: Variable upon return:   "foo"

If you removed setlocal and endlocal from callee.bat, the last line would show bar.

Question 3

Yes, you can think of the callee as it was embedded in the caller, with respect to the environment. When you do that with the above example scripts you will see the result is exactly the same.

But:

When it comes to block context, meaning parenthesised ( blocks of code ) or concatenated & commands, simply copying the callee into the caller does not work. This has nothing to do with the environment but with the way variables are expanded (read).

If call appears in a code block (like the body of a for loop for example), the callee is not executed in the block context of the caller, it starts a new block context. As soon as the callee terminates, the block context of the caller is restored. Let us take the above examples and modify them a bit:

caller.bat:

@echo off
set "VAR=foo"
(
    echo CALLER: Variable before call:  "%VAR%"
    call callee.bat "%VAR%"
    echo CALLER: Variable upon return:  "%VAR%"
)
exit /B

callee.bat:

echo CALLEE: Passed argument:   "%~1"
echo CALLEE: Original variable: "%VAR%"
set "VAR=bar"
echo CALLEE: Modified variable: "%VAR%"
exit /B

The call command line in the caller is now placed inside of a parenthesised block, the callee does not contain environment localisation (setlocal/endlocal), so one might expect the last returned line to show bar. But in fact it does not:

CALLER: Variable before call:   "foo"
CALLEE: Passed argument:        "foo"
CALLEE: Original variable:      "foo"
CALLEE: Modified variable:      "bar"
CALLER: Variable upon return:   "foo"

As soon as the last echo command line in the caller is moved after the closing ), the output holds bar. As said, all this has got nothing to do with the environment. This is caused by the fact that the entire (/) block is read at once and all variables are expanded (meaning replaced by their values) immediately, hence modification of variables is not reflected within that block. You can simply prove this by typing set "VAR=value" & echo "%VAR%" into the command prompt: the first time executed, the result will be "", because the &-concatenated line/block is read at once and %VAR% is immediately replaced by its current value, which is nothing at the first time, then execution of the commands start; when you execute it a second time, VAR already contains the string value from the beginning, so you will receive value as the result. This way of reading variables is often referred to as normal or immediate expansion, or also to percent expansion due to the enclosing %% signs of the variable.

There is a way to change this behaviour to something closer to other (real) programming languages, namely delayed expansion. Using that, you can assign or change a variable and access its value immediately, even in blocks. At first this feature needs to be enabled by the setlocal command (or alternatively, in command prompt, by the /V switch of cmd). Then it has to be actually used by enclosing the variable in a pair of exclamation marks, like !VAR!. Note that you can still use percent expansion when delayed expansion is enabled.

To apply that to the above example, it may look like that:

caller.bat:

@echo off
setlocal EnableDelayedExpansion
set "VAR=foo"
(
    echo CALLER: Variable before call:  "%VAR%"
    call callee.bat "%VAR%"
    echo CALLER: Variable upon return, but immediate expansion: "%VAR%"
    echo CALLER: Variable upon return, using delayed expansion: "!VAR!"
)
endlocal
exit /B

callee.bat:

echo CALLEE: Passed argument:   "%~1"
echo CALLEE: Original variable: "%VAR%"
set "VAR=bar"
echo CALLEE: Modified variable: "%VAR%"
exit /B

The output would now be:

CALLER: Variable before call:   "foo"
CALLEE: Passed argument:        "foo"
CALLEE: Original variable:      "foo"
CALLEE: Modified variable:      "bar"
CALLER: Variable upon return, but immediate expansion:  "foo"
CALLER: Variable upon return, using delayed expansion:  "bar"

Note: Although the block context has absolutely nothing to do with the environment, I still wanted mention this, because it affects the way variables are expanded/read.

Sign up to request clarification or add additional context in comments.

Comments

1

_result variable is set in the environment test.bat is called in, and that's why test2.bat can access it (and modify it), and also why you can echo _result after test.bat is finished.

When you call test2.bat %_result% you are just passing in the value of _result to test2's first argument (read by %1).

You can add setlocal at the start of test.bat to get the environment variables back where they were when you're done.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.