Skip to content

How to Return a Value from a Subroutine in Batch Script

Learn the professional method to safely return values from batch script subroutines using SETLOCAL and the ENDLOCAL & SET trick, avoiding variable scope issues.

Subroutines (or "functions") are essential for writing clean, reusable, and organized batch scripts. You create them with a label (:MySubroutine) and execute them with CALL. A key part of any function is its ability to perform a calculation or an operation and then return a result to the main script. However, unlike most programming languages, batch scripting has no return statement.

Furthermore, the best practice of using SETLOCAL inside a subroutine to prevent variables from "leaking" creates a scope barrier: any variable you create is destroyed when the subroutine ends with ENDLOCAL. This guide will teach you the standard, professional method for safely passing a value out of a subroutine's local scope and back to the main caller.

The Challenge: SETLOCAL and Variable Scope

Good script design dictates that you use SETLOCAL at the start of a subroutine. This creates a new, local environment. Any variables you create or modify will not affect the rest of the script. When ENDLOCAL is called (or the script ends), this local environment is destroyed, along with all its variables.

This creates a problem: how do you get a result out?

An example of the problem

@ECHO OFF
SET "MyResult="
ECHO Before call, MyResult is: "%MyResult%"

CALL :CalculateValue

ECHO After call, MyResult is: "%MyResult%"
GOTO :EOF

:CalculateValue
    SETLOCAL
    SET "LocalValue=This is the result"
    ECHO Inside subroutine, LocalValue is: "%LocalValue%"
    ENDLOCAL
GOTO :EOF

Output:

Before call, MyResult is: ""
Inside subroutine, LocalValue is: "This is the result"
After call, MyResult is: ""

The LocalValue was successfully created but was trapped and destroyed by ENDLOCAL. We need a way to pass it across that barrier.

The most robust and widely accepted method is to pass the name of the variable you want the result to be stored in as an argument to the subroutine. The subroutine then uses a special trick to set this variable in the caller's scope as it exits.

@ECHO OFF
SETLOCAL

SET "MyResult="
ECHO Before call, MyResult is: "%MyResult%"

REM Call the subroutine and tell it to put the result in a variable named "MyResult"
CALL :CalculateValue MyResult

ECHO After call, MyResult is: "%MyResult%"
GOTO :EOF


:CalculateValue
    SETLOCAL
    REM %1 is the first argument passed to the subroutine, which is "MyResult"
    SET "LocalResult=This value came from the subroutine"
    
    REM --- This is the magic line ---
    ENDLOCAL & SET "%1=%LocalResult%"
GOTO :EOF

Output:

Before call, MyResult is: ""
After call, MyResult is: "This value came from the subroutine"

Success! The MyResult variable in the main script was successfully updated.

How the ENDLOCAL & SET Trick Works

The line ENDLOCAL & SET "%1=%LocalResult%" is one of the most clever and important idioms in advanced batch scripting. It works because of the order in which the command processor parses the line.

  1. & is a command separator, which allows two or more commands on a single logical line.
  2. Before any command on the line is executed, the command processor parses the entire line and expands all %PERCENT% variables.
  3. During this parsing phase, %1 is replaced with MyResult and %LocalResult% is replaced with This value came from the subroutine.
  4. The command that is now ready to be executed looks like this in memory: ENDLOCAL & SET "MyResult=This value came from the subroutine"
  5. Now, the commands are executed. First, ENDLOCAL runs, destroying the local scope and the LocalResult variable.
  6. Next, the second command, SET "MyResult=This value came from the subroutine", is executed. Since ENDLOCAL has already finished, this SET command is running in the caller's scope, where it correctly assigns the value to the MyResult variable.

Method 2 (Simple but Risky): Using Global Variables

A much simpler, but less safe, way to return a value is to simply not use SETLOCAL. Any variable you create or modify inside the subroutine will be "global" and will persist after it finishes.

For example:

@ECHO OFF
CALL :SetGlobalValue
ECHO The global value is: %GlobalVar%
GOTO :EOF

:SetGlobalValue
    SET "GlobalVar=This is visible everywhere"
GOTO :EOF

Common Pitfalls and How to Solve Them

  • Special Characters: The ENDLOCAL & SET trick is very robust, but strings with special characters can still be tricky. Always quote the SET assignment (SET "%1=%Value%").
  • Forgetting GOTO :EOF: Every subroutine must end with GOTO :EOF. If you forget it, the script will "fall through" and continue executing the code below it, leading to unexpected behavior.

Practical Example: A Subroutine to Add Two Numbers

This script demonstrates a function-like subroutine that takes two numbers as input and "returns" their sum to a variable specified by the caller.

@ECHO OFF
SETLOCAL

CALL :AddNumbers Sum 10 5
ECHO The sum of 10 and 5 is: %Sum%

CALL :AddNumbers AnotherSum 100 23
ECHO The sum of 100 and 23 is: %AnotherSum%

GOTO :EOF


:AddNumbers
    REM Usage: CALL :AddNumbers <ReturnVar> <Number1> <Number2>
    SETLOCAL
    SET "Num1=%2"
    SET "Num2=%3"
    SET /A "Result=%Num1% + %Num2%"
    
    ENDLOCAL & SET "%1=%Result%"
GOTO :EOF

Conclusion

While batch scripting doesn't have a formal return statement, it provides a powerful mechanism to achieve the same result safely and effectively.

  • The "return by reference" method is the professional and recommended best practice. It uses SETLOCAL for safe, local scoping and the ENDLOCAL & SET "%1=%Value%" trick to pass the result back to the caller.
  • The global variable method is simpler but should be avoided in complex scripts, as it can lead to unintended side effects and bugs.

By mastering the ENDLOCAL & SET idiom, you can write clean, modular, and reusable subroutines that behave like functions in any other programming language.

Navigation